Skip to content

docs(h28): resolve the DbCommand residual - #399

Merged
PhysShell merged 7 commits into
mainfrom
arena/e1a5511b-own-net
Oct 10, 2026
Merged

PhysShell merged 7 commits into
mainfrom
arena/e1a5511b-own-net

Conversation

@PhysShell

@PhysShell PhysShell commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Correct the research report to a single final verdict: H28-INCONCLUSIVE until G1–G3 produce results. Keep PR docs(h28): resolve the DbCommand residual #399 open; no production analyzer or extractor semantics changed.
  • Repair the falsifier: P4 records one isolated Npgsql query immediately after DataTable.Load, reader disposal, and command disposal; P6 directly counts live SQLite statements with sqlite3_next_stmt (including its SQLitePCLRaw enablement and same-command reuse arm), with no RSS or forced GC.
  • Add a strict PASS/FAIL/SKIP runner, a PostgreSQL supervisor, a production Roslyn → OwnIR → summaries → SARIF G3 verifier, and a PR workflow where any SKIP fails.
  • Narrow Npgsql source wording: Dispose changes logical state and may cache/recycle a command; source review did not establish deterministic native-handle release or server-side DEALLOCATE. OWN001 remains a possible-leak diagnostic, not a proof of native leakage or a confirmed false positive.
  • Re-derive 44 source excerpts plus a pinned full-file finalizer scan. Verify that the .NET 8 falsifier is TFM-compatible with Npgsql 10.0.3 and Microsoft.Data.Sqlite 10.0.12.

Validation

  • tests/test_corpus.py: 33/33; whole-tree Ruff and mypy: pass.
  • Source evidence derivation: 44/44 excerpts + scan; handwritten OwnLang reduction matches its three expected OWN001 findings. This is not G3 evidence.
  • Python/shell checks, YAML parse, and C# grammar parse pass; the grammar parse is not a compiler build.
  • G1, G2, and G3 remain unverified. This sandbox has no .NET SDK. run.sh all-required correctly returned non-zero on the G3 and runtime SKIPs; see corpus/ownership-lab/h28-command/evidence/required-gates-local.txt. The CI workflow is the required reproducible run.
  • The repository-wide python tests/run_tests.py did not complete: the checkout is shallow and Cargo is unavailable. The transcript is saved in corpus/ownership-lab/h28-command/evidence/local-validation.txt.

No #382 architecture work is proposed or claimed from this report. Leave the PR open pending the required gate results.

PhysShell and others added 7 commits October 10, 2026 10:02
H-28 (research/ownership-semantics-lab-v1 @ 298b305, not on main) settled
DataTable.Load(reader) with a runtime falsifier: the reader IS closed, so the
presumed BORROW was refuted and the demanding instance
(victor-wiki/DatabaseManager DbInterpreter.GetDataTableAsync:699) is not a
protocol leak. The same record then wrote one sentence and never classified it:

  "what remains is the never-disposed DbCommand `cmd` (an object leak without
   a protocol consequence)"

That sentence is a description, not a verdict: it was never falsified, never
entered the witnessed-callee table, never reached a gate. This closes it.

VERDICT: H28-PROVIDER-SPECIFIC. The absence of DbCommand.Dispose() in this
family is not one fact.

  * Npgsql 10.0.3 (and main, identical): NpgsqlCommand has no finalizer, both
    ctors GC.SuppressFinalize, and Dispose(bool) only nulls _transaction, sets
    a managed state flag and optionally recycles the command into the
    connection's one-slot CachedCommand. Reset() is managed-only. Server-side
    prepared statements belong to NpgsqlConnector.PreparedStatementManager and
    are never DEALLOCATEd by the command. => releases nothing.
  * Microsoft.Data.SqlClient, MySqlConnector: managed-only Dispose, no
    finalizer. => releases nothing.
  * Microsoft.Data.Sqlite: SqliteCommand.Dispose -> DisposePreparedStatements
    finalizes native sqlite3_stmt SafeHandles (ReleaseHandle ->
    sqlite3_finalize) AND disposes its reader. A plain ExecuteReader() runs
    PrepareAndEnumerateStatements(), which puts those handles on the COMMAND;
    SqliteDataReader.Close()/Dispose() does not finalize them. => the command
    is the only deterministic releaser. REAL defect.

So the old residual is true for Npgsql and false for Microsoft.Data.Sqlite; it
was written from a two-provider falsifier whose SQLite arm never looked at the
command.

Sharper witness found on the way (pivot recorded in the report): the ownership
relation connection -> command -> reader is not what either side assumes.
NpgsqlCommand.CurrentActivity (System.Diagnostics.Activity IS IDisposable in
.NET 8 and 10) is stopped by exactly two call sites repo-wide, both in
NpgsqlDataReader.Dispose/DisposeAsync -> Command.TraceCommandStop(). Neither
NpgsqlDataReader.Close() nor NpgsqlCommand.Dispose() calls it — and
DataTable.Load calls Close(). With a tracing listener attached the span never
ends. Meanwhile reader Cleanup sets Command.State = Idle, which makes the
command reusable: that is not ownership.

CURRENT OWN.NET (extractor @ e889f8b, source-derived; engine executed):
IsOwningFactory mints an owned local for IDbConnection.CreateCommand()
(L4880-4882 -> L7035), IsDisposeOptional does not cover DbCommand (L2593), and
the argument-escape rule untracks the reader at table.Load(reader)
(L7155-7182). Predicted: OWN001 (error) on `command`, silence on `reader`. The
engine half reproduces exactly that (evidence/engine-CommandDispose.txt: 2x
command in B/E + 1x reader in E, A/D clean). On Npgsql with tracing on the
verdict is inverted — error on the object whose Dispose frees nothing, silence
on the one whose disposal is load-bearing.

The escape drop is also only accidentally right: DataTable.Load closes the
reader IFF !reader.NextResult(), so a multi-result-set reader is left OPEN and
the drop is a false negative there.

NO PRODUCTION CHANGE. Nothing here touches the extractor, the core, the
vocabulary or a diagnostic. corpus/ownership-lab/ is not globbed by
tests/test_corpus.py (still 33/33). The model gap is shown to be an instance of
#382 — which already names this lowering and this exact loss — with proven_call
(OwnIR v2, H1) as the landed transport precedent; section 10 proposes the
smallest experiment and explicitly not a lattice, a new code, or provider
special-casing.

Deliverables: docs/notes/h28-npgsql-command-resolution.md plus
corpus/ownership-lab/h28-command/ — fx/CommandDispose.cs (9 variants, abstract
ADO.NET types on purpose: that is what IsOwningFactory matches), a runnable
falsifier pinned to net8.0/Npgsql 10.0.3/MS.Data.Sqlite 10.0.12 with probes
P1-P6 each chosen to separate two hypotheses, and 32 source citations
auto-derived from pinned refs (blob sha + sha256 + line range) by
scripts/derive_source_evidence.py, which exits non-zero when the note goes stale.

Honest limits, stated in the report rather than papered over: there is no .NET
SDK and no reachable NuGet feed in the producing sandbox, so the Roslyn
extractor and the runtime falsifier were NOT executed. fx/CommandDispose.cs
expectations and probes P2/P4/P6 are marked PREDICTED; run.sh runtime prints
SKIP-WHY instead of faking it. What WAS executed: the engine half, all 32
source derivations, and a live PostgreSQL 16.2 (pgserver) used to pin that
prepared statements and cursors are session-scoped — independently corroborating
that no command-level Dispose can be their releaser.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@PhysShell
PhysShell merged commit e7d33e3 into main Oct 10, 2026
77 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant