From a8f3ea37f9acec888e343b4a814354d96bdcf867 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Wed, 21 Oct 2026 07:00:24 +0500 Subject: [PATCH 1/3] =?UTF-8?q?docs(evidence):=20P-014=20bounded=20design?= =?UTF-8?q?=20=E2=80=94=20pre-registration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG --- .../00-preregistration.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/evidence/p014-bounded-design-2026-10/00-preregistration.md diff --git a/docs/evidence/p014-bounded-design-2026-10/00-preregistration.md b/docs/evidence/p014-bounded-design-2026-10/00-preregistration.md new file mode 100644 index 00000000..9d8f4e8c --- /dev/null +++ b/docs/evidence/p014-bounded-design-2026-10/00-preregistration.md @@ -0,0 +1,75 @@ +# P-014 bounded design — pre-registration + +Written 2026-10-21 07:00 +05, before the source-level audit and before any falsifier run. Base: `main` +`ad17d7b2` (merge of #419). #420 (`bf0b4e0a`, report text only) is read but not merged or changed. + +**Budget.** 0.5 agent-day, counted from 06:55. Not extended. When it runs out, I stop and commit whatever evidence +exists, with the verdict `INCONCLUSIVE-P014-DESIGN`. + +**Owner mandate.** `GO-P014-BOUNDED-DESIGN / NO-PRODUCTION` (#420 §8.7). This packet is a design only: +- no production, OwnIR, core, extractor, CI or golden change; +- no restore, build or MSBuild evaluation of third-party projects; no package download; +- no re-run of the #419 sweep. + +## 1. Question + +Can C# semantic binding be made more trustworthy while meeting three conditions? +- foreign references do not leak between projects; +- a partial analysis is not reported as a complete one; +- newly resolved symbols do not turn into a stream of false OWN001/event findings. + +## 2. Allowed evidence + +- **Source reading** of the extractor, the core, the specs and the docs, at `ad17d7b2`. +- **#419 data as frozen:** + - R2 counts; + - the 50-site sample; + - the R3 triage: 1 actionable TP, 2 minor TPs and 6 FPs; + - K1–K7. + + Nothing is re-scored. +- **Cheap falsifiers.** Synthetic fixtures only, at most a few files each, written under this folder: + - run through the frozen #419 instrument (`instrument/`), or through a throw-away Roslyn script in a temp + folder; + - read-only inspection of the `project.assets.json` files already on disk for P1–P5 (structure only: + `targets` keys, `project.frameworks`, `type: project` libraries, RID targets). + + No third-party project is built or restored. +- **Official NuGet/MSBuild/Roslyn documentation**, only for claims about how those tools define things. It never + replaces our measurements. + +## 3. Gates and kill rules (fixed now) + +Each gate states a necessary condition, the cheapest falsifier I will try, and a KILL condition. A gate is `PASS` +only if its falsifier was tried and failed to break the condition, or the condition holds by construction and I +cite the code or documentation for it. + +| Gate | Necessary condition | Cheapest falsifier | KILL if | +|---|---|---|---| +| **D1 correctness** | For a bounded input class (SDK-style, existing `obj/project.assets.json`, one chosen TFM), a resolver can name the exact reference set from on-disk data | An input from the supported class where the algorithm picks a wrong assembly, or binds a symbol with certainty it should not have | The class shrinks to nothing useful, or excluding wrong picks needs MSBuild evaluation / a project-system clone | +| **D2 isolation** | Project A's references, usings or source cannot change project B's binding | Synthetic: two projects with `Dup.T`; same simple assembly name at two versions; `ImplicitUsings` enable vs disable; `Timer` ambiguity; one project with missing dependencies | The design still merges different projects into one compilation and reports the result as trustworthy | +| **D3 honest coverage** | A deterministic, machine-checkable coverage record with explicit numerators, denominators and scope, in which unsupported and skipped never count as clean | Construct a case where the proposed record reports "resolved" or "complete" while a method was skipped, or a reference was missing | No such record fits the existing `--stats`/OwnIR contract without a new incompatible vocabulary | +| **D4 precision** | New facts reach public verdicts only under a policy that cannot regress true findings or known negative controls | Replay the #419 R3 triage (9 new findings) under each policy; check that the policy does not depend on suppressing the 6 known FPs | Every policy that delivers the 1 actionable TP also activates the 6 FPs in the default mode, and there is no opt-in boundary | +| **D5 cost** | The design runs with no restore, no network, no third-party MSBuild targets and no `MSBuildWorkspace`; it keeps `--ref-dir` and directory mode; it degrades honestly when assets or packs are missing | Name a required input that is only obtainable by a forbidden action within the supported class | Safe resolution needs a forbidden action, or its cost clearly exceeds the measured benefit (1 actionable TP over 5 projects) | + +## 4. Verdict rule (fixed now) + +- **`GO-P014-TINY-POC`.** D1–D5 are all `PASS`, possibly with a narrowed input class. The recommended + architecture's PoC contract can be stated with fixtures, an expected manifest, K1–K7, a KILL and a budget. + - It does **not** authorise production code. + - It does **not** authorise diagnostic activation. +- **`KILL-P014-DESIGN`.** Any gate meets its KILL condition. +- **`INCONCLUSIVE-P014-DESIGN`.** A gate cannot be decided within the budget, or it depends on an input that is + not available locally. + +Priorities fixed now: +- A narrow, correct mode is preferred over a wide one that sometimes binds the wrong C# semantics. +- A result that keeps public verdicts unchanged ("coverage-only") is allowed to count as the useful outcome. The + owner's direction (#420 §8.7) puts coverage and the ban on silent wrong binding before new warnings. + +## 5. What I will not claim + +- Any performance figure that I did not measure. +- Correctness of symbols that were not read. +- That a reference-correct compilation has a complete source set. Implicit usings, `#if` defines, generated + sources and project references are a separate axis. From 3e736b7ddfcb31ab49eb1b92e8015ca40c6e921c Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Wed, 21 Oct 2026 07:10:22 +0500 Subject: [PATCH 2/3] =?UTF-8?q?docs(evidence):=20P-014=20bounded=20design?= =?UTF-8?q?=20=E2=80=94=20GO-P014-TINY-POC=20(coverage-only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D1 PASS-NARROWED (framework-first conflict rule falsified on nats.net MicroBench2; replace by the dotnet/sdk order), D2 KILL for one shared compilation / PASS per project (5 synthetic falsifiers), D3 coverage sidecar spec, D4 only coverage-only passes, D5 by construction. No production, OwnIR, extractor, CI or golden change. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG --- .../p014-bounded-design-2026-10/DESIGN.md | 426 ++++++++++++++++++ .../falsifiers/P014Iso.csproj | 15 + .../falsifiers/Program.cs | 152 +++++++ .../falsifiers/d1_conflicts.out.txt | 13 + .../falsifiers/d1_conflicts.py | 98 ++++ .../falsifiers/d2_isolation.out.txt | 50 ++ 6 files changed, 754 insertions(+) create mode 100644 docs/evidence/p014-bounded-design-2026-10/DESIGN.md create mode 100644 docs/evidence/p014-bounded-design-2026-10/falsifiers/P014Iso.csproj create mode 100644 docs/evidence/p014-bounded-design-2026-10/falsifiers/Program.cs create mode 100644 docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.out.txt create mode 100644 docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.py create mode 100644 docs/evidence/p014-bounded-design-2026-10/falsifiers/d2_isolation.out.txt diff --git a/docs/evidence/p014-bounded-design-2026-10/DESIGN.md b/docs/evidence/p014-bounded-design-2026-10/DESIGN.md new file mode 100644 index 00000000..8db95e11 --- /dev/null +++ b/docs/evidence/p014-bounded-design-2026-10/DESIGN.md @@ -0,0 +1,426 @@ +# P-014 bounded design — reference resolution without false confidence + +**Verdict: `GO-P014-TINY-POC`** (§7). +- The GO covers a coverage-only PoC in a research instrument, outside the extractor. +- It does **not** allow production code, extractor changes or any change to public verdicts. + +Rules: [`00-preregistration.md`](00-preregistration.md), commit `a8f3ea37` (07:00 +05, 2026-10-21), written before the +audit and the falsifiers. Base: `main` `ad17d7b2`. #420 (`bf0b4e0a`, open, CI green) was read and not changed. +Work: 06:55–07:10 +05, inside the 0.5 agent-day budget. + +No production, OwnIR, core, extractor, CI or golden change. No restore, build or MSBuild evaluation of a +third-party project, and no package download. The only things built are my own falsifier program (§2.2), offline +and with no package feed, and in-memory synthetic assemblies inside it. + +Each statement below is marked as one of: +- **[code]**: read in the source at `ad17d7b2`; +- **[run]**: measured by a falsifier in this folder; +- **[#419]**: frozen #419 data, not re-scored; +- **[doc]**: taken from the dotnet/sdk or NuGet source or documentation; +- **[inference]**: my reasoning, not verified. + +## 1. Current-state audit (`main` `ad17d7b2`, `frontend/roslyn/OwnSharp.Extractor/Program.cs`) + +| Mechanism | What the code does | Where | +|---|---|---| +| Compilation | **One** `CSharpCompilation.Create("own", …)` over every parsed input tree, default `CSharpCompilationOptions(DynamicallyLinkedLibrary)` | `:5983-5985` [code] | +| Parse options | `CSharpSyntaxTree.ParseText(text, path)`: default options, no defines, no generated global usings | `:5867` [code] | +| Framework set | The extractor **process's** `TRUSTED_PLATFORM_ASSEMBLIES`. The extractor targets `net8.0`, so this is the .NET 8 runtime implementation, not the project's TFM reference pack | `:5889-5894` [code] | +| `OWN_EXTRA_REF_DIRS` | Non-recursive; a DLL whose **file name** is already in the set is skipped | `:5901-5910` [code] | +| `--ref-dir` | Recursive, ordinal-sorted, **first file name wins**, failed loads skipped | `:5917-5934` [code] | +| `bin/` auto-reference | For `.csproj`/`.sln` inputs, `/bin` is added to the `--ref-dir` list unless `--no-project-refs` | `:426-451`, `:543-549` [code] | +| `.csproj` source set | Text read of the XML: default compile items, `EnableDefaultCompileItems`, ``, concrete out-of-tree links; `bin`/`obj`/`*.g.cs`/`*.Designer.cs` skipped. No MSBuild, no `Directory.Build.props` | `:276-370`, `:218-229` [code] | +| `.sln` | Text parse of `Project(...)` lines, fans out to `ProjectCsFiles`; **all projects still go into the one compilation** | `:391-424`, `:471-477` [code] | +| Event fact | `+=` is a subscription iff `GetSymbolInfo(a.Left).Symbol is IEventSymbol`. `Symbol == null` **and** `IsHandler(a.Right)` gives the `unresolved-subscription` marker (OWN050) | `:6155-6156`, `:6368-6379` [code] | +| `IsHandler` | Only `IdentifierName` or `MemberAccess` on the right | `:571-572` [code] | +| Disposable type | `IsOwnedDisposableType` uses the resolved type's real `IDisposable`; **if the type is unresolved it falls back to the name heuristic** `IsDisposableType` | `:5343-5357`, `:5318-5323` [code] | +| `stats` | `facts["stats"]` = `{methods_with_local, methods_flow_analysed, methods_skipped_unmodelled}`, always written; the stderr line is printed only with `--stats`, which requires `--flow-locals` | `:5997`, `:7578-7583`, `:7611`, `:7635-7638`, `:206-209` [code] | +| `stats` consumers | Not read by `ownlang/ownir.py` or by `rust/crates` (grep finds nothing). Not described in the `spec/OwnIR.md` envelope | grep [code] | + +**Three gaps relevant to "no false confidence"** (code reading only; not run on a project): + +1. **G1. Silent drop of unresolved lambda subscriptions.** For `x.Unknown += (s, e) => …` or `+= new EventHandler(H)` + with an unresolved left side, `IsHandler(a.Right)` is false, because the right side is not normalised at + `:6368`. So **neither a fact nor OWN050** is emitted, and nothing counts the site. +2. **G2. Name-heuristic disposables with no marker.** An unresolved field or parameter type such as `FooStream` is + treated as an owned disposable by name (`:5347`). The resulting fact carries no "type unresolved" mark. Correct + references therefore **remove** such facts as well as add them. This is consistent with the #419 R3 diffs, but + that is my inference. +3. **G3. Coverage is invisible to verdicts.** `stats` is producer-only. A run that binds a third of its calls + produces the same "0 findings" exit as a fully bound one. + +## 2. Necessary conditions, cheap falsifiers, KILL ledger + +### D1 — achievable correctness: `PASS-NARROWED` + +**Necessary condition.** For a bounded input class, the exact compile-time reference set can be derived from data +on disk. + +| Sub-question | Finding | +|---|---| +| SDK-style + existing `obj/project.assets.json` | `project.restore.projectStyle` and `targets` give per-TFM package `compile` assets. `_._` means nothing [run, P4/P5 structure dump] | +| Chosen TFM | `targets` has one RID-less key per TFM. RID keys (`net10.0-windows…/win-x64`, P5) carry runtime assets only and must be **ignored for compile** [run]. More than one RID-less TFM: **no guessing**; an explicit `--tfm` is required, otherwise `UNSUPPORTED(multi-tfm)` | +| Runtime vs reference assemblies | Use only `compile` items and the reference pack's `ref/`; never `runtime`, `runtimeTargets`, `native` or the process TPA. TPA is today's source of the P1 `TimeSpan.FromHours` net8-overload binding [#419] | +| Reference pack version | Assets record the exact pack version **only** when the SDK had to download it (`downloadDependencies`: P1 `Microsoft.NETCore.App.Ref [9.0.20]`). P4 and P5 have no such entry: the SDK's bundled pack was used and its patch is not recorded [run]. Rule: same `major.minor` pack present, so `tfm_status = PACK_MAJOR_MINOR`. Missing, so `REFERENCE_MISSING(pack)`, **no substitution** (P1's 10.0-for-9.0 becomes `REFERENCE_MISSING`, not `TFM-APPROX`). That a servicing patch does not change the reference API is [inference], not verified | +| Packages | Exact id/version from `targets`; identity via `libraries[*].sha512` = the cache's `.nupkg.metadata` `contentHash` [#419 §8.2]. Absent, so `REFERENCE_MISSING(package)` | +| Project-to-project | Compile asset is `bin/placeholder/X.dll`, **not usable** [run, P1/P4]. Closure is listed in `libraries[type=project].msbuildProject`, and the selected framework in the target entry's `framework` [run, P4: 3 projects, each with its own assets on disk]. So it is resolvable as **source compilations**, not as DLLs | +| Framework vs package conflicts | **Falsifier found, see below** | +| Same simple name, different identity | In one compilation, CS1704, and one side's calls are lost [run F2.2]. Per project, no conflict | + +**Falsifier F-D1a: the #419 "framework-first" rule picks the wrong assembly** [run, `falsifiers/d1_conflicts.out.txt`]. +- **Input.** `nats-io/nats.net` @ `176e52b`, `sandbox/MicroBench2`, `net8.0`, assets already on disk. +- **The case.** The project references package `System.Collections.Immutable` 9.0.0 and `System.Reflection.Metadata` + 9.0.0. Both are in the cache with AssemblyVersion `9.0.0.0`, against `8.0.0.0` in the 8.0.31 pack. + `PackageOverrides.txt` covers only versions up to 1.4.0 and 1.5.0. +- **What a real build does.** dotnet/sdk `ConflictResolver.ResolveConflict` applies, in order: package overrides, + then the higher assembly version, then the higher file version, then package rank, then the platform item [doc]. + So the package wins. +- **What the #419 rule does.** It skips the package, so the compilation binds the 8.0 API. +- **Effect on the #419 data.** For P2, P4 and P5 the rule happens to agree: the framework is newer or covered by + an override in every conflict. P1 cannot be judged, because its exact pack is missing. **This is not a reason + to re-score #419.** +- **Resolution.** Replace the rule with the dotnet/sdk order. Its inputs are `PackageOverrides.txt`, + `FrameworkList.xml` and the DLLs' metadata versions: all on disk, no MSBuild. A pair that the order leaves + undecided is `CONFLICT_UNRESOLVED`, and the project is not `COMPLETE`. + +**Still outside the class** (detected cheaply, never guessed): +- `` and ``; +- `packages.config` or non-PackageReference projects; +- `extern alias` (`Aliases=`); +- source generators and XAML code generation; +- `Directory.Build.props/targets` that set compile options. + +Each one sets `UNSUPPORTED` or `SOURCE_SET_INCOMPLETE` (§4), never a silent pass. Generator presence is visible +only from the package folder (`analyzers/dotnet/cs`); the assets target entries carry no `analyzers` key [run, key +dump]. WPF and WinForms code generation (`UseWPF`/`UseWindowsForms`, x:Name fields in `obj/*.g.cs`) is +source-set, not reference. + +**No KILL**: no wrong pick in this class needs a project-system clone to avoid. + +### D2 — project isolation: `KILL` for one shared compilation, `PASS` for one compilation per project + +`falsifiers/Program.cs`, Roslyn 4.9.2 (the extractor's version), `Microsoft.NETCore.App.Ref` 8.0.31. In-memory +synthetic projects: "merged" is today's shape, "per-project" is one compilation per project, with project +references as `CompilationReference`. Output: [`falsifiers/d2_isolation.out.txt`](falsifiers/d2_isolation.out.txt) +[run]. + +| Case | Merged (today's shape) | Per-project | +|---|---|---| +| F2.1 `Dup.T` in P1 (`One()`) and P2 (`Two()`) | CS0101 once, at compilation level. **P2's `new Dup.T().One()` binds with no site error**: silent false certainty | P2 `One()`: unresolved + CS1061 (correct). P3 referencing both: **CS0433 at the site**, so site-level conflict becomes representable | +| F2.2 `Lib` 1.0 in P1, 2.0 in P2 | first-name-wins: P2's `V2Only()` unresolved; with both: CS1704, P1's `V1Only()` unresolved | both bind to their own version | +| F2.3 ImplicitUsings on in P1, off in P2 | **P2's `File.Exists` binds**: the #407 leak, silent | P2: unresolved + CS0103 (correct) | +| F2.4 `Timer`: P1 implicit usings (`System.Threading`), P2 `using System.Timers` | P2: CS0104; `t.Elapsed` unresolved **with no site error** | P2 binds `System.Timers.Timer.Elapsed` (`IEventSymbol`) | +| F2.5 P3 lacks dependency `Dep`, P1 has it | **P3's `s.Changed += H` binds to `Dep`'s event**: an event fact the project could never compile | P3: unresolved, CS0246 (correct) | + +Two observations for the design: +- **Ambiguity is often not a candidate list.** In F2.1/P3 the site has `Symbol == null` and **no** + `CandidateSymbols`; the conflict is visible only as CS0433 on the site span. F2.4's `Elapsed` has neither. + Site status must therefore combine `SymbolInfo` with the error diagnostics whose span intersects the site + (§4). +- **The boundary.** A compilation is exactly one MSBuild project, at one TFM, with that project's own: + - source set; + - generated global usings; + - parse options; + - reference manifest. + + Project references are `CompilationReference`s to the referenced project's own compilation, at the framework + its target entry names. + +**Cost of the three shapes** (no timing measured; see §6): +- **A.** One compilation with an exact union reference set. Cannot represent F2.2 and has no per-project usings, + so F2.1, F2.3 and F2.5 stay. **KILL.** +- **B.** One compilation per project over the whole input. N compilations, shared `MetadataReference` objects. + Correct, but needs every project's assets. +- **C.** One root `.csproj`. + - **C0, no closure.** Correct **only** for a project with no project references (P5). Any project reference + makes the root `SOURCE_SET_INCOMPLETE`: P4 and P1 both have three. + - **C1, the root plus its assets closure.** B restricted to one root. Correct for P4 when all four assets are + present. + +### D3 — honest coverage: `PASS` (design, §4) + +**Falsifier attempted.** Can the record call something "resolved" or "complete" while it is not? +- **The trap.** Today's `stats` would report `methods_flow_analysed` for a method whose disposable type was + matched by name (G2). +- **The fix.** + - A record that counts name-heuristic disposable sites separately, never as resolved. + - A per-unit status in which `UNSUPPORTED`, `SKIPPED` and `INCOMPLETE` exclude a unit from "checked". + +**Placement.** A separate sidecar file, like `--heap-effects`, written by the PoC instrument, so the facts +document stays byte-identical. No new OwnIR fact and no version bump. A core never reads it. + +### D4 — diagnostic precision: `PASS` for coverage-only; `KILL` for full activation; opt-in deferred + +See §5. The 6 FPs are not binding errors: their events are real and correctly bound. Any policy that feeds new +event facts into the default verdicts brings them along. + +### D5 — operational cost: `PASS` by construction, cost unmeasured + +Inputs needed: +- the existing assets; +- the cache packages, read-only; +- the local `dotnet/packs` (`FrameworkList.xml`, `PackageOverrides.txt`, `ref/`); +- the `.csproj` text. + +No restore, network, MSBuild targets or `MSBuildWorkspace`. Directory mode and `--ref-dir` are unaffected: they stay +on today's path, labelled `legacy-merged`. Missing inputs give `REFERENCE_MISSING`/`UNSUPPORTED`, never a +substitute. + +**Expected cost drivers** [inference, to be measured in the PoC]: +- metadata loading of 166–251 references per project [#419 `refs_B`]; +- N compilations for N projects in a closure; +- `GetDiagnostics()` for site-level conflicts, which is the expensive part. + +**No benefit is claimed.** + +## 3. Supported and unsupported inputs + +| Input | Status in the recommended mode | +|---|---| +| SDK-style `PackageReference` `.csproj`, assets present, one RID-less TFM (or `--tfm`), all compile assets in the cache with matching content hash, `major.minor` ref packs present, no project references | **supported, `COMPLETE` reference set** (P5) | +| Same, with project references whose closure members each have assets and the target named by the root's entry | **supported via closure (C1)** (P4) | +| Project reference member without assets or source | root `SOURCE_SET_INCOMPLETE(p2p)` | +| Multi-TFM without `--tfm` | `UNSUPPORTED(multi-tfm)` | +| Missing ref pack of the TFM's `major.minor` | `REFERENCE_MISSING(pack)` (P1 net9) | +| Package compile assets missing from cache | `REFERENCE_MISSING(package)` (P1–P3) | +| Framework/package pair undecided by the dotnet/sdk order | `CONFLICT_UNRESOLVED` | +| ``, ``, `Aliases=` | `UNSUPPORTED(reference-kind)` | +| `packages.config`, legacy non-SDK `.csproj` | `UNSUPPORTED(project-style)` | +| Source generators in packages, XAML/WinForms code generation, `Directory.Build.*` setting options, unknown `DefineConstants` | reference set may be `COMPLETE`, but `SOURCE_SET_INCOMPLETE()` | +| Directory input, `.sln`, `--ref-dir`, `bin/` auto-reference | unchanged `legacy-merged`, coverage recorded as `APPROXIMATE` | + +## 4. `binding coverage` specification (sidecar, version 1) + +**Scope.** One record per **unit**, where a unit is a project at a TFM, or the whole merged compilation in +`legacy-merged`. Sites are counted only in source files of that unit's source set. Generated and skipped files +are counted under `files`, never under `sites`. + +**Two levels, never merged.** +- **Site status:** + - `SYMBOL_RESOLVED`: `Symbol != null`, its containing type is not an error type, and no error diagnostic + intersects the site span; + - `SYMBOL_AMBIGUOUS`: `CandidateReason` is `Ambiguous` or `OverloadResolutionFailure` with ≥ 2 candidates, + **or** CS0104, CS0121, CS0229 or CS0433 intersects the site; + - `UNRESOLVED`: otherwise. +- **Unit status:** + - `COMPLETE`; + - `REFERENCE_MISSING`; + - `SOURCE_SET_INCOMPLETE`; + - `UNSUPPORTED`; + - `ANALYSIS_SKIPPED`, also used per method for an unmodelled construct or an unreadable file. + + An `UNRESOLVED` site in a non-`COMPLETE` unit is reported as "unresolved, attributed to ``". It is + an attribution, not a proof. + +**Record.** Deterministic: sorted keys, sorted lists, no timestamps, paths relative to the root. + +```json +{ + "binding_coverage_version": 1, + "mode": "exact-project | legacy-merged", + "units": [{ + "project": "src/App/App.csproj", "tfm": "net10.0", + "tfm_status": "PACK_EXACT | PACK_MAJOR_MINOR | REFERENCE_MISSING | NOT_APPLICABLE", + "unit_status": "COMPLETE | REFERENCE_MISSING | SOURCE_SET_INCOMPLETE | UNSUPPORTED | ANALYSIS_SKIPPED", + "reasons": ["p2p-no-assets:../Lib/Lib.csproj", "generator:communitytoolkit.mvvm/8.4.0"], + "manifest": [{"kind": "pack|package|project", "id": "…", "version": "…", "path": "/…", + "assembly": "Name, Version=…, PublicKeyToken=…", "sha256_16": "…"}], + "missing": ["package:Foo/1.2.3"], + "conflicts": [{"name": "System.Collections.Immutable.dll", "winner": "package", "rule": "assembly-version"}], + "sites": { + "invocation": {"total": 0, "resolved": 0, "ambiguous": 0, "unresolved": 0}, + "type": {"total": 0, "resolved": 0, "error": 0}, + "event_assign": {"total": 0, "event": 0, "non_event": 0, "unresolved": 0, + "unresolved_no_marker": 0}, + "disposable_type": {"total": 0, "resolved": 0, "name_heuristic": 0} + }, + "compiler_errors": {"CS0246": 0, "CS0433": 0}, + "files": {"in_source_set": 0, "parsed": 0, "unreadable": 0, "generated_excluded": 0}, + "methods": {"total": 0, "all_relevant_sites_resolved": 0, "skipped": 0} + }] +} +``` + +- **Invariants**, each machine-checked: + - `total = resolved + ambiguous + unresolved` for each site kind; + - `event_assign.total = event + non_event + unresolved`; + - `unresolved_no_marker ≤ unresolved` (G1); + - `name_heuristic` is never in `resolved` (G2); + - a unit is "checked" only if `unit_status = COMPLETE`, and a method only if it is not skipped and all its + relevant sites (event `+=`, disposable types, `new` of disposables) are resolved. +- **"Relevant site"** = a site that feeds an existing detector. **"Checked" ≠ "clean".** +- **`--stats` compatibility.** + - The facts `stats` block and the stderr line are left exactly as they are. + - A future one-line stderr summary would print **per unit** `resolved/total` for calls and events, plus the + unit status, and would not require `--flow-locals`. + - That is an implementation decision, not part of the PoC. + +## 5. Precision risk from the real #419 findings + +The 9 new findings of R3 (A-ext → B-ext) [#419 §5]: + +| Finding | Verdict [#419] | Is it a binding error? | Would recur in exact-project mode? [inference] | +|---|---|---|---| +| P5 OWN014 `SettingsManager.Current.PropertyChanged` | **TP, actionable** | no | yes, if the event's declaring type binds | +| P5 OWN001 `mediaManager` | TP-benign | no | yes | +| P1 OWN001 `OpenFileDialog dlg` | TP-minor | no | **unknown**: P1 becomes `REFERENCE_MISSING`; the finding must not be credited to a complete unit | +| P1 `ProgressBar.IsVisibleChanged`, P1 `link.Click`, P5 `window.SourceInitialized`, P4 `dialog.Closed` | FP, same lifetime | no: correctly bound events | yes | +| P4 `desktop.Startup` / `desktop.Exit` | FP, app-lived source | no | yes | + +So the precision problem is in the **lifetime model** (P-004; #407's own FP classes), not in references. + +| Policy | Regression risk | Compatibility | Cost | Benefit | Gates | +|---|---|---|---|---|---| +| **1. Coverage-only** | none: facts and verdicts byte-identical | full; additive sidecar | resolver + counter | visible "how much was checked", honest `unit_status`, wrong-TFM and wrong-assembly bindings surfaced | facts byte-identical; deterministic record; controls K1–K12 | +| **2. Opt-in semantic resolution** | for opt-in users the sample gives 1 actionable : 6 FP | new flag; default unchanged | policy 1 + fact wiring + Python/Rust parity on changed facts | the 1 actionable TP becomes reachable | the hard gate below, plus a frozen sample | +| **3. Full activation** | 6 FPs into default verdicts on 5 projects; also removes G2 name-heuristic facts | changes default output | as 2 + golden churn | as 2 | — **KILL now**: fails the hard gate on existing data | + +**Hard gate for any future activation** (owner wording, restated): +- every new diagnostic carries source-level evidence; +- zero regressions on existing true findings and on known negative controls; +- no new suppressions aimed at the 6 known FPs. + +**Proposed thresholds for a future activation pre-registration.** These are my proposal, **not accepted +thresholds**: +- on ≥ 5 `COMPLETE` units from ≥ 3 repositories, chosen by rule; +- **≥ 3 actionable TPs in ≥ 2 repositories**; +- **precision of the added findings ≥ 50%** (actionable + minor TP over all added), with UNCLEAR counted as FP; +- 0 lost TPs; +- 0 findings on K-controls. + +Today's data would give 1 actionable TP in 1 repository and 3/9 = 33%. That **fails**, which is consistent with +#407's deferral until OWN001/event lifetime precision improves. + +## 6. Architectures compared (at most three) + +| | **A. one compilation, exact union set** | **B. per-project graph over the whole input** | **C1. one root `.csproj` + assets closure, coverage-only** | +|---|---|---|---| +| Scope | any multi-project input | `.sln`/directory with assets everywhere | one SDK-style root (§3) | +| Inputs | all assets | all assets + packs + cache | root + closure assets, packs, cache | +| TFM / refs | one union, cannot be per-project | per project: RID-less target, packs by `major.minor`, compile assets, dotnet/sdk conflict order | same as B | +| Identity | simple-name collisions (CS1704) | per project | per project | +| Missing / ambiguous | merged and invisible | per unit | per unit + site-level CS0433/CS0104 | +| Isolation | **fails F2.1, F2.3, F2.5 silently** | passes F2.1–F2.5 | passes F2.1–F2.5 | +| Fallback | — | `legacy-merged` | `legacy-merged`; anything outside §3 gets its status | +| Extractor interaction | replaces today's compilation | replaces today's compilation | **none**: a separate research instrument; extractor facts unchanged | +| OWN050 / OWN001 / events | changes verdicts | changes verdicts | **none** (policy 1) | +| Old CLI | changes semantics | needs a mode switch | untouched | +| Future implementation | — | project graph, N compilations, mode flag, parity | §8 files, if the PoC passes and the owner says so | +| Rough cost [inference] | — | several agent-days + perf work | PoC ≈ 0.5 agent-day | + +**Recommendation: C1 under policy 1.** B is C1 applied to many roots, and is a later decision once C1's cost is +measured. A is killed by D2. + +## 7. Verdict + +**`GO-P014-TINY-POC`.** +- D1 `PASS-NARROWED`: the conflict rule must be the dotnet/sdk order, not framework-first. +- D2 `PASS` for per-project compilation; `KILL` for A. +- D3 `PASS`. +- D4 `PASS` only for coverage-only; full activation killed now. +- D5 `PASS` by construction; cost unmeasured. + +**What this does not allow:** +- production code; +- extractor or OwnIR changes; +- diagnostic activation; +- reopening #407 or P-037. + +**No new OwnIR fact is needed for the PoC.** If a later stage wants the core to treat a `SOURCE_SET_INCOMPLETE` +or `REFERENCE_MISSING` unit differently, for example to refuse "clean", that is a **new fact family and a +separate design decision**. It must not be encoded through `release`, `use` or escape. + +## 8. Contract for the next PoC (not implemented) + +**Form.** A research instrument under `docs/evidence/p014-tiny-poc-/instrument/`, the same pattern as #419's +`P014Bind`, built offline. It reads assets, packs and the cache, and writes the §4 sidecar. It never touches the +extractor. + +**Budget.** 0.5 agent-day, not extended. + +**Fixtures:** +- **Synthetic.** + - F2.1–F2.5 from this folder, re-expressed as on-disk mini projects with hand-written assets. + - #419 K1–K7. + - New: + - **K8**: a package with a newer AssemblyVersion than the framework, so the package wins; plus a variant + covered by `PackageOverrides.txt`, so the framework wins; + - **K9**: multi-TFM without `--tfm`, expected `UNSUPPORTED`; + - **K10**: a project-reference member without assets, expected `SOURCE_SET_INCOMPLETE`; + - **K11**: an unresolved lambda subscription, expected `unresolved_no_marker = 1` (G1); + - **K12**: an unresolved `FooStream` field, expected `name_heuristic = 1` (G2). +- **Real.** + - P5 FluentFlyout: C0, `COMPLETE` reference set, source set incomplete because of WPF code generation. + - P4 BB84.IPTV: C1, a closure of 3. + - nats.net `sandbox/MicroBench2`: manifest only. + - P1: expected `REFERENCE_MISSING(pack)`. + +**Expected reference manifest:** +- **Synthetic fixtures.** Exact, written by hand before the run. +- **P4/P5.** #419's `manifest_B_P4/P5.tsv` plus closure members. Any difference must be explained by a rule + change (conflict order, P2P). +- **MicroBench2.** `System.Collections.Immutable`/`System.Reflection.Metadata` from the package; DiagnosticSource + 9.0.1 `REFERENCE_MISSING`. + +**Expected binding:** +- Every #419 B-resolved site in P4/P5 is `SYMBOL_RESOLVED` with the same symbol, or the difference is listed and + read. +- No resolved symbol's containing assembly is outside its unit's manifest. +- K4 shows CS0101 at compilation level, as before. F2.1/P3 shows **CS0433 at the site**, so the status is + `SYMBOL_AMBIGUOUS`: the per-site conflict check. + +**Baseline equivalence.** Extractor facts for P4/P5 are byte-identical before and after. This holds trivially, +because the extractor is not touched, and it is checked anyway. Python/Rust parity: **not applicable**, because +no fact or verdict changes; recorded as such. + +**#419 controls.** For the 1 actionable TP and the 6 FPs, record each site's status under C1: +- **expected:** all are `SYMBOL_RESOLVED` events; +- **the six FPs:** they are lifetime cases and stay outside P-014; +- **verdicts:** no change. + +**Cost.** Cold and warm wall time and peak working set, 3 runs each, for P4 and P5, for both `legacy-merged` +(today's extractor) and `exact-project`. These are reported. Proposed KILL: exact-project warm time > 3× legacy on +P5. + +**KILL if any of:** +- any synthetic fixture shows a silent false resolution; +- a manifest mismatches its hand-written expectation; +- a resolved symbol comes from an assembly outside its unit's manifest; +- two runs give different records; +- a forbidden action is needed; +- the budget runs out. + +## 9. Components a future implementation would touch (no change made) + +| Component | Change it would need | +|---|---| +| `frontend/roslyn/OwnSharp.Extractor/Program.cs` `:5889-5934`, `:5983` | reference set and compilation per unit instead of one TPA-first merged compilation, behind a mode flag | +| `Program.cs` `:276-451` | assets reader, closure, unit statuses; `ProjectBinDirs` stays for legacy only | +| `Program.cs` `:5867` | per-unit parse options (defines from TFM, LangVersion) and generated global usings: a source-set step, decided separately (#407) | +| `Program.cs` `:6368`, `:5343-5357` | count G1 and G2 in coverage; no behaviour change without a precision gate | +| `Program.cs` `:7578-7638` | sidecar writer; `--stats` line per unit | +| new `OwnSharp.Extractor/ReferenceResolution.cs` | assets, packs and conflict order (dotnet/sdk) | +| `scripts/own-check.sh`, `scripts/own-check.ps1`, `action.yml` | pass the mode flag and sidecar path | +| `spec/OwnIR.md` | none for the sidecar; a fact family only after its own design | +| `ownlang/ownir.py`, `rust/crates/*` | none under policy 1 | +| `tests/`, `tests/check_fix_candidates_facts.py` | facts byte-identity with the new flag on and off | +| `docs/proposals/P-014-semantic-resolution.md` | status, Tier B text (first-name-wins → per-unit manifest) | + +## 10. Files + +- `00-preregistration.md`, `DESIGN.md` +- `falsifiers/Program.cs`, `falsifiers/P014Iso.csproj`: the D2 program (offline build: + `-p:RoslynDir=`). Output: `falsifiers/d2_isolation.out.txt` +- `falsifiers/d1_conflicts.py`: the read-only conflict check (assets, packs, cache). Output: + `falsifiers/d1_conflicts.out.txt`. + - P1's 51 rows are counted per file across two packs and are not comparable with #419's 47. + - P2's 3 and P4's 1 match #419. + +## 11. Open questions + +- Does C1's binding on P4 rise above #419 B's 59 recovered calls once the project-reference sources compile as + their own units? Expected, not measured. +- Are reference-pack APIs really identical across servicing patches? This is the `PACK_MAJOR_MINOR` assumption, + and it is documented intent, not checked here. +- Should generated global usings and TFM defines enter the PoC as a separately reported source-set axis, or wait + for the owner's #407 rule (implicit usings are a separate feature with a census)? This packet keeps them out. diff --git a/docs/evidence/p014-bounded-design-2026-10/falsifiers/P014Iso.csproj b/docs/evidence/p014-bounded-design-2026-10/falsifiers/P014Iso.csproj new file mode 100644 index 00000000..3f6e834c --- /dev/null +++ b/docs/evidence/p014-bounded-design-2026-10/falsifiers/P014Iso.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0latest + enable + enable + p014iso + + + $(RoslynDir)/Microsoft.CodeAnalysis.dll + $(RoslynDir)/Microsoft.CodeAnalysis.CSharp.dll + + diff --git a/docs/evidence/p014-bounded-design-2026-10/falsifiers/Program.cs b/docs/evidence/p014-bounded-design-2026-10/falsifiers/Program.cs new file mode 100644 index 00000000..d4c6cee5 --- /dev/null +++ b/docs/evidence/p014-bounded-design-2026-10/falsifiers/Program.cs @@ -0,0 +1,152 @@ +// P-014 bounded design — D2 isolation falsifiers (research only, not product code). +// Builds tiny in-memory "projects" and compares ONE merged CSharpCompilation (today's extractor shape) +// with ONE compilation PER project (project references as CompilationReference). Prints, per probe site, +// the bound symbol, its containing assembly, or the candidate reason. Same Roslyn as the extractor (4.9.2). +// Framework references: the Microsoft.NETCore.App.Ref 8.0.31 reference pack passed as args[0]. +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +var packDir = args[0]; +var fw = Directory.EnumerateFiles(packDir, "*.dll").OrderBy(p => p, StringComparer.Ordinal) + .Select(p => (MetadataReference)MetadataReference.CreateFromFile(p)).ToList(); +var opts = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + +SyntaxTree T(string proj, string file, string code) => + CSharpSyntaxTree.ParseText(code, path: $"{proj}/{file}"); + +// A metadata-only "package" assembly with an explicit identity, so two versions share a simple name. +MetadataReference Lib(string name, string version, string body) +{ + var c = CSharpCompilation.Create(name, + new[] { CSharpSyntaxTree.ParseText($"[assembly: System.Reflection.AssemblyVersion(\"{version}\")]\n{body}") }, + fw, opts); + using var ms = new MemoryStream(); + var r = c.Emit(ms); + if (!r.Success) throw new Exception(string.Join("\n", r.Diagnostics)); + return MetadataReference.CreateFromImage(ms.ToArray()); +} + +// Probe sites are invocations / member accesses / identifier names carrying a `/*probe:ID*/` comment on the same line. +void Report(string label, Compilation comp) +{ + var errs = comp.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error) + .GroupBy(d => d.Id).OrderBy(g => g.Key).Select(g => $"{g.Key}x{g.Count()}"); + Console.WriteLine($" [{label}] errors: {string.Join(" ", errs)}"); + foreach (var tree in comp.SyntaxTrees) + { + var model = comp.GetSemanticModel(tree); + var text = tree.GetText(); + foreach (var line in text.Lines) + { + var s = line.ToString(); + var i = s.IndexOf("/*probe:", StringComparison.Ordinal); + if (i < 0) continue; + var id = s.Substring(i + 8, s.IndexOf("*/", i, StringComparison.Ordinal) - i - 8); + var node = tree.GetRoot().DescendantNodes(line.Span) + .Where(n => line.Span.Contains(n.Span)) + .OrderBy(n => n is AssignmentExpressionSyntax ? 0 : n is InvocationExpressionSyntax ? 1 : 2) + .FirstOrDefault(n => n is InvocationExpressionSyntax or AssignmentExpressionSyntax or ObjectCreationExpressionSyntax); + if (node is AssignmentExpressionSyntax a) node = a.Left; + if (node is null) { Console.WriteLine($" {id}: no node"); continue; } + var si = model.GetSymbolInfo(node); + string what = si.Symbol is { } sym + ? $"{sym.Kind} {sym.ToDisplayString()} @ {sym.ContainingAssembly?.Identity.ToString() ?? "-"}" + : si.CandidateSymbols.Length > 0 + ? $"null, {si.CandidateReason}: " + string.Join(" | ", si.CandidateSymbols.Select(c => $"{c.ToDisplayString()} @ {c.ContainingAssembly?.Name}")) + : "UNRESOLVED"; + var siteDiags = comp.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error + && d.Location.SourceTree == tree && line.Span.IntersectsWith(d.Location.SourceSpan)).Select(d => d.Id).Distinct(); + Console.WriteLine($" {tree.FilePath} {id}: {what} site-errors=[{string.Join(",", siteDiags)}]"); + } + } +} + +CSharpCompilation Merged(IEnumerable trees, IEnumerable refs) => + CSharpCompilation.Create("own", trees, fw.Concat(refs), opts); +CSharpCompilation Proj(string name, IEnumerable trees, IEnumerable refs) => + CSharpCompilation.Create(name, trees, fw.Concat(refs), opts); + +// SDK default global usings for Microsoft.NET.Sdk (C# 10 ImplicitUsings=enable). +const string ImplicitUsings = """ +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; +"""; + +// ---------- F2.1 two projects declare Dup.T ---------- +Console.WriteLine("F2.1 Dup.T declared in two projects"); +{ + var p1 = new[] { T("P1", "a.cs", "namespace Dup { public class T { public int One() => 1; } }"), + T("P1", "use1.cs", "class U1 { int M() => new Dup.T().One(); /*probe:p1-One*/ }") }; + var p2 = new[] { T("P2", "b.cs", "namespace Dup { public class T { public int Two() => 2; } }"), + T("P2", "use2.cs", @"class U2 { + int M() => new Dup.T().Two(); /*probe:p2-Two*/ + int N() => new Dup.T().One(); /*probe:p2-One-must-fail*/ +}") }; + var p3 = new[] { T("P3", "use3.cs", "class U3 { object M() => new Dup.T(); /*probe:p3-refs-both*/ }") }; + Report("merged", Merged(p1.Concat(p2).Concat(p3), Array.Empty())); + var c1 = Proj("P1", p1, Array.Empty()); + var c2 = Proj("P2", p2, Array.Empty()); + Report("per-project P1", c1); + Report("per-project P2", c2); + Report("per-project P3 -> P1,P2", Proj("P3", p3, new[] { c1.ToMetadataReference(), c2.ToMetadataReference() })); +} + +// ---------- F2.2 same simple assembly name, two versions ---------- +Console.WriteLine("F2.2 Lib 1.0 in P1, Lib 2.0 in P2"); +{ + var v1 = Lib("Lib", "1.0.0.0", "namespace L { public static class C { public static int V1Only() => 1; } }"); + var v2 = Lib("Lib", "2.0.0.0", "namespace L { public static class C { public static int V2Only() => 2; } }"); + var p1 = new[] { T("P1", "a.cs", "class A { int M() => L.C.V1Only(); /*probe:p1-V1Only*/ }") }; + var p2 = new[] { T("P2", "b.cs", "class B { int M() => L.C.V2Only(); /*probe:p2-V2Only*/ }") }; + Report("merged, first-name-wins (v1 only)", Merged(p1.Concat(p2), new[] { v1 })); + Report("merged, both versions", Merged(p1.Concat(p2), new[] { v1, v2 })); + Report("per-project P1 (v1)", Proj("P1", p1, new[] { v1 })); + Report("per-project P2 (v2)", Proj("P2", p2, new[] { v2 })); +} + +// ---------- F2.3 ImplicitUsings enable in P1, disable in P2 ---------- +Console.WriteLine("F2.3 ImplicitUsings: P1 enable, P2 disable"); +{ + var p1 = new[] { T("P1", "GlobalUsings.g.cs", ImplicitUsings), + T("P1", "a.cs", "class A { bool M() => File.Exists(\"x\"); /*probe:p1-File*/ }") }; + var p2 = new[] { T("P2", "b.cs", "class B { bool M() => File.Exists(\"x\"); /*probe:p2-File-must-fail*/ }") }; + Report("merged", Merged(p1.Concat(p2), Array.Empty())); + Report("per-project P1", Proj("P1", p1, Array.Empty())); + Report("per-project P2", Proj("P2", p2, Array.Empty())); +} + +// ---------- F2.4 Timer: System.Timers vs System.Threading ---------- +Console.WriteLine("F2.4 Timer: P1 implicit usings (System.Threading), P2 `using System.Timers;` only"); +{ + var p1 = new[] { T("P1", "GlobalUsings.g.cs", ImplicitUsings), + T("P1", "a.cs", "class A { void M() { var t = new Timer(_ => { }); /*probe:p1-Timer*/ } }") }; + var p2 = new[] { T("P2", "b.cs", """ + using System.Timers; + class B { Timer t = new Timer(100); /*probe:p2-Timer*/ + void M() { t.Elapsed += H; /*probe:p2-Elapsed*/ } + void H(object s, ElapsedEventArgs e) { } } + """) }; + Report("merged", Merged(p1.Concat(p2), Array.Empty())); + Report("per-project P1", Proj("P1", p1, Array.Empty())); + Report("per-project P2", Proj("P2", p2, Array.Empty())); +} + +// ---------- F2.5 one project with a missing dependency ---------- +Console.WriteLine("F2.5 P3 misses its dependency; P1 is complete"); +{ + var dep = Lib("Dep", "1.0.0.0", "namespace D { public class Src { public event System.EventHandler Changed; public void Raise() => Changed?.Invoke(this, System.EventArgs.Empty); } }"); + var p1 = new[] { T("P1", "a.cs", @"class A { D.Src s = new D.Src(); + void M() { s.Changed += H; /*probe:p1-Changed*/ } void H(object o, System.EventArgs e) { } }") }; + var p3 = new[] { T("P3", "c.cs", @"class C { D.Src s = new D.Src(); + void M() { s.Changed += H; /*probe:p3-Changed-missing*/ } void H(object o, System.EventArgs e) { } }") }; + Report("merged (Dep referenced, P3 did not declare it)", Merged(p1.Concat(p3), new[] { dep })); + Report("per-project P1 (Dep)", Proj("P1", p1, new[] { dep })); + Report("per-project P3 (no Dep)", Proj("P3", p3, Array.Empty())); +} diff --git a/docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.out.txt b/docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.out.txt new file mode 100644 index 00000000..b2914e6c --- /dev/null +++ b/docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.out.txt @@ -0,0 +1,13 @@ +## P4 (net10.0, packs 10.0.12) +System.ComponentModel.Annotations/5.0.0 System.ComponentModel.Annotations.dll pkgAV=5.0.0.0 fwAV=10.0.0.0 override=4.3.0 -> framework(tie/lower) +## P5 (net10.0-windows10.0.22000.0, packs 10.0.12) +## P2 (net10.0, packs 10.0.12) +System.Diagnostics.DiagnosticSource/6.0.1 System.Diagnostics.DiagnosticSource.dll pkgAV=6.0.0.0 fwAV=10.0.0.0 override=10.0.12 -> framework +System.IO.Pipelines/8.0.0 System.IO.Pipelines.dll pkgAV=8.0.0.0 fwAV=10.0.0.0 override=10.0.12 -> framework +System.Runtime.CompilerServices.Unsafe/6.0.0 System.Runtime.CompilerServices.Unsafe.dll pkgAV=6.0.0.0 fwAV=10.0.0.0 override=7.0.0 -> framework +## nats.net sandbox/MicroBench2 @176e52bc (net8.0, packs 8.0.31) +System.Collections.Immutable/9.0.0 System.Collections.Immutable.dll pkgAV=9.0.0.0 fwAV=8.0.0.0 override=1.4.0 -> package +System.Diagnostics.DiagnosticSource/9.0.1 System.Diagnostics.DiagnosticSource.dll pkgAV=MISSING fwAV=8.0.0.0 override=4.4.0 -> ? +System.Reflection.Metadata/9.0.0 System.Reflection.Metadata.dll pkgAV=9.0.0.0 fwAV=8.0.0.0 override=1.5.0 -> package +## P1 (net9 vs 10.0.12 packs, TFM-approx): winners + 51 framework diff --git a/docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.py b/docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.py new file mode 100644 index 00000000..fd86c400 --- /dev/null +++ b/docs/evidence/p014-bounded-design-2026-10/falsifiers/d1_conflicts.py @@ -0,0 +1,98 @@ +"""P-014 bounded design, falsifier F-D1a (read-only, research only). + +For one project.assets.json target, list package compile assets whose file name collides with a +framework reference-pack DLL, and name the winner under the dotnet/sdk conflict order +(package overrides -> higher AssemblyVersion -> ...). Reads the assets, the local packs' +FrameworkList.xml / PackageOverrides.txt and the cached package DLLs' metadata; builds nothing. + +usage: python -I d1_conflicts.py +""" + +import json +import os +import re +import subprocess +import sys +import xml.etree.ElementTree as ET + +NUGET = os.path.expanduser("~/.nuget/packages") +PACKS = r"C:\Program Files\dotnet\packs" +BAD = ("MISSING", "ERR") + + +def pack_info(name: str, ver: str) -> tuple[dict[str, str], dict[str, str]]: + data = os.path.join(PACKS, name, ver, "data") + framework: dict[str, str] = {} + flist = os.path.join(data, "FrameworkList.xml") + if os.path.exists(flist): + for f in ET.parse(flist).getroot(): + if f.get("Type", "Managed") == "Managed" and f.get("AssemblyName"): + framework[f.get("AssemblyName", "").lower()] = f.get("AssemblyVersion", "") + overrides: dict[str, str] = {} + po = os.path.join(data, "PackageOverrides.txt") + if os.path.exists(po): + with open(po) as fh: + for line in fh: + if "|" in line: + n, v = line.strip().split("|") + overrides[n.lower()] = v + return framework, overrides + + +def vt(v: str) -> tuple[int, ...]: + return tuple(int(x) for x in re.findall(r"\d+", v)[:4]) + + +def assembly_versions(paths: list[str]) -> dict[str, str]: + if not paths: + return {} + ps = ( + "foreach($p in $input){ try{ $n=[Reflection.AssemblyName]::GetAssemblyName($p);" + ' "$p|$($n.Version)" }catch{ "$p|ERR" } }' + ) + out = subprocess.run( + ["pwsh", "-NoProfile", "-Command", ps], + input="\n".join(paths), capture_output=True, text=True, check=False, + ).stdout + return dict(line.rsplit("|", 1) for line in out.splitlines() if "|" in line) + + +def winner(pkg_ver: str, pkg_av: str, fw_av: str, override: str | None) -> str: + if override is not None and vt(pkg_ver) <= vt(override): + return "framework" + if pkg_av in BAD: + return "?" + return "package" if vt(pkg_av) > vt(fw_av) else "framework(tie/lower)" + + +def main() -> None: + assets, tfm, pack_ver, packs = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] + with open(assets, encoding="utf-8") as fh: + doc = json.load(fh) + framework: dict[str, str] = {} + overrides: dict[str, str] = {} + for p in packs.split(","): + a, b = pack_info(p, pack_ver) + framework.update(a) + overrides.update(b) + rows = [] + for key, entry in doc["targets"][tfm].items(): + if entry.get("type") != "package": + continue + pid, pver = key.split("/") + for c in entry.get("compile") or {}: + name = os.path.basename(c) + if name == "_._" or name[:-4].lower() not in framework: + continue + path = os.path.join(NUGET, pid.lower(), pver.lower(), c) + fw_av = framework[name[:-4].lower()] + rows.append((pid, pver, name, fw_av, overrides.get(pid.lower()), path)) + av = assembly_versions([r[5] for r in rows if os.path.exists(r[5])]) + for pid, pver, name, fw_av, ovr, path in rows: + pkg_av = av.get(path, "MISSING") + print(f"{pid}/{pver}\t{name}\tpkgAV={pkg_av}\tfwAV={fw_av}\toverride={ovr}" + f"\t-> {winner(pver, pkg_av, fw_av, ovr)}") + + +if __name__ == "__main__": + main() diff --git a/docs/evidence/p014-bounded-design-2026-10/falsifiers/d2_isolation.out.txt b/docs/evidence/p014-bounded-design-2026-10/falsifiers/d2_isolation.out.txt new file mode 100644 index 00000000..b7dc4eb5 --- /dev/null +++ b/docs/evidence/p014-bounded-design-2026-10/falsifiers/d2_isolation.out.txt @@ -0,0 +1,50 @@ +F2.1 Dup.T declared in two projects + [merged] errors: CS0101x1 + P1/use1.cs p1-One: Method Dup.T.One() @ own, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + P2/use2.cs p2-Two: Method Dup.T.Two() @ own, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + P2/use2.cs p2-One-must-fail: Method Dup.T.One() @ own, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + P3/use3.cs p3-refs-both: Method Dup.T.T() @ own, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + [per-project P1] errors: + P1/use1.cs p1-One: Method Dup.T.One() @ P1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + [per-project P2] errors: CS1061x1 + P2/use2.cs p2-Two: Method Dup.T.Two() @ P2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + P2/use2.cs p2-One-must-fail: UNRESOLVED site-errors=[CS1061] + [per-project P3 -> P1,P2] errors: CS0433x1 + P3/use3.cs p3-refs-both: UNRESOLVED site-errors=[CS0433] +F2.2 Lib 1.0 in P1, Lib 2.0 in P2 + [merged, first-name-wins (v1 only)] errors: CS0117x1 + P1/a.cs p1-V1Only: Method L.C.V1Only() @ Lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + P2/b.cs p2-V2Only: UNRESOLVED site-errors=[CS0117] + [merged, both versions] errors: CS0117x1 CS1704x1 + P1/a.cs p1-V1Only: UNRESOLVED site-errors=[CS0117] + P2/b.cs p2-V2Only: Method L.C.V2Only() @ Lib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + [per-project P1 (v1)] errors: + P1/a.cs p1-V1Only: Method L.C.V1Only() @ Lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + [per-project P2 (v2)] errors: + P2/b.cs p2-V2Only: Method L.C.V2Only() @ Lib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] +F2.3 ImplicitUsings: P1 enable, P2 disable + [merged] errors: + P1/a.cs p1-File: Method System.IO.File.Exists(string?) @ System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a site-errors=[] + P2/b.cs p2-File-must-fail: Method System.IO.File.Exists(string?) @ System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a site-errors=[] + [per-project P1] errors: + P1/a.cs p1-File: Method System.IO.File.Exists(string?) @ System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a site-errors=[] + [per-project P2] errors: CS0103x1 + P2/b.cs p2-File-must-fail: UNRESOLVED site-errors=[CS0103] +F2.4 Timer: P1 implicit usings (System.Threading), P2 `using System.Timers;` only + [merged] errors: CS0104x2 + P1/a.cs p1-Timer: Method System.Threading.Timer.Timer(System.Threading.TimerCallback) @ System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a site-errors=[] + P2/b.cs p2-Timer: UNRESOLVED site-errors=[CS0104] + P2/b.cs p2-Elapsed: UNRESOLVED site-errors=[] + [per-project P1] errors: + P1/a.cs p1-Timer: Method System.Threading.Timer.Timer(System.Threading.TimerCallback) @ System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a site-errors=[] + [per-project P2] errors: + P2/b.cs p2-Timer: Method System.Timers.Timer.Timer(double) @ System.ComponentModel.TypeConverter, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a site-errors=[] + P2/b.cs p2-Elapsed: Event System.Timers.Timer.Elapsed @ System.ComponentModel.TypeConverter, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a site-errors=[] +F2.5 P3 misses its dependency; P1 is complete + [merged (Dep referenced, P3 did not declare it)] errors: + P1/a.cs p1-Changed: Event D.Src.Changed @ Dep, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + P3/c.cs p3-Changed-missing: Event D.Src.Changed @ Dep, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + [per-project P1 (Dep)] errors: + P1/a.cs p1-Changed: Event D.Src.Changed @ Dep, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null site-errors=[] + [per-project P3 (no Dep)] errors: CS0246x2 + P3/c.cs p3-Changed-missing: UNRESOLVED site-errors=[] From 3d6be6c05e64378e21a67a5e240a827c0f36679a Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Wed, 21 Oct 2026 08:49:01 +0500 Subject: [PATCH 3/3] =?UTF-8?q?docs(evidence):=20P-014=20bounded=20design?= =?UTF-8?q?=20=E2=80=94=20scope=20D1,=20CS0101=20vs=20CS0433,=20owner=20ru?= =?UTF-8?q?ling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D1 is a counterexample to framework-first, not proof of a replacement rule; CS0101 (merged, two source definitions) vs CS0433 (two referenced project assemblies) made explicit; owner ruling recorded (PoC ceiling 0.25 agent-day, metrics only, no production, #407 KILL stands). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG --- .../p014-bounded-design-2026-10/DESIGN.md | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/docs/evidence/p014-bounded-design-2026-10/DESIGN.md b/docs/evidence/p014-bounded-design-2026-10/DESIGN.md index 8db95e11..fbf0e860 100644 --- a/docs/evidence/p014-bounded-design-2026-10/DESIGN.md +++ b/docs/evidence/p014-bounded-design-2026-10/DESIGN.md @@ -79,9 +79,17 @@ on disk. - **Effect on the #419 data.** For P2, P4 and P5 the rule happens to agree: the framework is newer or covered by an override in every conflict. P1 cannot be judged, because its exact pack is missing. **This is not a reason to re-score #419.** -- **Resolution.** Replace the rule with the dotnet/sdk order. Its inputs are `PackageOverrides.txt`, - `FrameworkList.xml` and the DLLs' metadata versions: all on disk, no MSBuild. A pair that the order leaves - undecided is `CONFLICT_UNRESOLVED`, and the project is not `COMPLETE`. +- **What this shows, and what it does not.** F-D1a is a **counterexample to framework-first**. It is **not** a + proof that any replacement rule is correct. + - `d1_conflicts.py` applies only the first two steps of the dotnet/sdk order (package overrides, then + AssemblyVersion). On the rows above, those two steps decide every case. + - Taking the highest `AssemblyVersion` on its own does not replace MSBuild's conflict resolution. +- **Direction for the PoC.** Before the first PoC run, write down the selection rule from the SDK's contract + (the `ResolvePackageFileConflicts` / `ConflictResolver` order, with assembly identity, not only file names). + - Its inputs are on disk: `PackageOverrides.txt`, `FrameworkList.xml` and the DLLs' metadata. No MSBuild is + needed. + - A pair the rule leaves undecided is `CONFLICT_UNRESOLVED`, and the project is not `COMPLETE`. + - The rule is verified by the K8 controls (§8), not assumed. **Still outside the class** (detected cheaply, never guessed): - `` and ``; @@ -106,13 +114,17 @@ references as `CompilationReference`. Output: [`falsifiers/d2_isolation.out.txt` | Case | Merged (today's shape) | Per-project | |---|---|---| -| F2.1 `Dup.T` in P1 (`One()`) and P2 (`Two()`) | CS0101 once, at compilation level. **P2's `new Dup.T().One()` binds with no site error**: silent false certainty | P2 `One()`: unresolved + CS1061 (correct). P3 referencing both: **CS0433 at the site**, so site-level conflict becomes representable | +| F2.1 `Dup.T` in P1 (`One()`) and P2 (`Two()`) | **CS0101** once (two source definitions in one compilation). **P2's `new Dup.T().One()` binds with no site error**: silent false certainty | P2 `One()`: unresolved + CS1061 (correct). P3 referencing **two assemblies**, P1 and P2, that each define `Dup.T`: **CS0433 at the site**, so site-level conflict becomes representable | | F2.2 `Lib` 1.0 in P1, 2.0 in P2 | first-name-wins: P2's `V2Only()` unresolved; with both: CS1704, P1's `V1Only()` unresolved | both bind to their own version | | F2.3 ImplicitUsings on in P1, off in P2 | **P2's `File.Exists` binds**: the #407 leak, silent | P2: unresolved + CS0103 (correct) | | F2.4 `Timer`: P1 implicit usings (`System.Threading`), P2 `using System.Timers` | P2: CS0104; `t.Elapsed` unresolved **with no site error** | P2 binds `System.Timers.Timer.Elapsed` (`IEventSymbol`) | | F2.5 P3 lacks dependency `Dep`, P1 has it | **P3's `s.Changed += H` binds to `Dep`'s event**: an event fact the project could never compile | P3: unresolved, CS0246 (correct) | Two observations for the design: +- **CS0101 vs CS0433.** CS0101 (a namespace already contains the definition) is the merged-compilation form: + two source definitions in one compilation, reported once and not at the site. CS0433 (the type exists in two + referenced assemblies) appears only once the projects are separate assemblies. The per-site conflict signal + therefore exists only with per-project compilation. - **Ambiguity is often not a candidate list.** In F2.1/P3 the site has `Symbol == null` and **no** `CandidateSymbols`; the conflict is visible only as CS0433 on the site span. F2.4's `Elapsed` has neither. Site status must therefore combine `SymbolInfo` with the error diagnostics whose span intersects the site @@ -314,13 +326,14 @@ measured. A is killed by D2. ## 7. Verdict **`GO-P014-TINY-POC`.** -- D1 `PASS-NARROWED`: the conflict rule must be the dotnet/sdk order, not framework-first. +- D1 `PASS-NARROWED`: framework-first is falsified. A replacement rule must be written down from the SDK's + conflict-resolution contract and verified in the PoC; this packet does not prove one correct. - D2 `PASS` for per-project compilation; `KILL` for A. - D3 `PASS`. - D4 `PASS` only for coverage-only; full activation killed now. - D5 `PASS` by construction; cost unmeasured. -**What this does not allow:** +**What this does not allow** (the #407 KILL and its deferral stand unchanged): - production code; - extractor or OwnIR changes; - diagnostic activation; @@ -336,7 +349,7 @@ separate design decision**. It must not be encoded through `release`, `use` or e `P014Bind`, built offline. It reads assets, packs and the cache, and writes the §4 sidecar. It never touches the extractor. -**Budget.** 0.5 agent-day, not extended. +**Budget.** Proposed here as 0.5 agent-day; the owner set **0.25 agent-day** to the next kill-gate (§12). **Fixtures:** - **Synthetic.** @@ -366,8 +379,9 @@ extractor. - Every #419 B-resolved site in P4/P5 is `SYMBOL_RESOLVED` with the same symbol, or the difference is listed and read. - No resolved symbol's containing assembly is outside its unit's manifest. -- K4 shows CS0101 at compilation level, as before. F2.1/P3 shows **CS0433 at the site**, so the status is - `SYMBOL_AMBIGUOUS`: the per-site conflict check. +- K4 (one compilation, two source definitions) shows CS0101 at compilation level, as before. F2.1/P3 (two + referenced project assemblies) shows **CS0433 at the site**, so the status is `SYMBOL_AMBIGUOUS`: the per-site + conflict check. **Baseline equivalence.** Extractor facts for P4/P5 are byte-identical before and after. This holds trivially, because the extractor is not touched, and it is checked anyway. Python/Rust parity: **not applicable**, because @@ -424,3 +438,27 @@ P5. and it is documented intent, not checked here. - Should generated global usings and TFM defines enter the PoC as a separately reported source-set axis, or wait for the owner's #407 rule (implicit usings are a separate feature with a census)? This packet keeps them out. + +## 12. Owner ruling (21.10.2026, recorded after review of `3e736b7`) + +`ACCEPT-PHASE-0 / GO-EVIDENCE-PR / GO-METRICS-ONLY-POC / NO-PRODUCTION`. + +- **This evidence.** It may be published as a PR containing only this folder. +- **D1.** Accepted as a counterexample to framework-first, not as proof of a new selection rule. The exact rule + must follow the SDK's contract; `AssemblyVersion` alone is not a replacement for MSBuild. +- **The Tiny PoC.** Allowed as a **separate stage after this evidence is accepted**, with a ceiling of + **0.25 agent-day** to the next kill-gate. + - Inputs: existing assets and reference packs only, with no third-party restore or build. + - Conflicts are resolved by assembly identity and verifiable SDK rules. + - Implicit usings and defines are not added globally; #407 stays a separate axis. + - It repeats the D1/D2 controls and measures P4/P5: binding coverage, compiler diagnostics, time and peak + memory. + - Time and memory limits are fixed before the first run. + - No OwnIR or public verdict change. Any finding change is recorded as a breach of the PoC boundary, not as an + improvement. +- **KILL if:** + - isolation cannot be achieved without false bindings; + - the references cannot be selected on a sound basis; + - the cost is out of proportion to the observed gain. +- **Production stays forbidden.** #407 stays deferred and its KILL stands. #420 is a separate package and is not + touched.