Repository navigation
Conversation
…ead from a process global after the fact (refs #2250) P54 Phase 2 task 4 (plan-9ef825, Design Decision 8). CompileToBinary carried its ICE (3) / escaped-C# (2) / ordinary-error (1) distinction to its four callers (build, run, compile <file>.spy, the compile server's RunCompile) through the static CliHelpers.LastFailureExitCode, written by the failing call and read after it returned. A long-lived process serving many requests (sharpyc server) read "the most recent failed compile" — serialised today by the server's _compileLock, but a read-after-the-fact global all the same. It is now an `out int failureExitCode` of the call itself; the static is deleted. CliStaticStateConformanceTests (new, Cli.Tests) is the Cli twin of StaticStateConformanceTests: every static field of sharpyc is readonly/const, and a static readonly List/Dictionary/HashSet/ConcurrentDictionary/Lazy/ ConditionalWeakTable needs a Roster row. One row: ShowDiagnosticProvenance (written by every command handler from its own parse result before any rendering). Positive control: a scratch type with a mutable static and a static ConcurrentDictionary is flagged, a static readonly string is not. mutation: re-add `internal static int LastFailureExitCode` to CliHelpers → red (1 failed: "[not readonly] Sharpy.Cli.CliHelpers.LastFailureExitCode"), restored via cp → green (3 passed). Sharpy.Cli.Tests (Category!=Benchmark): 886 passed / 0 failed / 1 skipped @ this commit's tree (measured; baseline 883/0/1 @ 3f16f26 + the 3 new facts). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
… port instead of asserting on it (refs #2250) P54 Phase 3 task 4 (plan-9ef825, Design Decision 7, cell 13). Both refused-port cells — test_connect_with_timeout_refused_raises_error_not_timeout (inline bind-0/close probe) and the create_connection(timeout=) arm of _assert_timeout_rule (via _closed_loopback_port) — picked a port by binding 0 and closing it, so another process could take it before the connect (a low-probability flake at Stdlib.Tests' 4 threads). Holding the port bound is not a cure: a bound-but-not-listening port TIMES OUT on macOS (python3 3.12: TimeoutError; a closed port is ConnectionRefusedError at once). Both now route through _connect_to_closed_port(timeout, via_create_connection, first_port): one connect attempt is classified by _connect_outcome as "accepted" / "socket.timeout" / _timeout_outcome's name, and an accepted connect — the only symptom of a re-taken port — picks another port, up to five attempts. The assertions stay on the refused outcome ("socket.error", never "socket.timeout"). New cells: - test_connect_helper_reports_accepted_for_a_listening_port — positive control: a genuine accept is reported, not masked (python3: four connects to one listen(8) socket that never accepts all succeed — verified). - test_connect_to_closed_port_retries_an_accepted_first_pick — the retry arm: a LISTENING first pick is replaced by a fresh closed port → "socket.error". mutation: retry loop cut to one attempt (generated C# `attempt < 5` → `< 1`) → red (1 failed: TestConnectToClosedPortRetriesAnAcceptedFirstPick), restored via cp → green (64 passed). Spy-test C# regenerated (regenerate_spy_tests.sh: only socket_module_tests.cs changed). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…resh processes; two registries loading same-named assemblies from two paths each see their own (refs #2250) P54 Phase 1 task 5 (plan-9ef825, cells 4 and 5). Tests only; both reds land as literal KnownRed rows that assert the defect reproduces, so the Phase 2 task 2 cure fails each row as stale ("drain it"). CompileServerE2ETests.RequestSequence_NoRef_Ref_NoRef_AnswersAsThreeFreshProcesses (Cli.Tests): ONE `sharpyc server`; requests `run main.spy` -> `run -r Tablegreeter.dll main.spy` -> `run main.spy`, each sent by a child `sharpyc ... --server=<pipe>` (the route is asserted to reach the server) and compared with a fresh child `sharpyc` running the same command. Fingerprint = exit code | sorted SPY codes (SPY0908 spelled with its CS code) | the program's line when it ran. Every client is a child process, so the library never loads into the test host. The fresh oracle is pinned literally (SPY0300 / runs / SPY0300) so parity is not vacuous. By direction @ 486c9a8 (measured, 1/1): request 1 (no -r) server exit 1 SPY0300 = fresh -> worked (control) request 2 (-r) server exit 0 runs = fresh -> worked (control) request 3 (no -r) server exit 2 SPY0908(CS0400), fresh exit 1 SPY0300 -> ICE (KnownRed row "3: run main.spy") RegistryIsolationTests.TwoRegistries_SameNamedAssemblyFromTwoPaths_EachSeesItsOwn (Compiler.Tests/Project): two P54IsoTypes.dll (a name no other test loads) in two temp dirs, copy A = Isoapp.Alpha, copy B = Isoapp.Beta; two CompilerApi instances compile in a fixed order through the CLI's single-file route (ForCli + references). By direction @ 486c9a8 (measured, 1/1): A/Alpha ok -> worked (positive control) B/Beta refused SPY0200,SPY0301 -> wrong output: a valid program refused; B's load of the second copy fails in the default ALC (SPY0306, filtered out of the result) and B's semantic pass sees A's copy (KnownRed row) A/Beta refused SPY0200,SPY0301 -> correct refusal (the inert-fix control: a member another registry loaded must stay invisible) B/Alpha refused SPY0908(CS0234) -> ICE: B resolves A's Alpha at the registry, Roslyn compiles against B's file (KnownRed row) UsedAssemblyPaths is deliberately not in the oracle: the CLR-namespace import arms record none (observed "none" even for A/Alpha) - #2313's cell. mutation: delete KnownRed row "3: run main.spy" -> red (1 failed: server exit 2 SPY0908(CS0400) vs fresh exit 1 SPY0300), restored -> green (CompileServerE2E|Conformance 13 passed) mutation: delete KnownRed row "B/Beta" -> red (1 failed: must compile against its own reference), restored -> green mutation: delete KnownRed row "B/Alpha" -> red (1 failed: must be refused by a diagnostic, observed SPY0908(CS0234)), restored -> green (RegistryIsolation|ModuleNamespaceShadowsClrType + census classes 73 passed; final bytes RegistryIsolation 1 passed) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…static cache is rostered — 25 resolution sites (31 with the rostered key-pure loads) and 22 caches anchored to literals, drained by the visibility seam (refs #2250) ProcessWideResolutionScanTests (new, regular suite): a Roslyn SEMANTIC scan of src/Sharpy.Compiler/**/*.cs, compiled with the compiler's TPA references (Sharpy.Core under its SharpyRT alias) and its SDK global usings, reusing DispatchSiteScan's helpers. A site is a name whose bound symbol is AppDomain.GetAssemblies, the STATIC Type.GetType (any overload), a static Assembly.Load* (invoked or passed as a method group), AssemblyLoadContext.Default, or a construction of an AssemblyLoadContext (or a derived type). Every candidate name has to bind. An unresolved candidate turns the test red, so a degraded compilation cannot pass by finding nothing. Two lists: Roster (permanent, 4 keys / 6 sites: the netstandard and framework-name Assembly.Load calls in AssemblyCompiler and ReplSession, the REPL's own submission bytes, and the collectible SharplyGenerators context) and Allowlist (drain-on-fix, 21 keys / 25 sites, each citing #2250 "drains in Phase 2"). Rows are keyed file::Type.Member::kind with a site count, never a line. Unlisted keys, count mismatches and stale rows are each red. Literal anchors: 14 AppDomain + 7 static Type.GetType, 25 allowlisted, 6 rostered, 31 total. Census measured @ 486c9a8: 31, which matches the verifier. TypeSyntaxMapper.cs:800's instance GetType() is correctly not counted. StaticStateConformanceTests rule 2b: static readonly ConcurrentDictionary<,> / Lazy<> / ConditionalWeakTable<,> fields in the compiler assembly are no longer exempt because of their type. Each one needs a ProcessWideCacheRoster row giving its key-purity justification. Static auto-property backing fields are checked too, keyed by the property name. Measured @ 486c9a8: 22 (20 ConcurrentDictionary + 2 Lazy, 0 ConditionalWeakTable), anchored by Assert.Equal(22). Five name-keyed rows cite #2250 "drains in Phase 2" (_originNameDefinitionCache, _displayNameDefinitionCache, s_typePresentCache, _probedNamespaces, _loadedRuntimeNamespaces). The other 17 are permanent, with the key read for each: content-addressed (s_referenceCache, s_inMemoryIndices), keyless process constants (s_version, _byName over typeof(Enumerable)), and Type/Assembly-keyed reflection memos. Every value is a function of its key alone (ClrTypeBridge's _mapsToSharpyBuiltinCache probes a parameterless bridge's FullName arm table). No new not-key-pure cache was found. A theory pins each IsProcessWideCache arm with positive and negative controls; ConditionalWeakTable has zero census hits. Synthetic controls (same classifier, in-memory sources): one AppDomain scan is found. Instance x.GetType() and typeof(T).GetType() are not found, while static Type.GetType in the same source is. One cell per kind/spelling (Load, LoadFrom, LoadFile, a LoadFrom method group, Default, explicit, target-typed and derived ALC construction, and an AppDomain held in a local) is found. An unbound Missing.Domain.GetAssemblies() is reported unresolved, and the bound scan beside it is still found. mutation A (scratch compiler sites in Shared/CompilerIdentity.cs: an AppDomain scan, a static readonly ConcurrentDictionary, a static Lazy auto-property) → red (2 failed, 20 passed): census [unlisted] CompilerIdentity.MutationScan, rule 2b lists both the field and the auto-property; restored (cp + touch) → green mutation B (delete Allowlist row TypeChecker.TryGetClrType; delete roster row ClrTypeHelper._paramsIndexerCache; drop IsStatic from the Type.GetType arm) → red (3 failed, 19 passed): census [unlisted], rule 2b [unlisted] _paramsIndexerCache, Synthetic_InstanceGetType (2 sites); restored → green mutation C (no synthesized global usings) → red (1 failed, 12 passed): 322 UNRESOLVED lines, only 18 of 31 sites found; restored → green restored: 66 passed / 0 failed (the two classes, 22 tests, plus StaticState/ProcessGlobalState/PropertySeedDiscipline/CiFilterCoverage/EmitterCarrierOnly) @ 486c9a8 + this change (measured). Scan wall time about 2 s, 22 tests in 7.5 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…ock tick — an internal clock seam on CrashBundleWriter (refs #2250) P54 Phase 1 task 6 (crash half), plan cell 12, Design Decision 6. Seam: CrashBundleWriter gains an internal TryWrite(string?, CrashBundleRequest, Func<DateTime> clock) overload holding the existing body; the public TryWrite calls it with () => DateTime.UtcNow. Only the bundle-directory timestamp reads the clock (the report's "Timestamp (UTC)" line is untouched). No settable static, so StaticStateConformanceTests rule 1 has nothing to flag. The check-then-create suffix scan is NOT fixed here (Phase 3 task 3). Test: CrashBundleExclusivityTests theory over writers {1, 2, 8} x 10 bundles, each writer on its own thread behind a start Barrier, every request carrying a unique marker (Producer and exception message). The injected clock returns one constant tick AND re-aligns the writers on a per-round Barrier, so round b of every writer scans the same directory listing; a scheduler skew cannot serialize the cell green. Oracle: no null return, writers*10 distinct returned paths, writers*10 directories on disk, and every report.md names its own marker as producer and message and no other writer's marker. Measured @ 486c9a8 (R-iteration loop of the same RunWriters, since deleted): writers=1: red 0/300 (always 10 of 10) -> positive control, asserted exclusive writers=2: red 1000/1000 (at most 12 of 20 distinct) -> KnownRed row, asserts the defect reproduces writers=8: red 300/300 (at most 37 of 80 distinct) -> KnownRed row, asserts the defect reproduces (unaligned constant clock, before the per-round barrier: 2 -> 1000/1000 at most 19 of 20, 8 -> 300/300 at most 49 of 80; aligned adopted for margin) No null returns observed: overlapping writers share one directory and the later report.md overwrites the earlier (foreign reports, not torn ones). mutation: KnownRed rows for 2 and 8 removed -> red (2 failed, 13 passed of the CrashBundle filter), restored via cp+touch -> green (15 passed) fix-direction control: the Phase 3 cure simulated in CrashBundleWriter (CreateDirectory then FileMode.CreateNew on .owner, next suffix on IOException) -> both KnownRed rows report "stale, drain" with 80/80 and 20/20 distinct (2 failed), writers=1 green; production restored via cp+touch -> green (15 passed) census filter (StaticStateConformance|ProcessGlobalStateConformance| PropertySeedDiscipline|CiFilterCoverage|EmitterCarrierOnly): 46 passed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…xclusive marker and named with the process id — two crashes in one millisecond get two bundles (refs #2250) P54 Phase 3 task 3 (plan-9ef825, Design Decision 6, cell 12). TryWrite picked its directory by "while (Directory.Exists(candidate)) suffix++, then CreateDirectory" — check-then-create: two writers in one millisecond (one compile server, one LSP, parallel tests) read one free suffix, share its directory, and the later report.md overwrites the earlier one. Now the directory is <timestamp>-<pid>[-n]: the process id separates processes, and within one a writer owns a candidate only if it creates the `.owner` marker with FileMode.CreateNew — the one create-exclusive primitive .NET offers on every platform (CreateDirectory succeeds on an existing directory, so it cannot tell two writers apart). A writer that finds the marker taken moves to the next suffix; after 100 candidates it returns null ("no bundle"), as any failure did. CrashBundleExclusivityTests (8 writers × 10 bundles at one injected clock tick, added at 522a19e): with the KnownRed rows still present both cells reported "stale — 80/80 and 20/20 distinct" (2 failed); the rows (1000/1000 and 300/300 red @ 486c9a8) are drained here. mutation: CrashBundleWriter.cs replaced by its pre-fix copy (git show HEAD) → red (2 failed: writers=2, writers=8), restored via cp → green (4 passed; cmp identical). CrashBundle|InternalCompilerErrorBundle|StaticStateConformance| ProcessWideResolutionScan: 38 passed / 0 failed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…sus and exercised at 1/2/8 threads against CPython's process-wide model (refs #2250) P54 Phase 1 task 4 — tests only, no stdlib fix (Phase 3 cures). StdlibModuleGlobalRosterTests: a Roslyn census over src/Sharpy.Stdlib/**/*.cs (generated spy C# included; bin/obj/spy/modules excluded as the csproj does), bound by a semantic model built from the Stdlib's own references + net10.0 preprocessor symbols and asserted error-free (0 errors, 0 unbound targets). A static non-const field is WRITTEN when it is the target of an assignment / compound / ??= / ++ / -- / element assignment / ref-out argument outside its own initializer, or the receiver of a literal mutator-roster call. Found @ 486c9a8: EIGHT written statics — the plan's six (random _Random, socket _DefaultTimeout, calendar _moduleFirstweekday, xml _registeredNamespaces, logging _loggers, numpy.random _rng) plus two the census found (rostered, safe, ledger rows): HTTPStatus._valueMap (written only by the private ctor inside the type initializer) and TypedLoadContract.FieldCache (key-pure ConcurrentDictionary). Totality anchored to the literal 8; the spy constants (math.pi, socket.AF_INET) are excluded by the predicate (negative control); generated random.seed's `_Random = new ...` is the positive control; one synthetic snippet per predicate arm. StdlibModuleGlobalConcurrencyTests: per rostered global x {write, read, cross-thread} x threads {1, 2, 8} x 2000 iterations through the public module API on dedicated threads, CPython/numpy facts verified with python3 3.12.13 / numpy 2.5.1 and quoted in comments. Cross-thread reads use a different seed/value per iteration so a per-thread state cannot pass by re-seeding itself. Collections: random -> PrngState (existing); new parallel collections SocketDefaultTimeout, CalendarFirstweekday, XmlRegisteredNamespaces, NumpyRandomState, each shared with the spy test class reading that global (twins in Spy/SpyTestCollections.cs); logging uses fresh names, no collection. KnownRed rows (6 runs @ 486c9a8): numpy.random write/1,2,8, read/1,2,8, cross-thread/2,8 deterministic 6/6; random/read/8 4/6 (e.g. 15803/16000), random/read/2 0/6; xml write/8 6/6, write/2 3/6, read/8 5/6, read/2 1/6; socket write/read at 2 and 8 0/6 — mechanism only (16-byte double? not atomic by the CLR memory model), never reproduced. Green controls: calendar, logging, HTTPStatus, typed-load cache, random write/cross-thread, socket/xml cross-thread, numpy.random/cross-thread/1. mutation: roster row (calendar) deleted + Stdlib non-framework references dropped + KnownRed rows numpy.random/read/8 and xml/write/8 removed -> red (6 failed / 77 passed: EveryWrittenStatic unlisted, Totality, EveryRosterRow_ HasAnExerciser, TheSemanticModelBinds 2403 errors, numpy.random/read/8 16000/16000, xml/write/8 4190/16002), restored -> green (83 passed) mutation: written static P54Mutant.Add(x) added to a cp-backed Calendar/CalendarConstants.cs -> red (2 failed: unlisted P54Mutant via mutator .Add, Totality), restored -> green (83 passed) pre-commit: Stdlib.Tests Conformance 456/0/0, Random 78/0/0, Compiler.Tests ProcessGlobalStateConformance 34/0/0 @ this tree on 486c9a8. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…ring × threads × warmth, ratcheting in-process-isolation-allowlist.txt (refs #2250) P54 Phase 1 task 1: the interleaving generator. InProcessIsolationCorpus is a literal roster (cell 1's program regenerated from SharpyFuzzer(2025) iteration 18 and refused if the generator drifts; the three P47 stems; `from system.io import DriveInfo`; the CLR-attribute fixture decorators/bracket_attr_import_used_1429) plus a stride-16 sample of the single-file fixture corpus (213 programs; fixtures with a .features sidecar excluded, since a Compiler binds its features at construction). Each thread runs a schedule drawn from new Random(<literal seed>): the roster under both operations plus 36 sample draws, shuffled (48 steps). Sharing {compiler-per-thread, api-per-thread, api-shared} × operations {analyze, compile-through-IL}. The oracle is the same (program, operation) on a fresh instance alone on the test thread, computed before the concurrent phase. The fingerprint is success + the sorted (code, line, col) multiset + UsedAssemblyPaths (compile on both kinds and Compiler.Analyze; CompilerApi's SemanticResult exposes none) + the type of an escaped exception. Buckets: crash, order (one thread, warm instance differs from a fresh one) and drift (2 or 8 threads). The GapDiscovery fact is 8 threads × R=8; the regular slice is threads {1, 2} × R=1, and SHARPY_ISOLATION_SWEEP_FULL=1 runs {1, 2, 8} × R=8 (read only). Reports go to .claude/tmp/in-process-isolation-report.json (GapDiscovery) and .claude/tmp/in-process-isolation-slice-report.json (slice). The code-action Extract and applied-text operations are lane "warm"'s LSP twin, not here. Measured @ 486c9a8 (worktree), 0 reds in both modes, so the allowlist ships EMPTY (header only). Nothing is invented. GapDiscovery run 1: observations=9216 compared=9216 cancelled=0 cells=660 red=0 (drift=0 crash=0 order=0), elapsed 374 s (machine shared with other agents' runs) GapDiscovery run 2: same counts, red=0, elapsed 153 s regular slice: observations=432 cells=372 red=0; 21 s / 13 s / 15 s over three runs Limit: the oracle phase warms the process before the concurrent phase, so a first-use-in-process race (cell 1's TPA gate) cannot show here. That is task 2's barrier probe. Mutations (regular slice, `FullyQualifiedName~InProcessIsolation&Category!=GapDiscovery`): (b) oracle discriminates: oracle[(Api, tpa/drive_info, compile)] replaced by iter-18's → red (1 failed, 4 unlisted violations: the cell's {api-per-thread, api-shared} × {1, 2} instances, order on 1 thread, drift on 2), restored → green (3 passed) (a') stale arm (no reproduced row exists to delete): appended the deterministic row `tpa/drive_info api-shared 2 analyze drift # #2250 …` → red (1 failed, exactly that stale row), restored → green (3 passed; the second full run, 4 passed incl. GapDiscovery) Controls (regular suite): Oracle_DiscriminatesPrograms_PositionsAndBuckets (two fresh instances of one program are equal; different programs and a one-line move differ; each bucket per thread count, on both kinds × both operations); Schedules_AreSeededAndCarryTheRoster. Finding (ledger, outside #2250): Compiler/CompilerApi with OutputAssemblyPath and no AssemblyName ICEs with SPY0908 CS8203 (empty assembly name). The sweep names the assembly as ForCli does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…refs #2250) P54 Phase 1 task 7. The sweep matrix gains its sixth entry, `in-process-isolation` (`Category=GapDiscovery&FullyQualifiedName~InProcessIsolation`, python: false). The ci-summary expected-trx list gains `sweep-in-process-isolation`, and the header comment now reads "matrix of 6". COMPILER_MAIN_FILTER excludes the sweep by Category AND name, `&(Category!=GapDiscovery|FullyQualifiedName!~InProcessIsolation)`, not by name alone as its siblings do. Unlike them, InProcessIsolationSweepTests carries a regular-suite slice (threads {1, 2} × 1) plus two controls, and those must stay in the Compiler shards. A bare `FullyQualifiedName!~InProcessIsolation` would leave them selected by no step. The evaluator in CiFilterCoverageConformanceTests already reads parenthesised filters, so its roster needed no change (it reads the workflow itself). Mutations (`FullyQualifiedName~CiFilterCoverage`): main filter spelled `&FullyQualifiedName!~InProcessIsolation` → red (1 failed: EveryCompilerTest_IsSelectedByAtLeastOneCiStep, 3 tests unselected — Sweep_RegularSlice, Oracle_DiscriminatesPrograms_PositionsAndBuckets, Schedules_AreSeededAndCarryTheRoster) the new sweep matrix entry removed → red (1 failed, 1 test unselected — Sweep_EightThreads_EightRepetitions) restored → green (census filter StaticStateConformance|ProcessGlobalStateConformance| PropertySeedDiscipline|CiFilterCoverage|EmitterCarrierOnly: 46 passed) Regular slice @ this tree: 3 passed, red=0, 30 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…are probed for completion and cross-compile poisoning under 8 threads (refs #2250) P54 Phase 1 task 2: the cell-1 seam probes (Conformance/TpaNamespaceGateTests.cs). Production change: one test seam, passed as a PARAMETER. The new internal ModuleRegistry.GetNamespaceTypes(string, Action<string>? beforeTpaLoad) is called by the public GetNamespaceTypes(string) with null. EnsureRuntimeAssembliesLoaded keeps its name, gains the parameter, and invokes it after winning the TryAdd gate, before any TPA load. There is no new static and no settable hook, so no StaticStateConformance row is needed. With null, behaviour is unchanged. Site note for the resolution-scan roster: the AppDomain scan in GetNamespaceTypes now lives in the internal (string, Action<string>?) overload (same member name; the public overload is a one-line delegation). The scan and the LoadFrom in EnsureRuntimeAssembliesLoaded stay in the same member. Probe 1, TpaNamespaceGate_ConcurrentFirstUse_IsComplete: thread A wins the gate and blocks in beforeTpaLoad; seven fresh registries, released by a Barrier, ask meanwhile; A is released once they have answered, or after 5 s. A gate with a completion barrier forces the 5 s path, because its askers wait for A. - Mechanism cell (tpa-gate.passes-before-completion, KnownRed): uses a fresh system.p54tpagateprobe<guid> key that no test can have used and that loads nothing. 7/7 askers returned while A was inside. Measured in EVERY host. - Count cells, on microsoft.win32 plus a test-only marker type (own load context) so the dynamic mapping branch maps it. The concurrent-first-use cell (KnownRed) gave askers [1,1,1,1,1,1,1] against solo 9. Green controls: the gate winner 9 = 9, and after completion 8/8 = 9. These cells need a first-use namespace, and the gate is single-shot per process. A bare `from system import` (62 test files) loads every System.* TPA file, so no system.* namespace is first-use in a shared host. The first version of this probe used microsoft.win32 and failed as INCONCLUSIVE in a Conformance co-run (16706/1/0): an earlier test had already loaded Microsoft.Win32.Registry. The loader was not identified. Where the namespace is not first-use, the count cells print NOT MEASURED; the mechanism cell is asserted either way. Probe 2, OriginNameCache_CrossCompile_IsNotPoisoned (ClrTypeHelper._originNameDefinitionCache, via InternalsVisibleTo; test-only assemblies built in memory, each loaded into its own context): - reference-seen-by-unreferencing-compile (KnownRed): 8/8 threads. A name loaded in compile A's context resolves to A's Type for a compile with no reference. This is the static cache AND the AppDomain scan beneath it. - unresolved-name-pinned-null (KnownRed): 8/8 threads. A null cached by a compile that could not see the name is the answer for a later compile whose reference holds it. Cache only. - framework-name (List`1): green control. VERDICT: cell 1 seam = neither. The gate and the cache are both real, deterministic defects of the class (reproduced 3/3 + 5/5 class-alone and 9/9 + 7/7 test-host runs @ 486c9a8, before the final runs below), but neither produced cell 1's "5 vs 1": - The iter-18 program (`from snake_case import __dunder__` / `def count() -> tuple:` ...) imports no system.* namespace, never enters the gate, and does no stored-name resolution. - Run with the timeout at infinity (CancellationToken.None) on 8 threads x 50 repetitions (scratch test, not committed), twice @ 486c9a8: 0/400 + 0/400 pairs differ, and 1600/1600 compiles equal the solo 5-diagnostic set (SPY0300, SPY0200, SPY0403, SPY0266, SPY0320). - The same program with a pre-cancelled token gives exactly 1 diagnostic: [SPY0901]. - Cell 1 was measured @ af6f309 (2026-10-05). At that commit SemanticPropertyTests had FuzzIterationTimeoutMs = 2000 and no WasCancelled skip. 4562b1d (#2278, 2026-10-08) raised the timeout to 30 s and now skips a cancelled pair; its own mutation B reproduced this shape ("count differs: 4 vs 1", [SPY0901]). So the "1" is a 2 s cancellation under 8-thread load. The Adversarial Review's exclusion of cancellation read the code @ 486c9a8, not the code at the measurement sha. mutation M1: rows passes-before-completion + unresolved-name-pinned-null removed -> red (2 failed, 1 passed): "7 askers returned ... while the winner was still inside it"; "8/8 ... returned null" mutation M2: rows concurrent-first-use + reference-seen removed -> red (2 failed, 1 passed): "7/7 askers ... counted [1,1,1,1,1,1,1] vs solo 9"; "8/8 ... from compile A's context" mutation M3 (production cure-mutant): EnsureRuntimeAssembliesLoaded body under lock (_loadedRuntimeNamespaces), i.e. a completion barrier -> red (1 failed, 2 passed): "STALE KnownRed row 'tpa-gate.passes-before-completion'". The probe reports the cure. restored (cp + touch, cmp-identical to the pre-mutation copies) -> green Final bytes @ 486c9a8 + this change (measured): ModuleRegistry|TpaNamespaceGate 21/0/0 (class alone: every cell measured); census (StaticState|ProcessGlobalState|PropertySeedDiscipline| CiFilterCoverage|EmitterCarrierOnly) 46/0/0; Sharpy.Compiler.Tests.Conformance&Category!=GapDiscovery co-run 16707/0/0 (the same co-run was 16706/1/0 with the first, fail-when-unmeasurable version). Wall time of the new tests: gate probe ~0.25 s (class alone), cache probe ~0.4 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…inst a cold process on the P47 stems, with the master-registry key-set snapshot between steps (refs #2250) P54 Phase 1 task 6 (LSP half), bullet 8. Tests only. - WarmInstanceParityTests (Lsp.Tests/Conformance): the three P47 stems (cr twin, P47 selections) x warmth {fresh instance (positive control), warm-sequential, warm- concurrent DOP 4 x 4} x operation {analyze, Extract method applied through SharpyCodeActionHandler, applied-text analyze}; oracle = a child Sharpy.Lsp process per document via LspTestClient. The masters' key sets are snapshotted between every step. - MasterRegistryKeySets (TestInfrastructure, shared by Lsp.Tests and Compiler.Tests): key-set snapshot of BuiltinRegistry _interfaceSymbols/_clrTypeCache/ _pendingInterfacePopulation/_types/_functions, CachedModuleDiscovery and ClrTypeBridge caches, ModuleRegistry _dynamicNamespaceCache/_loadedAssemblies/_usedAssemblyPaths; process-wide name-keyed statics recorded, never asserted; a renamed field throws. - WarmInstanceTriggers (TestInfrastructure, shared): the two earlier documents that flip the targets (see seam below). - MasterRegistryImmutabilityTests: key sets are a fixed point after one warm-up pass of AnalyzeDocument(ForLsp) -> handler queries -> applied-text Analyze -> oracle Analyze (green: no growth on pass 2); symbol properties compared from the baseline (2 KnownRed writes); WarmInstanceTriggers_DoNotRetypeABuiltinForALaterDocument (compiler-level twin of cell 2, print = positive control); KeySetSnapshot_DetectsAWriteToAMasterDictionary. The RegistrySnapshot walk now skips a null dictionary value (it NRE'd once _clrTypeCache cached an unresolved name). Seam (Design Decision 4), measured: a SYMBOL-PROPERTY WRITE, not dictionary growth and not _clrTypeCache's TPA dependence. Replaying the P47 FULL DOP-1 sweep on one instance @ 43aab26 (worktree) flips the targets' builtin identifier types with zero instance key-set change; clearing each master cache does not cure it; a fresh instance in the same warm process is correct; per-document replay names two Wrap-in-try applied texts (a builtin's name defined inside try:) whose analysis rewrites the master len/format FunctionSymbol.Parameters. @ 486c9a8 the live sweep no longer produces those texts (e3c5cbd), so a full sweep replay is green - but replaying the texts reproduces every P47 cell: +SPY0220 on the IFormattable fixtures, Extract parse (12,55)/(8,55)/(5,148) vs cold (12,59)/(8,59)/(5,149). Reds @ 486c9a8 (KnownRed rows, drain in Phase 2 task 3): warm-sequential 5 cells deterministic, 1/1 in each of 2 runs; warm-concurrent the same 5 cells probabilistic (k/4 = 2,2,3,3,2 | 1,1,0,0,1 | 4,4,4,4,4 over three runs), xunit maxParallelThreads 8, 14 processors, Lsp Conformance classes concurrent. Green controls: all fresh cells, all applied-analyze cells, deferred analyze. mutation: deleted KnownRed row 'deferred_overload_annotated_pin_1589#cr warm-sequential extract' -> red (1 failed: WarmSequential); deleted KnownRetypedBuiltins['len'] -> red (1 failed); MasterRegistryKeySets.DiffTo never reports -> red (1 failed: KeySetSnapshot_DetectsAWriteToAMasterDictionary); restored (cp + touch) -> green (Lsp WarmInstanceParity 4/4, Compiler MasterRegistryImmutability 8/8). Runs: Lsp.Tests WarmInstanceParity|Conformance|Inventory 16624/0/0, Compiler.Tests MasterRegistryImmutability + census filter 54/0/0 @ this tree. Wall time in the 8-thread Lsp filter: warm-sequential 40 s, warm-concurrent 31 s, fresh 3 s (+ cold oracle); alone: 4 s / 6 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
… the static-state rules (refs #2250, refs #1036) StaticStateConformanceTests dropped every compiler-generated static field before rules 1 and 2 ran, so a static auto-property's `<X>k__BackingField` escaped both: `static int Counter { get; set; }` (a plain mutable static — rule 1) and `static Dictionary<K, V> Map { get; } = new();` (a mutable static collection — rule 2) were invisible, though each is exactly the field it hides. Rule 2b already keyed a process-wide-cache auto-property by its property name; now every rule does: a backing field is read as its property (key = declaring type + PROPERTY name) and only other compiler-generated fields (lambda caches, fixed buffers) are skipped. Zero such properties in the compiler today, so no allowlist row changes and the 2b census stays at 22 (ledger row, census lane). The rule scans the compiler assembly, so the positive control is a mutation of a compiler type, not a synthetic type in the test assembly. mutation: added `internal static int MutationProbe { get; set; }` and `internal static Dictionary<string, int> MutationProbeMap { get; } = new();` to src/Sharpy.Compiler/Shared/CSharpKeywords.cs (cp-backed) → red (1 failed: CompilerAssembly_HasNoUnlistedMutableStaticState naming `[rule 1: not readonly/const] Sharpy.Compiler.Shared.CSharpKeywords.MutationProbe : Int32` and `[rule 2: mutable static collection] Sharpy.Compiler.Shared.CSharpKeywords.MutationProbeMap : Dictionary<String, Int32>`), restored (cp + touch) → green (StaticStateConformance 4/4 within the MasterRegistry|StaticState filter). The mutation ran on the P2.3 tree (the next commit); this commit's rule loop is the same. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…n and a user definition never writes a master symbol — a warm CompilerApi answers as a cold process (refs #2250) The measured seam (P1.6b, e3a987e): analysing a text that defines a builtin's NAME inside a `try:` body — a module-level `def len(x: int)` (the Wrap-in-try applied text of builtins_qualified_escapes_shadow) or a class-body `def format(self, value: int)` (classes/overload_by_type) — wrote the master BuiltinRegistry's `len`/`format` FunctionSymbol.Parameters (and ReturnType), and every later document on that CompilerApi read the user's signature: the IFormattable fixtures gained SPY0220 and Extract method parsed at (12,55)/(8,55)/(5,148) instead of the cold (12,59)/(8,59)/(5,149). The #1633 shape. Why `try` mattered: NameResolver declares only the DIRECT members of a module or type body, and TypeChecker.CheckFunction declared a missing symbol only for a def nested in a function. A def inside a block at module or class level (try/if/for/while/with/match) therefore had no symbol of its own, and LookupFunctionSymbol — a bare name lookup — answered with whatever the name already meant: the master registry's builtin, seeded into the global scope by reference. RegisterFunctionParameters (`Parameters[i] = …`) and UpdateFunctionSymbol (`ReturnType = …`) then wrote it. Seam cure (semantic, TypeChecker.Definitions.cs): - CheckFunction declares the def's OWN symbol in the scope it appears in for every def NameResolver did not declare: nested in a function (as before) OR inside a control-flow block (`_controlFlowDepth > 0`). - LookupFunctionSymbol returns only the symbol declared AT this def (IsDeclaredBy: same declaration span — both declaring sites stamp it — and never a BuiltinRegistry symbol); a same-named builtin, import or enclosing definition is no longer a write target. - ResolveModuleFunctionSignature and the generator/async/cache metadata passes, which re-looked the symbol up by bare name (`?? _symbolTable.Lookup(name) as FunctionSymbol`), route through the same seam (LookupMetadataTarget; constructors stay out of those passes, as before). Class made impossible (Design Decision 4, BuiltinRegistry.cs / Symbol.cs): - `_pendingInterfacePopulation` is no longer a field: the deferred interface work list is a local of LoadBuiltins, drained before the constructor returns — no registration state survives construction. - `_interfaceSymbols` is a ConcurrentDictionary filled by GetOrAdd with CreateInterfaceSymbol, a static function of the CLR definition alone (all fills still happen in the constructor today). `_types`/`_functions` were already written only during construction (code-read: every writer is a LoadBuiltins helper) and stay Dictionaries; the key-set snapshot guards their growth. - Every symbol the registry built (types, function overloads, interface symbols and their methods, constructors, fields, overload/operator/protocol tables, nested types, union cases, interface definitions, base and declaring types) is FROZEN at the end of the constructor. Every `internal set` property of Symbol/VariableSymbol/FunctionSymbol/TypeSymbol (22) checks the freeze and throws PhaseViolationException — always on, like SemanticBinding's phase freezes. A `with` copy is born unfrozen (the flag records the object it was frozen AS). `Parameters` is a List, so its element writes go through FunctionSymbol.ReplaceParameter (the three sites: TypeChecker x2, RestoredTypeRelinker) and a source scan pins that no compiler source writes a Parameters element directly. `_clrTypeCache`/TryFindClrType untouched (lane "seam"); its lazily created symbols are not frozen by this commit. - CompilerApi.GetOrBuildAnalysisContext documents the frozen contract. Other writers of the same shape (grep of `.Parameters[`, `.ReturnType =`, `.Type =`, every `internal set` symbol property under src/Sharpy.Compiler): the three metadata passes and ResolveModuleFunctionSignature above were the only lookup-then-write sites that could reach a master symbol; the remaining writers write symbols their own pass created (NameResolver nested types, dataclass synthesis, const folding, module properties — IsModuleProperty, never a master — and Optional/Result union cases built in place). The full Compiler.Tests run with the always-on freeze found no further writer (no PhaseViolationException). Drained (stale reports, then deleted): - WarmInstanceParityTests warm-sequential, deterministic: 5 rows (format_iformattable#cr analyze + extract, format_no_dunder#cr analyze + extract, deferred_overload#cr extract) — each reported "0/1 differ … the deterministic KnownRed row no longer reproduces: stale" before deletion. - WarmInstanceParityTests warm-concurrent, probabilistic: 5 rows — 0/4 on every cell in 3 runs (0/12 per cell, R = 4 per run). - MasterRegistryImmutabilityTests KnownWrittenMasterProperties (`_functions[][]:len.Parameters`, `:format.Parameters`) and KnownRetypedBuiltins (`len`, `format`): deleted, facts green. - StaticStateConformanceTests.SymbolVendingRoster: `_pendingInterfacePopulation` row deleted (field gone), `_interfaceSymbols` row now describes the ConcurrentDictionary; MasterRegistryKeySets roster drops `_pendingInterfacePopulation`. New guards (MasterRegistryImmutabilityTests): - WarmInstanceTriggers_EachBuiltinNamedDefDeclaresItsOwnSymbol — each builtin-named def of the two trigger texts resolves (GetFunctionDeclarationSymbol) to its own unfrozen, non-builtin symbol with its own arity; floor ≥ 2 defs checked. - MasterSymbols_AreFrozen_AndAWriteIsRefused — every registered type/overload frozen (floor ≥ 50); ReturnType / ReplaceParameter / Documentation / BaseType writes throw and leave the master unchanged; a `with` copy is unfrozen and writable. - NoCompilerSourceWritesAParameterElementPastTheFreeze — regex scan with positive/negative pattern controls and the one sanctioned write in Symbol.cs as the in-corpus control. - Snapshot_DetectsAMutationOfAMasterSymbol now poisons through the backing field (the setter refuses). - KeySetSnapshot_DetectsAWriteToAMasterDictionary reads `_interfaceSymbols` as a ConcurrentDictionary. Mutations (cp-backed, restored with cp + touch): - seam reverted (CheckFunction condition back to `_currentFunctionReturnType != null`, LookupFunctionSymbol unfiltered), freeze ON → red (1 failed: WarmInstanceTriggers_EachBuiltinNamedDefDeclaresItsOwnSymbol — the write throws, the def has no declaration symbol); the retype/key-set facts stay green because the freeze refuses the write — the two layers are independent. - seam reverted AND freeze OFF (ThrowIfFrozen never throws) → the original defect, exactly: Compiler.Tests red (4 failed: WarmInstanceTriggers_DoNotRetypeABuiltinForALaterDocument "len: cold (object) -> int32 / warm (int32) -> int32", "format: … warm (Formatter, int32) -> str"; MasterKeySets_AreAFixedPoint… "_functions[][]:len.Parameters / :format.Parameters written"; EachBuiltinNamedDefDeclaresItsOwnSymbol; MasterSymbols_AreFrozen…), Lsp.Tests red (2 failed: WarmSequential 1/1 on the 5 measured cells, WarmConcurrent 4/4 on the same 5 cells). - freeze OFF alone → red (1 failed: MasterSymbols_AreFrozen_AndAWriteIsRefused). - a direct `function.Parameters[i] = …` restored in RestoredTypeRelinker → red (1 failed: NoCompilerSourceWritesAParameterElementPastTheFreeze naming Project/RestoredTypeRelinker.cs:174). - restored → green (see counts below). Direction (no program newly refused; probes run through the built sharpyc, base binary vs this tree): - def len inside a function's try (worked: 700) → worked (700); def len after an `if` block in a function → worked. - module-level `try:` with defs (refused SPY0340) → refused SPY0340 (an inner call's SPY0200 for a try-body def disappears: the def now has a symbol). - class-body `try:` with a builtin-named method `format` (refused, FALSE SPY0220 from the same-compile retype of builtin `format`) → SPY0510 ICE "unrecognized statement type 'TryStatement' in class body" — the pre-existing class-body-try ICE (same ICE at base for a non-builtin name); findings ledger row. - nested redefinition `def f` twice in one function (ICE: SPY0908 CS0128) → refused SPY0220; ledger row. Tests @ this tree (measured): Compiler.Tests Category!=Benchmark 49224/6/3 — the 6 are PerModuleAssemblyTests "Sharpy.Stdlib.Math.dll not found in src/Sharpy.Stdlib/modules/bin" (this worktree had never built sharpy.sln); after `build sharpy.sln` the class + MasterRegistry + StaticStateConformance run 30/0/0, so 49230/0/3 attributed; Compiler.Tests direction + census filter (FileBasedIntegration|MasterRegistry|Shadow|Builtin|StaticStateConformance| ProcessGlobalStateConformance|PropertySeedDiscipline|CiFilterCoverage|EmitterCarrierOnly) 4052/0/0; Lsp.Tests WarmInstanceParity|FrontEndParity|CodeAction|Conformance|Inventory 16642/0/0; Lsp.Tests WarmInstanceParity 4/0/0; Stdlib.Tests Conformance 456/0/0. The two Lsp runs predate the LookupMetadataTarget constructor exclusion (Compiler-side only, reached by no LSP path the filters exercise beyond what the full Compiler.Tests run covers). Correction to db9c57a40's body: the restored StaticStateConformanceTests count is 9/9 (theory rows included), not 4/4. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…der one module lock, as CPython's are under the GIL — regenerated (refs #2250) Rung 1 (Sharpy source): random_module.spy and socket_module.spy each gain one module-level `_lock: Lock = Lock()` and `with _lock:` around every read and write of the module global — random: seed() (swaps the instance) and every draw (next_double, randint, uniform, choice, shuffle, both randrange, gauss, getrandbits, choices, sample); socket: getdefaulttimeout, setdefaulttimeout, socket.__init__'s default read, and create_connection(address)'s default read. Random/Random.cs and Socket/SocketModule.cs are regenerated in this commit. Import spelling: `from threading import Lock`, NOT the plan's `from sharpy import Lock`. Read of the first regen diff: under `from sharpy import Lock`, `with _lock:` lowers to `using (Lock) { … }` — the IDisposable lowering, never Enter(), and Lock.Dispose disposes its SemaphoreSlim at block exit, so the lock would be inert (the plan's "inert fix (3)" by a different route). The same probe program under `from threading import Lock` lowers to `__ctx.Enter(); try … finally __ctx.Exit()` and prints True/False/True like python3. Findings-ledger rows (compiler with-lowering keyed on the import spelling; `sharpyc run` of a `from sharpy import Lock` program cannot load Sharpy.Stdlib) — outside this lane. Binding: the plain module binding, not `const _lock` (which emits `static readonly` but makes build_tools' doc generator render `_lock` as a public constant in docs/stdlib/{random,socket}.md — ledger row). Generated diff read: exactly one `public static global::Sharpy.Lock _Lock = new global::Sharpy.Lock();` per module, assigned nowhere else (grep `_Lock = ` = 1 per file), every `with` lowered to `var __ctx_N = …_Lock; __ctx_N.Enter(); try { … } finally { __ctx_N.Exit(); }` (13 in random, 4 in socket), locals hoisted where a draw fed a declaration (`int j = 0; with: j = …`) — no other change. Roster census still finds the 8 written globals; the lock is init-once and not flagged. Docs regen: no change. Drained KnownRed rows (StdlibModuleGlobalConcurrencyTests): - random/read/8 (red 4/6 @ 486c9a8) → 0/4 runs after the cure (plus 0/5 with the intermediate const-binding regen), rows deleted so the cells assert k == 0; - random/read/2 (0/6 at base) → deleted with the cure; - socket/write/2, socket/write/8, socket/read/2, socket/read/8 were never reproduced (0/6 at base) — deleted because the cure lands: the 16-byte double? write now happens under the lock (mechanism, not reproduction). Still 0/4 after. Roster rows' Synchronization text now names the module lock. mutation: delete every `__ctx_N.Enter();`/`__ctx_N.Exit();` from the generated Random.cs + SocketModule.cs (cp-backed) → red 3/3 runs (1 failed each: random/read/8, 15534/16000, 13878/16000, 15242/16000 draws not the seed-7 stream); socket cells 0/3 under the mutation (mechanism-only, as at base); restored (cp + touch, cmp-verified) → green (83 passed). Direction: single-threaded seeded sequences unchanged — Stdlib.Tests FullyQualifiedName~Random 78/0/0 (PrngState collection), ~Socket 73/0/0. Pre-commit @ this tree: Stdlib.Tests ~Conformance 456/0/0, ~StdlibModuleGlobal 83/0/0 (×3), ~Random 78/0/0, ~Socket 73/0/0, ~Xml 93/0/0, ~Numpy 221/0/0; check_spy_staleness.sh OK (18/18), check_spy_tests_staleness.sh OK (119 files). Compiler.Tests census classes (run separately): StaticStateConformance 2/0/0, ProcessGlobalStateConformance 34/0/0, PropertySeedDiscipline 1/0/0, CiFilterCoverage 3/0/0, EmitterCarrierOnly 6/0/0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…s-wide, locked generator/registry — a seed on one thread is seen by every thread, as numpy's global RandomState is (refs #2250) Rung 2 (hand-written Stdlib C#, C# 9 on netstandard2.1: a private/internal readonly object as the monitor, never a System.Threading.Lock). xml: Xml/XmlFunctions.cs — RegisterNamespace writes _registeredNamespaces under `lock (RegisteredNamespacesLock)` (internal static readonly object). Nothing in production reads the registry (tostring prefix support is a ledger row outside P54 and is NOT added here); the concurrency theory's reader (Has) and its Restore (Clear) now take the same lock — every read and write of the registry holds it. numpy.random: Numpy/NumpyRandom.cs — [ThreadStatic] dropped; ONE process-wide System.Random (`_rng`, initialised once) behind `RngLock`; Seed swaps it under the lock and every draw (rand, randn, randint, normal, uniform, choice, shuffle) holds the lock for its whole draw, as RandomState's methods hold its lock. Direction record (contract §3): NumpyRandom.cs:10-15 DOCUMENTED the per-thread generator as a design choice ("matches the safer .NET model"). Reversed, with the measurement as the reason, re-verified 2026-10-15 with python3 3.12 + numpy 2.5.1: np.random.seed(7); main = np.random.rand(); np.random.seed(7); a worker thread's np.random.rand() → 0.07630828937395717 0.07630828937395717 True i.e. the global RandomState is process-wide. The remarks now say so; Seed's summary "thread-local" → "process-wide" (docs/stdlib/numpy.md regenerated by `python3 -m build_tools stdlib generate --force`, the one changed line). Pin grep (src/Sharpy.Stdlib.Tests, docs/): no test asserts per-thread seeding; the only pins were the P54 concurrency theory's own header/KnownRed comments and the roster row's Synchronization text, updated here. No spy test depends on it: only numpy_random_tests.spy draws from numpy.random, in the NumpyRandomState collection. Drained KnownRed rows (StdlibModuleGlobalConcurrencyTests — the set is now empty): - numpy.random/{write,read}/{1,2,8}, numpy.random/cross-thread/{2,8} (deterministic, 6/6 @ 486c9a8): with the rows still present against the cured production all 8 report "is a deterministic KnownRed row but no longer reproduces (0/N) — stale: drain the row" (8 failed, 75 passed); deleted. - xml/write/{2,8}, xml/read/{2,8} (probabilistic, red 3/6, 6/6, 1/6, 5/6 @ base): 0/5 runs after the cure (rows deleted, cells assert k == 0). Roster: Synchronization text for xml and numpy.random names the lock; the census still finds exactly 8 written globals (the two lock objects are readonly and never mutated — not flagged). mutation (cp-backed, one build): RegisterNamespace's lock removed AND the [ThreadStatic] generator restored (a [ThreadStatic] backing field behind an `_rng` property with the old `??= new Random()` getter) → red 3/3 runs (13, 13, 12 failed): numpy.random 8/8 cells red in every run (deterministic); xml/write/8 3/3 (4292/16002, 4213/16002, 2766/16001 — InvalidOperationException), xml/write/2 3/3 (16, 28, 8 of 4000 prefixes lost), xml/read/8 2/3 (306/16000, 370/16000); plus the roster census 2 red (the mutant's new written static `_threadRng` is unrostered and `_rng` is no longer a field — the census biting on the shape). Restored (cp + touch, cmp-verified) → green (83 passed; Conformance 456/0/0). Pre-commit @ this tree: Stdlib.Tests ~Conformance 456/0/0, ~StdlibModuleGlobal 83/0/0 (×4 with the cure, plus once inside ~Conformance), ~Random 78/0/0, ~Socket 73/0/0, ~Xml 93/0/0, ~Numpy 221/0/0; check_spy_staleness.sh OK (18/18), check_spy_tests_staleness.sh OK (119 files); format whitespace: no change. Compiler.Tests census classes (run separately): StaticStateConformance 2/0/0, ProcessGlobalStateConformance 34/0/0, PropertySeedDiscipline 1/0/0, CiFilterCoverage 3/0/0, EmitterCarrierOnly 6/0/0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…s against {framework} ∪ {this registry's references}, the framework set is a process-constant index, the name-keyed type caches move off the process (refs #2250)
P54 Phase 2 task 1. New Discovery/AssemblyVisibility.cs answers every "which CLR types / namespaces
exist" question for a compilation: ResolveType(full or assembly-qualified name), TypesInNamespace,
NamespaceExists, GenericDefinitionsNamed, ResolveGenericDefinitionByDisplayName. ModuleRegistry owns
one (ModuleRegistry.Visibility), shared with its CachedModuleDiscovery (LoadAssembly adds the
assembly before resolving its names); the TypeChecker, TypeInferenceService, SemanticContext,
ProtocolMembership, ClrAttributeResolver, the symbol-cache restore (SymbolSerializer decoders,
IncrementalCompilationCache.RestoreSymbols) and ProjectCompiler's CLR-origin relink read it. A
compile with no registry gets AssemblyVisibility.FrameworkOnly.
Design Decision 1 choice: OPTION A (lead, recorded). The framework set is a process Lazy: every
trusted-platform System / System.* / Microsoft.* / netstandard / mscorlib assembly, Sharpy.Core, the
compiler and its direct dependencies, loaded once by name (Assembly.Load(AssemblyName), the TPA
binding) and indexed by namespace and (simple name, arity). EnsureRuntimeAssembliesLoaded and the
ClrAttributeResolver second-chance loader are subsumed; the TPA gate is gone. First-use cost
(measured, Compiler.Tests host, class run alone @ this commit's tree): 205 assemblies, 6266 visible
types, 61 ms.
Sites routed (ProcessWideResolutionScanTests allowlist, 25 rows @ 486c9a8): ModuleRegistry
TryResolveNetType / GetNamespaceTypes / MapModuleToNamespace (dynamic branch; _dynamicNamespaceCache
deleted, the index lookup is the cache) / EnsureRuntimeAssembliesLoaded (deleted); CachedModuleDiscovery
CreateSkeletonTypeSymbol + ConvertTypeSignature; ClrTypeHelper ResolveClrTypeByStoredName and
ResolveGenericDefinitionByDisplayName (deleted — the two static name-keyed caches are per-visibility
state now; ResolveOriginDefinition / TryConstructClosedGeneric / HasParamsIndexer take the
visibility); ClrAttributeResolver FindType + EnsureFrameworkAssembliesLoaded (deleted with
s_typePresentCache and _probedNamespaces); BuiltinRegistry.TryFindClrType; TypeChecker
ResolveClrParameterType and TryGetClrType; RoslynEmitter.IsClrTypeGenericInNamespace ->
AssemblyVisibility.FrameworkOnly (Rule-2 cell: the emitter asking reflection a semantic question;
its only caller passes a .NET framework namespace; ledger row, emitter otherwise unchanged).
BuiltinRegistry deviation (lead ruling 2026-10-15): the bare-name CLR fallback reads the PAIRED
module registry's visibility, passed at construction (optional ctor parameter, default
FrameworkOnly; CompilerApi.GetOrBuildAnalysisContext now builds the registry first,
ProjectCompiler.InitializeSharedState passes _moduleRegistry?.Visibility). FrameworkOnly, as planned,
refused working programs: InteropSweep_AllPublicStdlibMembers went red with SPY0202 on bare
StatisticsError, CapturedOutput, CapturedStderr, TmpPathFixture (property/method/match/annotate
cells). Those public Sharpy-namespace Stdlib classes declare no [SharpyModuleType], so this
fallback is their only route into user code — #2053 (public Sharpy-namespace types leaking as
builtins) and #2064 (bare-name Stdlib resolution); not widened into either. The master is keyed by
reference set together with its ModuleRegistry, so _clrTypeCache stays key-pure.
Design Decision 3: every static Type-/Assembly-keyed cache (ClrTypeHelper x8, ClrTypeBridge
_mapsToSharpyBuiltinCache, ClrExtensionMethodResolver._ancestorCache, BuiltinExceptionSurface x2)
writes only when AssemblyVisibility.IsProcessStable holds — the type and every constituent (element,
generic arguments, declaring type) in the default, non-collectible context; otherwise answered
uncached. Inert until task 2 adds per-registry contexts.
TRANSITIONAL (drains in task 2): LoadReference still uses Assembly.LoadFrom; when a same-named
assembly from another path is already in the default context (the SPY0306 arm), the colliding
default-context copy is made visible (ModuleRegistry.BindAlreadyLoadedCopy, an allowlist row), so
this commit changes no program's answer — RegistryIsolationTests' B/Beta and B/Alpha KnownRed rows
still reproduce byte-identically and drain with task 2.
Guards:
- ProcessWideResolutionScanTests re-anchored: AppDomain scans 14 -> 0, static Type.GetType 7 -> 1,
Allowlist 25 -> 3 (AssemblyIdentity.FromAssemblyPath LoadFrom, LoadReference LoadFrom, the
transitional BindAlreadyLoadedCopy Assembly.Load — all task 2), Roster 6 -> 9 (the seam's three:
TypeNameResolution Type.GetType with both resolvers supplied, LoadFrameworkAssembly Assembly.Load,
IsProcessStable AssemblyLoadContext.Default), total 31 -> 12 (measured).
- StaticStateConformanceTests: the five name-keyed rows drained (_originNameDefinitionCache,
_displayNameDefinitionCache, s_typePresentCache, _probedNamespaces, _loadedRuntimeNamespaces — fields
gone), AssemblyVisibility.s_framework rostered key-pure; anchor 22 -> 18 (measured).
- TpaNamespaceGateTests rewritten against the new seam (the gate and the ClrTypeHelper resolvers no
longer exist). Mechanism cells: 8 fresh registries released together ask for microsoft.win32;
each answer must equal a METADATA census of the framework TPA files (System.Reflection.Metadata,
nothing loaded); reference cells: a type in compile A's own load context is null for 8 compiles
without the reference, and a visibility that cached a null resolves the type once it gains the
reference. KnownRed drained: with the four Phase 1 rows re-inserted, the run reported all four
STALE (tpa-gate.passes-before-completion 0/8 incomplete, tpa-gate.concurrent-first-use
[8x8] vs solo 8, origin-cache.reference-seen-by-unreferencing-compile 0/8,
origin-cache.unresolved-name-pinned-null 0/9) -> rows deleted.
- AttributeAbsenceVerdictScopeTests: the assembly arrives as compilation 2's reference (its
visibility), and a compilation 3 without it must NOT see it (new isolation assertion; before this
commit a process load was visible to every compilation). LoadedAssemblyNameIsolationTests (#1840)
retargeted to AssemblyVisibility.SafeSimpleNames (LoadedSimpleNames moved there).
- MasterRegistryKeySets: the three process-field rows and ModuleRegistry._dynamicNamespaceCache are
gone with their fields.
Mutations (each cp-backed, restored with cp + touch):
- AssemblyVisibility.FindTypeInVisibleSet also scans AppDomain.CurrentDomain.GetAssemblies() ->
red (3 failed: ProcessWideResolutionScan [unlisted] site, TpaNamespaceGate
origin-cache.reference-seen 8/8, AttributeAbsence compilation-3 isolation); restored -> green.
- ModuleRegistry.GetNamespaceTypes back to a static TryAdd gate + TPA LoadFrom + AppDomain scan
(class method alone) -> red (1 failed: 7/8 concurrent first-use answers [0,0,0,0,0,0,8,0] vs
census 8); restored -> green.
- StaticState roster row for s_framework renamed -> red (1 failed); restored -> green
(StaticState + gate method: 10 passed).
Measured green on this tree: guard set (TpaNamespaceGate, ProcessWideResolutionScan,
StaticStateConformance, AttributeAbsence, LoadedAssemblyName, BracketAttribute, ProtocolMembership,
RegistryIsolation, ModuleNamespaceShadows, MasterRegistry, EmitterCarrierOnly,
ProcessGlobalStateConformance, PropertySeedDiscipline, CiFilterCoverage) 134/0/0; Compiler.Tests
Interop|Clr|ModuleRegistry|Import|FileBasedIntegration|InProcessIsolation 4775/2/0 — the 2 reds:
InteropSweep (fixed by the BuiltinRegistry deviation above, re-run InteropConformance + guards
74/0/0) and PerModuleAssemblyTests.MultipleModuleImports (environmental: per-module stdlib not built
by a project-scoped build, #1479 message). Direction: no program newly refused on these filters.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…ibility seam — drained (refs #2250) Follow-up to ff6b31010 (P54 Phase 2 task 1), measured on its tree: CompileServerE2ETests. RequestSequence_NoRef_Ref_NoRef reported row "3: run main.spy" stale — recorded "exit 2 | SPY0908(CS0400)", observed "exit 1 | SPY0300" (Cannot find module Tablegreeter), equal to the fresh-process oracle. Request 2's reference no longer reaches request 3: the dynamic namespace mapping and the type lookups answer from request 3's own AssemblyVisibility. Direction: ICE -> the refusal a fresh process gives (contract section 3), a fix. Row deleted; the test passes (1/0/0). The plan expected this row to drain with task 2 (the per-registry load context); it drained one task early because the visibility seam alone removes the process-wide lookup it depended on. Blast radius measured @ ff6b31010 (rebased onto dev dae06b7): Compiler.Tests Interop|Clr|ModuleRegistry|Import|FileBasedIntegration|InProcessIsolation + the guard/census set + Frozen|BuiltinRegistry|WarmCold 4911/1/0 (the 1: PerModuleAssemblyTests.MultipleModuleImports — environmental, per-module stdlib not built by a project-scoped build, #1479 message); Lsp.Tests FrontEndParity|WarmInstanceParity 5/0/0; Stdlib.Tests Conformance 456/0/0; Cli.Tests whole project 886/1/1 (the 1 = this row, stale). No program newly refused. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…ext keyed by its reference set — a same-named assembly from another path is its own copy, SPY0306 means only "cannot be loaded as given", and every registry error reaches every door (R-HI, refs #2250, refs #2182) P54 Phase 2 task 2. ModuleRegistry.LoadReference no longer calls Assembly.LoadFrom: a non-framework reference loads with LoadFromAssemblyPath into a collectible Discovery/ReferenceLoadContext obtained from ReferenceLoadContext.GetOrLoad. A framework assembly (Sharpy.Core is a default reference) is the framework copy — one identity; a simple name the registry already loaded keeps its first copy (first wins, as before, no new diagnostic). Keyed by the reference set (lead ruling 2026-10-15, replacing the plan's "one context per ModuleRegistry"): the key is the reference file's (full path, length, last-write ticks), the identities of the references the registry loaded before it in order, and the registry's module paths — AssemblyCompiler.s_referenceCache's convention. The context's contents are a function of the key, so registries with identical inputs share it (observably one context per compilation) and any different input gets its own. The share is a process-wide ConcurrentDictionary<key, WeakReference<ReferenceLoadContext>> (rule 2b row, key-pure); registries hold their contexts strongly, so a context unloads once no registry uses it; a lookup that finds a dead entry replaces it and every creation prunes dead entries. The key is applied one reference at a time, so no load is deferred and no refusal moves. Load(AssemblyName) answers from the earlier references / the context's own loads, else null (default context: one framework / Sharpy.Core identity); Resolving searches the reference's directory and the key's module paths only. Why not one context per registry (measured): every analysis loads its own Sharpy.Stdlib copy; loader memory is native, invisible to GC pressure, so Lsp.Tests FrontEndParity_AllFixtures ran OutOfMemoryException at 13m51s. With the keyed share: FrontEndParity_AllFixtures 1m13s (1m14s @ ff6b31010, before this commit); Lsp FrontEndParity|WarmInstanceParity 5/0/0. Peak RSS: not read — /usr/bin/time -l around the wrapper reported 207 MB, which is the dotnet CLI process, not the testhost. SPY0306 one meaning (R-HI): MissingReferenceDiagnostics() (the SPY0305-only filter and its <remarks> naming the leak) is now ReferenceDiagnostics(), every registry error; callers updated (CompilerApi CompileAsSyntheticProject / AnalyzeCore / AnalyzeProject, Compiler.CompileProject, ProjectConfig doc, RegistryErrorDoorTests doc). The IOException / BadImageFormatException / UnauthorizedAccessException catches stay. AssemblyIdentity.FromAssemblyPath DELETED (zero callers — grep src/: only its own definition and the scan guard's allowlist row). The transitional ModuleRegistry.BindAlreadyLoadedCopy from task 1 is deleted. ClrAttributeResolver's LoadFrom site (:473) was already subsumed by task 1's framework index. Direction (contract section 3) — refusals that moved, each verified against the prior commit: - RegistryErrorDoorTests.Door_ReportsAnUnloadableReferenceAsSpy0306 (new, door x {not-an-assembly, control}): CompileProject / CompileProject(token) went from SPY0908(CS0009 "PE image doesn't contain managed metadata") — an ICE — to SPY0306 refused before the pipeline writes anything; AnalyzeProject went from success with no diagnostic to SPY0306 (a missing refusal). Controls (a real assembly) pass. - Condition (2) of the ruling — no refusal moved by the keyed loading: every RegistryErrorDoorTests cell green at this tree, 18/18: ProjectConfigDoors pin; Door_ReportsAMissingReferenceAsSpy0305 x 3 doors x {missing, control}; AnalyzeProject cached re-key; SingleFileAnalyze SPY0305 x {path, no path} x {missing, control}; Door_ReportsAnUnloadableReferenceAsSpy0306 x 3 doors x {not-an-assembly, control}. An UNREADABLE (permission-denied) reference cell does not exist in the suite; not added. - No program that worked is refused: Compiler.Tests blast filter (below), Cli.Tests whole project, FileBasedIntegration all green. Drained / new guards: - RegistryIsolationTests KnownRed rows reported stale under the fix and were deleted: B/Beta (was "refused | SPY0200,SPY0301", now compiles against B's own file) and B/Alpha (was "refused | SPY0908(CS0234)", now refused by a diagnostic). The server row "3: run main.spy" already drained with task 1 (25287a3f8). - ModuleNamespaceShadowsClrTypeTests asserts no SPY0306 in every run/project cell (each cell builds its own AppTypes.dll; before, the second and later loads recorded a filtered SPY0306 and compiled against the first copy). - RegistryIsolationTests.ReferencedLibrary_SharesTheCompilersSharpyCore (new): a referenced library loads into a collectible non-default context, yet its Sharpy.List<int> return type's definition IS typeof(Sharpy.List<>), and a Sharpy.Core reference is the framework copy. - RegistryIsolationTests.CompileServer_FiftyRequests_LoadContextsCollect (new, in-process twin of the server: one CompilerApi per request) over 50 DISTINCT reference paths: measured 0 static-cache keys from the reference's assembly; 0 of 50 contexts alive after GC (ceiling 2); share entries 2 after the 50 requests + GC and 1 after one more request (ceiling 2). The class runs alone (new run-alone collection LoadContextRetention, rostered in ProcessGlobalStateConformanceTests 7 -> 8 collections, 52 -> 53 members) because it forces GCs (#2254). - ProcessWideResolutionScanTests: Allowlist EMPTY (3 -> 0); Roster 9 -> 10 (ReferenceLoadContext.GetOrLoad's construction); total 12 -> 10; AppDomain scans 0, static Type.GetType 1 (the seam's). StaticStateConformanceTests: ReferenceLoadContext.s_shared rostered, anchor 18 -> 19 (measured). Mutations (cp-backed, restored with cp + touch): - LoadReference back to Assembly.LoadFrom -> red (27 failed: RegistryIsolation TwoRegistries, SharesTheCompilersSharpyCore, FiftyRequests; ModuleNamespaceShadows 24 cells — "Assembly with same name is already loaded" SPY0306 now refuses); restored -> green. - ReferenceDiagnostics back to the SPY0305-only filter -> red (3 failed: the three unloadable door cells); restored -> green. - AssemblyVisibility.IsProcessStable(Assembly) forced true (Design Decision 3 gate off) -> red (FiftyRequests: 550 static-cache keys from the request references, BuiltinExceptionSurface._typeCache first); restored -> green (0 keys). Note: the AssemblyLoadContext.All count alone stayed 0/50 under this mutation — a pinned LoaderAllocator outlives its collected ALC object — so the static-cache-key assertion is the falsifiable half of the probe; the All count is recorded, not relied on. - AssemblyVisibility TypesInNamespace / NamespaceExists / FindTypeInVisibleSet also consult AppDomain.CurrentDomain.GetAssemblies() (the plan's "revert the visibility filter to the raw scan", measured on the per-registry-context WIP of this task) -> red: RegistryIsolation A/Beta and B/Alpha "refused | SPY0908(CS0234)", TpaNamespaceGate origin-cache.reference-seen, and the Cli server row request 3 "exit 2 | SPY0908(CS0400)"; restored -> green. Restored run: RegistryErrorDoor, RegistryIsolation, ModuleNamespaceShadows, ProcessGlobalStateConformance, StaticStateConformance, ProcessWideResolutionScan, TpaNamespaceGate 106/0/0. Blast radius @ this tree (measured): Compiler.Tests Interop|Clr|ModuleRegistry|Import|FileBasedIntegration|InProcessIsolation + guard/census set (EmitterCarrierOnly, ProcessGlobalStateConformance, PropertySeedDiscipline, CiFilterCoverage, ...) + Frozen|BuiltinRegistry|WarmCold|Reference|Discovery 5483/1/0 (the 1: PerModuleAssemblyTests. MultipleModuleImports, environmental — per-module stdlib not built by a project-scoped build, #1479 message; red the same way @ ff6b31010); Stdlib.Tests Conformance 456/0/0; Cli.Tests whole 887/0/1; Lsp.Tests FrontEndParity|WarmInstanceParity 5/0/0. Ledger (not fixed, per ruling): a reference rewritten in place with the same length inside the mtime granularity shares the stale context in a long-lived process — #2114's class. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…e threading module their module lock comes from (refs #2250) Follow-up to ff61019 (P54 Phase 3 task 1). random_module.spy and socket_module.spy now import `from threading import Lock` for their module lock, so the generated Random/Random.cs and Socket/SocketModule.cs name global::Sharpy.Lock. The per-module packaging projects (src/Sharpy.Stdlib/modules/Sharpy.Stdlib.{Random,Socket}.csproj) compile only their own module's sources, so `build sharpy.sln` failed with CS0234 "'Lock' does not exist in the namespace 'Sharpy'" (4 errors: both modules × net10.0 / netstandard2.1) @ dae06b7. The lane's worktree never built the whole solution. Each module project now carries a ProjectReference to Sharpy.Stdlib.Threading, the precedent being Pathlib → Os and Toml/Zoneinfo → Datetime. Measured on this tree: `build sharpy.sln` 0 errors; Compiler.Tests PerModuleAssembly 10/0/0; Stdlib.Tests NuGetModuleCoverage|StdlibModuleGlobal 84/0/0. Phase 2 task 7 regen @ 32ea542 (after the visibility seam and the reference-set load context): regenerate_spy_stdlib.sh, regenerate_spy_tests.sh and `build_tools stdlib generate --force` produce no diff; check_spy_staleness and check_spy_tests_staleness report up to date. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…byte-identical in regular and FULL mode, regular mode 1m29s → 46s (refs #2250, refs #2232) P54 Phase 2 task 5. The P47 sweep gave every (stem, twin) a FRESH CompilerApi because a warm instance answered differently from a cold one (measured @ 43aab26). P54 measured that seam as a user `def len` / `def format` inside a `try:` rewriting the master builtin's parameters, and cured it at cbabbf7: every def declares its own symbol, and the master registry's symbols are frozen after construction. Isolated() now hands each (stem, twin) a fresh driver over ONE shared CompilerApi, as the language server shares one for the life of its process. Direction control (the plan's acceptance: pins unchanged @ sha): - regular mode: 48/0/0, 1m29s (isolated, this tree's parent) → 46s (shared) - FULL mode (SHARPY_CODEACTION_SWEEP_FULL=1): 48/0/0 in 8m08s with the shared instance; every pin's (count, digest) pair in code-action-shape-allowlist.txt matches — the allowlist file is unchanged (git status clean apart from this test file). Not a guard for the warm-instance defect on its own: @ this sha the live sweep no longer produces the two trigger texts (Wrap in try is not offered over a declaration since e3c5cbd), so reverting cbabbf7 would not redden it. WarmInstanceParityTests replays the trigger texts and is that guard (mutation recorded in cbabbf7). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…, reference-set load context, frozen master registry, stdlib global roster, crash exclusivity (refs #2250) P54 Phase 4 task 1. gap-discovery-contracts.md gains the "In-process isolation" class row (contract, standing harnesses, umbrella #2250). docs/architecture.md › Semantic Analysis Pipeline › the Key registries paragraph names the one resolution seam (AssemblyVisibility: a process-constant framework index ∪ the registry's own loads), the reference-set-keyed ReferenceLoadContext, and the frozen master BuiltinRegistry. Every named type and behaviour was read at this tree: AssemblyVisibility.cs (FrameworkOnly), ReferenceLoadContext.cs (collectible, keyed by the reference set), Symbol.cs (PhaseViolationException on a frozen master symbol); the only remaining GetAssemblies() call in the compiler is in AssemblyVisibility.cs. CLAUDE.md is unchanged (no new rule). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…in noise, no fallback needed (refs #2250) P54 Phase 2 task 6 (Design Decision 3: measure warm-compile cost before and after the visibility seam, the reference-set load context and the frozen master registry; a regression beyond noise moves the Type-keyed caches to per-registry instance caches). No code change. Measured back to back on one machine, two rounds each, Benchmark-traited suites: base = 486c9a8 (a detached worktree), head = 62558b7 (the code-bearing commits of P54 through Phase 2 task 5). CachedDiscoveryPerformanceTests (Compiler.Tests), ms, round 1 / round 2: first load (cache build) base 129 / 100 head 92 / 84 first load base 119 / 101 head 95 / 91 cached loads (median) base 0 / 0 head 0 / 0 compile without modules base 219 / 238 head 212 / 207 compile with modules base 9 / 13 head 9 / 12 LspAnalysisLatencyBaselineHarness (Lsp.Tests), median ms of 15 runs, r1 / r2: project full reanalysis (6 files) base 5.3 / 6.3 head 4.9 / 4.9 single-file stage attribution (default refs) base 9.7 / 14.2 head 9.6 / 9.8 single-file parse-truncated (default refs) base 9.8 / 14.6 head 10.5 / 10.8 single-file full analysis (default refs) base 9.0 / 9.2 head 9.1 / 9.9 single-file full analysis (no refs) base 8.7 / 9.0 head 9.1 / 9.1 single-file parse-truncated (no refs) base 9.8 / 9.9 head 9.5 / 10.2 project no-change edit skip base 0.0 / 0.0 head 0.0 / 0.0 Verdict: within the base runs' own spread on every row; the Design Decision 3 fallback (per-registry instance caches) is not taken. The option-A framework index's first use was measured by the seam lane at 61 ms (205 assemblies, 6266 types; Compiler.Tests host, 333de07). Lsp FrontEndParity_AllFixtures: 1m14s before the reference-set load context, 1m13s after (32ea542). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…ork plus Sharpy.Core, never the host's own application assemblies — sharpyc and the LSP agree (refs #2250) P54 final-verify F3. AssemblyVisibility's process-constant framework set was every trusted-platform assembly NAMED System/System.*/Microsoft.*, plus the compiler and its direct dependencies. A host's TPA list also carries its deps.json packages (sharpyc: System.CommandLine, Microsoft.CodeAnalysis*, Microsoft.Extensions.*, System.Reactive, System.Composition.*; the LSP lacks System.CommandLine; test hosts add Microsoft.TestPlatform.*), so the framework a compile saw depended on WHICH host ran it, and an import of a host package compiled and then died at run. Rung: (3), a bridge rule keyed on CLR identity — the shared-framework directory, not a name prefix. The framework set is now CoreLib + Sharpy.Core + every TPA name that has a .dll in the directory CoreLib was loaded from (Microsoft.NETCore.App's runtime directory), loaded by name as before. A host with no file-backed CoreLib (browser-wasm Playground, single-file) falls back to the compiler's direct System/System.*/netstandard/mscorlib dependencies (never Microsoft.*; unverified by execution — no wasm host here). Nothing in the compiler resolves Roslyn/compiler/host types for user programs (grep: no ResolveType of a Microsoft.CodeAnalysis or Sharpy.Compiler name). A user reference whose simple name is a host package (-r System.CommandLine.dll) is no longer framework, so ModuleRegistry.LoadReference loads the user's file into its reference context instead of returning the host's copy (F3 (d)); no code change was needed there beyond the framework set. Direction (built sharpyc, base 486c9a8 -> effba77 -> this commit): - from microsoft.extensions.primitives import StringValues: SPY0300 -> compiled + FileNotFoundException -> SPY0300 - from microsoft.code_analysis.c_sharp import CSharpSyntaxTree: compiled + exit 134 FileNotFound -> same -> SPY0300 - from system.command_line import RootCommand: compiled + exit 134 FileNotFound -> same -> SPY0301 (the system.* arm maps any name without an existence check; from system.no_such_namespace import Foo is SPY0301 at base, measured — same refusal as a namespace that exists nowhere) - from system.reactive.linq import Observable (used): exit 134 FileNotFound at base -> SPY0301 - from microsoft.data.sqlite import SqliteConnection: SPY0300 at base -> SPY0300 - the verifier's 12-program direction corpus (scratchpad p/dir): base vs this commit IDENTICAL (same runner both sides, 6 run / 6 refused) - -r <copy of System.CommandLine.dll> + from system.command_line import RootCommand: runs, prints hello F4 (BuildModuleRegistry adds module paths after references) NOT changed: reordering makes the ReferenceLoadContext Resolving module-path search live on sharpyc/LSP, turning a reference dependency found only under -m from unresolved into resolved — observable, unpinned by any test, and a change vs base (LoadFrom same-directory probing). Recorded in the findings ledger. Tests: - Compiler.Tests Conformance/FrameworkVisibilityTests: the framework set is a subset of the shared-framework directory plus Sharpy.Core (positive rows System.Private.CoreLib, System.Runtime, System.Collections, System.Text.RegularExpressions, Microsoft.Win32.Primitives, netstandard, Sharpy.Core; negative rows Microsoft.CodeAnalysis(.CSharp), Microsoft.Extensions.Primitives, Microsoft.Extensions.FileSystemGlobbing, System.Reactive, System.Composition.Hosting, Microsoft.TestPlatform.CoreUtilities, xunit.core — each asserted present in this host's TPA so it discriminates — plus System.CommandLine and Sharpy.Compiler); host namespaces invisible with no reference; host-package imports refused at the import line (7 cells incl. two nowhere-namespace controls and a system.text positive control); a user reference named like a host assembly binds the user's file in a non-default context, Sharpy.Core keeps the framework copy (positive control). - Cli.Tests E2E/HostAssemblyVisibilityE2ETests: sharpyc run twins (StringValues -> exit 1 SPY0300, RootCommand -> exit 1 SPY0301, no FileNotFoundException) and the -r positive control. - TpaNamespaceGateTests' metadata census keys on the runtime directory (independent of the production predicate). mutation: framework TPA filter restored to the System.*/Microsoft.* name prefix -> red (Compiler.Tests FrameworkVisibility 5 failed of 9: subset, user-reference, extensions_primitives, code_analysis, reactive; Cli HostAssemblyVisibility 2 failed of 3: extensions_primitives, command_line), restored -> green (Compiler.Tests facts + census classes 78 passed; Cli 6 passed). Measured on this tree: Compiler.Tests Interop|Clr|ModuleRegistry|Import|Visibility|RegistryIsolation| ProcessWideResolutionScan|FileBasedIntegration + census 4874/3/0 — 2 = this class not rostered in LoadContextRetention (collection attribute dropped, not needed; re-run green), 1 = PerModuleAssemblyTests environmental (#1479, per-module stdlib not built by a project-scoped build); Cli.Tests whole 890/0/1; Lsp.Tests FrontEndParity|Conformance|Inventory 16624/0/0; Stdlib.Tests whole 4273/0/0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…ozen master symbol refuses writes through its collections too — a class/struct/union/interface/delegate in a block never writes a builtin or an enclosing type (refs #2250) P54 final verification F1 (HIGH) / F2 (MEDIUM) @ effba77. NameResolver declares the type definitions of a module body and of a type body only. A class/struct/interface/union/delegate in a function body or a block (if/for/while/try/with/match) had no symbol of its own, and CheckClass/CheckStruct/CheckInterface/CheckUnion/ CheckDelegate looked the NAME up and wrote whatever it found: the MASTER builtin `str` (a mixed auto/setter property added a `_size` backing field through `typeSymbol.Fields.Add` — a List the property freeze of cbabbf7 did not reach — so a later document's `s._size` lost its SPY0203 on the same CompilerApi/LSP), or an enclosing user type (a function-local `class Foo` gave the module's Foo a `_size` field within one compile: `Foo()._size` reported SPY0283 instead of SPY0203). `@dataclass class str` in a module-level try set IsDataclass on the master: SPY0340 at 486c9a8, a SPY0909 PhaseViolationException ICE + crash bundle at effba77 (F2, a regression of cbabbf7). Rung: semantic (the checker's declaration seam) — a definition's symbol is a fact of the definition, not of its name; no lower rung carries it. Seam (one rule, Symbol.IsDeclaredAt: same declaration span, never a BuiltinRegistry symbol): - TypeChecker.LookupTypeDefinitionSymbol, routed through by all five type kinds: CheckClass (+ the dataclass pass ProcessDataclassDecorator, field/property/event/method-signature passes, which take its symbol), CheckStruct, CheckInterface, CheckUnion, CheckDelegate. CheckEnum looks nothing up. The def seam (IsDeclaredBy, cbabbf7) now shares the same predicate. - A type definition with no symbol of its own is REFUSED with the SPY0202 "<Kind> symbol for 'X' not found" a function-local class with an unbound name already got at base — C# has no local types and the spec places nested types only in type bodies, so the definition is not declared (declaring it would turn the base SPY0202 of `def f(): class Foo:` into the SPY0510 emitter ICE). A duplicate definition at a declaring position keeps NameResolver's refusal and gets no second diagnostic. - ConstEligibility (the const-fact metadata pass, which descends into function bodies and blocks) resolved a type body's symbol by bare name too and recorded Foo.X's compile-time fact from a block-nested `class Foo: const X: int = len("abc")`; it now collects only a body whose type was declared for that definition (ResolveTypeSymbol + IsDeclaredAt). - Other `Lookup(<decl>.Name)` sites read only: DetectGeneratorAttributes (decorator name), ProjectCompiler.Generators (module-level ClassDefs), CodeGenInfoComputer (module-level statements, per- compile binding), NameResolver.ResolveNestedTypeDeclaration (same-scope only); module property (:2330, a user IsModuleProperty symbol, never a master). Freeze hole closed (Symbol.cs + SymbolMemberCollections.cs): every member collection of TypeSymbol (15: TypeParameters, Fields, Methods, Properties, Events, OperatorMethods, ProtocolMethods, MethodOverloads, Constructors, UnionCases, NestedTypes, NestedTypeAliases, Interfaces, UnresolvedInterfaces, UnresolvedSynthesizedInterfaces) and FunctionSymbol (Parameters, TypeParameters) is a SymbolMemberList<T> (Collection<T> whose Insert/Set/Remove/Clear refuse once frozen) or a SymbolOverloadTable (the three name-keyed tables, freezing every overload list in them); Symbol.Freeze() freezes them (FreezeMembers). A List converts implicitly by WRAPPING (no copy), so every declaration site keeps one list. A `with` copy shares its lists, so a write through a copy of a master symbol is refused too (closes the frozen lane's Parameters residual). Ripple: helper signatures widened to IReadOnlyList (SubstituteTypeParameters, FindBestOverload, the overload-hierarchy lookups, OverloadResolutionContext.Candidates, Deconstruct emission), BuiltinRegistry.RegisterType locals/ApplyNonDiscoverableDefinitions/ApplyClrVariance retyped. StaticStateConformance SymbolVendingRoster: SymbolOverloadTable._table rostered (a symbol's own table, not a cache). The full Compiler.Tests run with the collection freeze ON threw no PhaseViolationException. Tests: - WarmInstanceTriggers.TypeDefinitionTexts (6: class str in if-in-def with property+setter; @DataClass str in module try; struct str in def; interface str in for-in-def; union str in module try; delegate str in def), AllTexts, LaterDocumentProbes (str-has-no-_size, str-upper-control). Replayed by WarmInstanceParityTests (TriggerTexts = AllTexts) and MasterRegistryImmutabilityTests (request sequence, DoNotRetype). - Lsp WarmSequential_LaterDocumentProbes_AnswerAsTheColdProcessAfterEveryTrigger: cold child Sharpy.Lsp per probe (must carry SPY0203), same instance before any trigger = cold (positive control), then after each trigger (opened through the server route + analysed). - Compiler: WarmInstanceTriggers_DoNotChangeALaterDocumentsDiagnostics (the compiler twin), WarmInstanceTriggers_TypeDefinitionsAreRefusedWithoutAnInternalError (no SPY09xx; SPY0202 is the positive control; SPY0340 for module blocks), MasterSymbolCollections_AreFrozen_AndEveryWriteIsRefused (every collection of every master type/overload frozen, floor 500; every mutator on str/len throws and leaves the collection unchanged), SymbolMemberCollections_AcceptWritesUntilFrozen_ThenRefuseEvery (positive control: a fresh symbol accepts every mutator; after Freeze() refuses every one; 15/2 anchored; no symbol type the master owns declares a raw List/Dictionary/HashSet/IList/ICollection/IDictionary/ISet). - TypeDefinitionOwnSymbolMatrixTests (generated, 7 kinds x 4 positions x 3 names = 84 cells, each beside a line-preserving `pass` control on one CompilerApi): no SPY09xx, one SPY0202 at the definition line, every other diagnostic = control's, module Foo's members/const facts = control's. Fresh column = base refusal (positive control). Single-compile Foo cell: Foo()._size keeps SPY0203. - Cli TypeDefinitionInBlockDirectionTests (`sharpyc run`, 6 texts): exit 1, SPY0202, SPY0340 for module blocks, no SPY09xx/SPY0510, no "crash bundle" in the output, none beside the source; instrument control finds a planted bundle. Direction (built sharpyc: base worktree @ 486c9a8 / main @ effba77 / this tree): - c4 @DataClass str in module try: SPY0340 / SPY0909+crash bundle / SPY0340+SPY0202. - c5, c6 class str in module try/if: SPY0340 / SPY0340 / SPY0340+SPY0202+SPY0203. - s4 class str in if in def: SPY0510 ICE / SPY0510 ICE / SPY0202. s6 local Foo: SPY0283 / SPY0283 / SPY0202+SPY0203+SPY0283 (= the no-local-class control's SPY0203+SPY0283). s5 StringBuilder: SPY0202 both. - struct/interface/delegate str in def: SPY0510 ICE / SPY0510 ICE / SPY0202; union str in module try: SPY0340 / SPY0340 / SPY0340+SPY0202. a/b/c local Foo (fresh name): SPY0202 at all three. No program that worked at base is refused or changed: no type definition in a function body or block ever compiled (each was SPY0202, SPY0340 or a SPY0510 ICE at base). Mutations (cp-backed, restored with cp + touch, cmp-verified): - full revert of this commit's production fix (type seam + collection freeze + ConstEligibility seam; the cbabbf7 property freeze stays): Compiler red (6 failed: DoNotChangeALaterDocumentsDiagnostics "str-has- no-_size after trigger class-str-in-if-in-def: warm [] / fresh [SPY0203@3:11]" for 5 triggers; MasterKeySets fixed point "BuiltinRegistry._types[]:str.Fields [] -> [sym#3057]"; matrix (74 problem lines: dataclass x str SPY0909, Foo cells SPY0203 lost + field:_size on Foo, class-const Foo.X=7); TypeDefinitionsAreRefused (SPY0909); both collection guards), Lsp red (1 failed: LaterDocumentProbes), Cli red (6 failed: 4 SPY0510, dataclass SPY0909 + crash bundle, union no SPY0202). - type seam alone reverted (freezes on): red (2 failed: matrix 15 SPY09xx cells, TypeDefinitionsAreRefused) — the freeze turns the writes into ICEs, the layers are independent. - collection freeze off alone: red (2 failed: MasterSymbolCollections_AreFrozen, AcceptWritesUntilFrozen). - `Fields.Freeze` dropped from TypeSymbol.FreezeMembers: red (2 failed, both naming Fields). - a raw `List<int> ProbeList` added to TypeSymbol: red (1 failed: "TypeSymbol.ProbeList : List`1"). - ConstEligibility seam alone reverted: red (1 failed: matrix, 4 class-const x Foo cells "field:X=5/False" vs control "X=5/True"). - restored -> green (below). Tests @ this tree (measured, no-build runs after one build): Compiler.Tests Category!=Benchmark 49243/1/3 (before the ConstEligibility seam and the roster row; the 1 = SymbolVendingRoster missing SymbolOverloadTable._table, fixed); then Compiler.Tests direction+census filter (FileBasedIntegration| MasterRegistry|Shadow|Builtin|Dataclass|Struct|Union|Property|StaticStateConformance|ProcessGlobalState- Conformance|PropertySeedDiscipline|CiFilterCoverage|EmitterCarrierOnly|TypeDefinitionOwnSymbol|Const) 6569/0/2; after the restore MasterRegistry|TypeDefinitionOwnSymbol|census|Const 1605/0/0; Lsp.Tests WarmInstanceParity|FrontEndParity|Conformance|Inventory 16625/0/0; Stdlib.Tests Conformance 456/0/0; Cli.Tests TypeDefinitionInBlockDirection|Conformance 12/0/0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…mpiled, since each nested def declares its own symbol — allowlisted against #2314; the third reports stale (refs #2250, refs #2314) The spec-block gate wraps a block with module-level statements in main(), so a module-level overload set becomes two nested defs with one name in one function — the #2314 cell. Since cbabbf7 (P54) each nested def declares its own symbol and the name binds the first, so the gate's emit-only compile changed on three blocks. Direction under `sharpyc run` of the wrapped text (base 486c9a8 / HEAD 7ae6ed7): - function_parameters.md:142 (f(int)/f(float), f(42), f(3.14)): SPY0908 CS0128 ICE / SPY0220 refusal - overload_resolution.md:261 (same shape, -> str): SPY0908 CS0128 ICE / SPY0220 refusal - overload_resolution.md:320 (six uint8/int16/... overload pairs, every call fits the first def): SPY0220 (a wrong refusal) / SPY0908 CS0128 ICE — now compiles under emit, so its entry is stale The first two are allowlisted citing #2314, and the stale entry is deleted (ratchet: stale entries fail). The :320 cell (wrong refusal -> CS0128 at run) is posted on #2314, which stays open for the owner's rebind / overload / refuse ruling (owner chose this bookkeeping over deciding #2314 in this PR, 2026-10-15). bash build_tools/check_spec_blocks.sh --jobs 8 @ 7ae6ed7 + this commit: pool 1206, allowlisted 479, failing 0, EXIT=0 (was failing 2 + 1 stale, the dev Staleness Gates CI run 38756362024). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
…read BuiltinRegistry's registrations as cbabbf7 spells them — Register(...), the LoadBuiltins-local helper (refs #2250) cbabbf7 (P54 frozen master registry) moved every builtin type registration in BuiltinRegistry.LoadBuiltins onto a local helper, `Register(name, typeof(T), kind, ...)`, which forwards to RegisterType with the method's own interface work list (no registration work survives construction). The registrations themselves are unchanged — bytes is still Sharpy.Bytes, tuple System.ValueTuple — but build_tools' docs tests read them by source text: - _registered_builtin_types / _registered_generic_builtins matched `RegisterType\(` and found none, so the rendered-type roster was empty (test_roster_sources_are_live and the roster cases red); - 14 _MAPPING_IDENTITY citations quoted `RegisterType(<name>, typeof(...)` lines that no longer exist. Both now read `\bRegister\(` (the helper's own declaration `void Register(string ...` cannot match: the pattern requires a string literal or BuiltinNames.X as the first argument), and the citations and generate_stdlib_docs.py's provenance comments quote the current spelling. Evidence: python-build-tools CI on PR #2318 (run 38759166845): test_generate_stdlib_docs.py 22 failed (this is the broken observation: old pattern vs the new source). With this commit, `pytest build_tools/tests/test_generate_stdlib_docs.py`: 318 passed; the whole `pytest build_tools/tests/`: 1135 passed / 7 failed in the sandbox, the 7 (dotnet_serialized watchdog signals, spec_blocks real-compiler smoke) pass unsandboxed (7 passed) and were green in CI. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT
4 of 5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P54: a compile is a pure function of its inputs. No process-wide state crosses compilations, registries, threads or hosts. 24 commits, 486c9a8..7ae6ed7. Refs #2250. The issue closes once this PR's CI is green, including the new isolation sweep step.
What changes
defand type definition declares its own symbol. A type definition inside a function body or a block is refused with SPY0202 (C# has no local types; at base these were already refused, internal errors, or silent writes to the builtin).Evidence
from system.reactive.linq import Observableis now SPY0301.The full close-out with retention and warm-cost numbers is on #2250 (issuecomment-6194337667).
🤖 Generated with Claude Code
https://claude.ai/code/session_015PqhUgKnxnf6TRcFqvxiYT