P-037 A2.0: evidence harness hardening + terminal-green baseline (T, R) - #361
Merged
Merged
Conversation
…" is checkable A1.1 changes the fact surface the Roslyn extractor emits BEFORE it changes any semantics (#304, the a1/a2 sequence). Every one of those steps carries the same obligation — richer facts, same verdicts — and an obligation nobody can check is a wish. This is the checker. No production code, no verdict change: the tool only runs the launcher and records what it already says. Method is deliberately scripts/benchmark.py's: one own-check run per FILE, never per directory. A directory run compiles a case's before.cs and after.cs into one compilation where each resolves the other's symbols; the per-file runs are what the corpus was labelled against, so a snapshot taken any other way would measure a different program. Captured at --severity note, which is strictly MORE information than the verdict threshold: every finding is recorded with its level, so a comparison can be read at verdict level (error/warning) or including advisories. That separation is load-bearing for A1. An OWN051 appearing where a fabricated `release` used to sit is the LEGACY_HONESTY class arriving, not a regression, and a snapshot that had discarded advisories could not tell those two apart. --engine is explicit and required (#262 Stage 3). A bare invocation resolves the Rust candidate and exits 2 without one, and the A1.1 change is in the EXTRACTOR, shared by both engines, so a snapshot that did not name its engine would not say which half of the seam it measured. `compare` refuses two snapshots whose engines differ: that comparison measures the engine, not the change. Each snapshot records its source_commit and whether the tree was dirty, and a dirty snapshot marks itself `is_evidence: false` in its own payload rather than being quietly indistinguishable from a clean one. It is still written — the inner loop needs it — it just cannot be cited. A file whose SARIF fails to parse is recorded as parse_error with a stderr tail, never as a file with no findings. The two are opposite facts and must not share a representation. ruff check . All checks passed! mypy (repo config, --strict) Success: no issues found in 55 source files tests/test_stage3_surfaces.py rc=0 — 65 invocations, 25 explicit Refs #304. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01376o4syzCem5aQ6NssxwX2
…not exist; fail closed on an unreadable run The first version passed `--severity note`. The launcher takes `error|warning` and nothing else, so every one of the 137 runs exited 2 with "unknown --severity 'note'" having analysed nothing, and the tool recorded a confident, entirely empty baseline for both engines. Fixed to `--severity warning`, the most inclusive threshold the flag actually offers, and the docstring no longer describes a level the CLI does not have. The wrong flag was one bug. Calling the result evidence was the worse one, and it is the one fixed here: a snapshot with an unreadable run is not a snapshot with fewer findings. `take` now clears `is_evidence`, records the unreadable paths, prints the exit code and stderr of the first few, and returns 1; `compare` refuses such a snapshot outright, because comparing it would report the absence of those runs as agreement. This is the same principle the P-022 merge gate already states about its own controls — a gate whose controls are failing must not be the thing that says a merge is safe. A baseline is a gate. The `parse_error` field is what made the defect visible at all: had unreadable runs been recorded as "no findings", the broken baseline would have been indistinguishable from a clean one, and every "same verdicts" proof built on it would have been worthless while looking rigorous. Verified against independently measured facts before being trusted again (corpus/p036-bakeoff, engine=rust, 12 files): 3 findings total — the three G-V4 controls' false OWN003, and nothing else the four guarded-consume before.cs files: 0 findings — the F3 false negatives legacy-honesty-else-unresolved-forward: 0 findings tree correctly marked DIRTY (uncommitted fix in place at snapshot time) each of which matches a measurement taken by hand earlier, through a different path. ruff check . All checks passed! mypy (repo config, --strict) Success: no issues found in 55 source files Refs #304. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01376o4syzCem5aQ6NssxwX2
The "richer facts, same verdicts" obligation needs something to be the same AS. This is it, taken on a clean tree at 17e7285 (the snapshot tool's own fix), one own-check run per file, engine named explicitly. 137 files · 84 findings · 69 files with findings · 0 unreadable · is_evidence true OWN001 45 · OWN002 14 · OWN050 11 · OWN025 7 · OWN003 6 · DI001 1 The property worth recording, because A1.1-a1 changes the EXTRACTOR and the extractor is shared: at this commit the two engines are identical on all 137 files — same exit codes, same findings, same lines, same levels, zero differences. So any post-change divergence BETWEEN the engines is attributable to the change rather than to pre-existing drift, and does not have to be untangled from noise that was already there. OWN050 appearing in the capture (11, note level) confirms the threshold does what the tool claims: advisories ride through `--severity warning` and are recorded with their level, so a comparison can be read at verdict level or over everything. A1 needs that distinction — an OWN051 arriving where a fabricated `release` used to sit is the LEGACY_HONESTY class, not a regression. The seven A1 controls, in the baseline, as the acceptance matrix expects them to stand TODAY: guarded-consume-early-return exit 0, no findings <- F3 false negative guarded-consume-flag-branch exit 0, no findings <- F3 false negative guarded-consume-negation-wrapper exit 0, no findings <- F3 false negative guarded-consume-wrapper-forward exit 0, no findings <- F3 false negative gv4-control-aliased-self-null OWN003 warning @28 <- G-V4 false positive gv4-control-mutated-guard OWN003 warning @38 <- G-V4 false positive gv4-control-ref-alias-guard OWN003 warning @35 <- G-V4 false positive legacy-honesty-else-unresolved-forward exit 0, no findings <- class-3 pin Four false negatives and three false positives from one mechanism seen from two sides. None of them may move in a1 or a2: those steps change what the extractor SAYS, not what anything concludes. They move only at the retirement step, and then in the direction the matrix names. Refs #304. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01376o4syzCem5aQ6NssxwX2
…o semantic cut The frontend now SAYS what a callee does with its owned parameters. Nothing reads the new field to change a verdict, and `ConsumesParam` is untouched — the semantic cut is a later step, deliberately, so that a verdict movement here is a defect rather than a feature. WHY THIS WAS NEEDED `build_skeletons` (own-bridge/src/lower.rs and its Python twin) reads `functions[].params` to derive each parameter's transfer. The C# frontend has never emitted that field, so the MOS layer — solver, transfer lattice, the whole INF rule set — has had no parameter input from C# at all, and "does this call consume its argument?" was answered instead by `ConsumesParam`, syntactically and flow-insensitively, inside the extractor. That layer is not missing the ability, only the input. Fed the shapes by hand it already discriminates `if (g) release p` -> may, `release p` -> must, `use p` -> no, correctly and without flattening. It was simply never asked. TWO CORRECTIONS TO THE DIAGNOSIS PUBLISHED ON #304 1. The gate is 6509, not 6621. There are two: `if (candidates.Count == 0)` at 6509 and `if (tracked.Count == 0)` at 6621. A parameter-only method exits at the FIRST and never reaches the second, so a patch applied only at 6621 — the line the #304 comment names — builds, runs, and changes nothing. Found the way anyone following that citation would find it. Both gates now admit a method that has an ownership-relevant parameter; the #304 comment needs the correction once a1 is proven. 2. The predicate is `IsOwnedDisposableType`, not `ImplementsIDisposable`. The strict form (which `ConsumesParam` uses) demands a resolved symbol and silently answers false for a type the compilation cannot see — on a single-file run, most of the BCL. Locals use `IsOwnedDisposableType`, which falls back to the syntactic name heuristic exactly for that case. A frontend that tracks a `Stream` local but not a `Stream` parameter would be inconsistent about one type for no reason a reader could defend. WHAT CHANGED Owned parameters are computed once, before BOTH gates; by-value only, read off the syntax so the test agrees with itself when the symbol does not resolve. Neither gate was deleted — removing them would emit every method in the compilation, most carrying nothing an ownership analysis can read, a five-figure fact diff bought with one line that looked suspicious. A method earns a record by having something to say: tracked locals, or a parameter whose ownership a caller must reason about. Lowering runs over locals AND owned parameters so `param_signals` / `definite_release` / `forward_targets` have a body to read. No `acquire` is emitted for a parameter: every acquire site is gated on a declarator and a parameter has none — checked against the using-statement path, not assumed. `params` rides only when non-empty, so a method with no owned parameter keeps byte-for-byte the record shape it had before. MEASURED The callees now reach the facts: Guarded.Close params=[{s,14}] body=[if@16] guarded -> may GuardedWrapper.Inner params=[{s,9}] body=[if@11] guarded -> may GuardedWrapper.Outer params=[{s,17}] body=[release:s@19] That third line is worth reading twice. `Outer` only calls `Inner(s, keep)`, but `ConsumesParam` judges `Inner` a consumer, so a `release` is emitted where a `call` belongs and `Outer` now enters the summary layer as an UNCONDITIONAL consumer. The fabrication does not merely corrupt the caller's facts; now that summaries exist it will corrupt those too. That is not a regression introduced here — it is the reason retiring `ConsumesParam` is its own later step, and it is now visible instead of implied. corpus/p036-bakeoff (12 files, engine=rust): 0 of 12 verdicts moved The full-corpus proof over both engines is still measuring and lands in the next commit; this message claims the subset only. Refs #304. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01376o4syzCem5aQ6NssxwX2
…actory transfer
CI run 2170 went red on six jobs. Root cause, one line of it:
[OWN004] 'parg_59' is a borrow and cannot be returned
(it would outlive the resource it borrows)
reproduced locally on samples/OverloadSigSample.cs:
public static FileStream Open(FileStream existing, bool flush)
{
var probe = new MemoryStream(); // a tracked local, so this overload emits a record
probe.Dispose();
if (flush) existing.Flush();
return existing; // returns a PARAMETER -> not fresh
}
The rule at that site already said what it meant — "a tracked LOCAL returned
BARE is a fresh-factory transfer" — and it held for free only because a
parameter could never be in the set. a1 put owned parameters there, so
`return existing` started lowering as a fresh return, and the core read a fresh
return of something it had not seen acquired as an escaping borrow. The core is
right and the fact was wrong: the resource belongs to the CALLER and outlives
the call.
What that return actually is, is an ALIAS of the parameter, and OwnIR cannot say
so: `aliasOf`/`aliased` are reserved in the schema and the production skeleton
builder has never emitted them — own-bridge/src/mos.rs says exactly that in its
own header. So the frontend keeps the silence it has always kept here, a bare
CFG exit claiming no owned return, rather than asserting a kind that is false.
Teaching the IR alias returns is a semantic change and is not this step's
business; this is a gap now named rather than a behaviour now invented.
The parameter test is semantic on purpose: a parameter symbol resolves from
source even when its TYPE does not (a single-file run sees little of the BCL),
and a local never resolves to IParameterSymbol, so no local loses its fresh
return to this.
MEASURED — against origin/main's extractor, same inputs, same core
the five --flow-locals samples main 40 findings · a1 40 findings · IDENTICAL
dogfood, frontend/roslyn/samples main 164 findings · a1 164 findings · IDENTICAL
rc=1 with --fail-on-finding, OWN001 present
The first comparison in this session was run wrong and is worth recording: it
compared a1 against the previous COMMIT rather than against main, which already
carried the broken a1, so "before" was the internal error and scored 0 findings.
A diff against the wrong baseline is not a weaker result, it is a different
question answered confidently. Redone against origin/main.
Refs #304.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01376o4syzCem5aQ6NssxwX2
The obligation a1 carries is "richer facts, same verdicts". Here is the same. CORPUS — 137 files, both engines, both levels, clean trees rust 17e7285 -> 2de574a verdict: UNCHANGED all levels: UNCHANGED python 17e7285 -> 2de574a verdict: UNCHANGED all levels: UNCHANGED "All levels" matters as much as the verdict line: it says no advisory moved either, so nothing was traded quietly below the verdict threshold. THE REPOSITORY'S OWN C# — 81 files, main's extractor vs a1's, same core frontend/ + audit/ rust 165 findings IDENTICAL python 165 findings IDENTICAL frontend/roslyn/samples (the dogfood job's input) 164 IDENTICAL, rc=1 the five --flow-locals samples 40 IDENTICAL WHY TWO SOURCES AND NOT ONE The 137-file corpus said UNCHANGED for the FIRST version of a1 too — the one that broke six CI jobs. The corpus contains no method that returns its own disposable parameter; the repository's own tree does, and that shape is what `[OWN004] 'parg_59' is a borrow and cannot be returned` was. The corpus is labelled for VERDICTS. a1 changes the FACT SURFACE, and those are not the same coverage question: a corpus can be complete for the bugs it names and still miss the syntax that breaks a lowering. Treating a green corpus as a green change is exactly how the first version of this step got pushed. So the repo-tree comparison is not a nicety here, it is the source that has already caught something the corpus could not, and it stays in the gate for a2. Neither source is evidence on its own and both were run against origin/main's extractor rather than against the previous commit — a mistake made once in this session and not repeated: the previous commit already carried the broken a1, so diffing against it answered a different question with great confidence. Snapshots: docs/evidence/p037-a1-after-{rust,python}.json, taken clean at 2de574a, against docs/evidence/p037-a1-baseline-{rust,python}.json at 17e7285. Refs #304. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01376o4syzCem5aQ6NssxwX2
Two gaps closed. The second one was embarrassing.
THE CENSUS — corpus/p037-shapes, 11 shapes, scripts/p037_fact_shapes.py
a1 taught this the hard way: the 137-file VERDICT corpus reported UNCHANGED for
a version of a1 that turned six CI jobs red. It contains no method returning its
own disposable parameter; the repository's tree does.
verdict coverage != syntax / fact-shape coverage
Two hundred more random C# files would not have closed that gap. One fixture per
shape does, and it fails by NAME rather than as a number that moved somewhere.
Each case records today's emitted facts and verdict; `anchored` means the facts
must stay, `pending_a2` pins today's facts anyway so a2's diff is visible per
shape instead of aggregated away, and carries its contract in prose.
return-disposable-parameter ANCHORED — the OWN004 shape, nailed down so it
does not depend on OverloadSigSample.cs happening
to survive in the tree
named-arguments-reordered a2: resolve to declared parameter index
extension-receiver a2: receiver -> declared parameter 0
guard-bool-const-true/false a2: {kind: bool_const, value: …}
guard-forward-bare/negated a2: {kind: param, source_param, negated?}
guard-mutated, guard-ref-alias a2: NOT eligible, no guard metadata at all
guard-opaque-expression a2: {kind: opaque} and nothing else
call-expression-statement a2: emit a real `call` for statement-level calls
Two findings while recording them, both corrections to things stated earlier:
1. `CallReleasesReceiver` is reached ONLY from two sites gated on
`ThisFieldName(...)`. It is part of the FIELD disposal detector and never runs
on a local. The earlier claim — that fixing the seam only at
`ConsumeReleaseArgs` would leave a guarded `this T` receiver fabricating
call-site releases — was wrong in its location. Corrected inventory:
A ConsumeReleaseArgs @3762 flow-locals, ARGUMENT handoff <- the seam
B recursion @4586 internal to ConsumesParam
C CallReleasesReceiver FIELD channel only, @5785 @5801
D ConsumeReleaseArgs @6562 escape analysis, rides on A
2. Consequently `var s = ...; s.DisposeIt();` — a first-party extension that
disposes its receiver UNCONDITIONALLY — is charged OWN001. Verified with an
unguarded probe, so the guard is not what does it. A pre-existing false
positive of exactly the family P-037 addresses, arriving through the RECEIVER
rather than the argument. Not A1's to fix; recorded so it cannot later be
mistaken for something A1 caused.
CI — neither P-037 checker was running anywhere
`scripts/p037_controls.py` shipped with the A1.0 bootstrap and nothing invoked
it. A conformance control that no job runs is a file, not a control. Both
censuses now run in the Rust-default dogfood job, the one place that already has
a .NET SDK, a built Rust candidate and a Python, on the Linux leg only: both are
about the extractor's output and the two engines' reading of it, neither of
which is platform-shaped, and the Windows leg exists for own-check.ps1.
The verdict snapshot is deliberately NOT wired: 137 files times two engines is
about fourteen minutes, and it is the per-STEP gate each A1.1 commit runs and
records under docs/evidence/. Said out loud in the workflow itself, because an
unstated omission is indistinguishable from an oversight.
scripts/p037_controls.py --engine both 4/4 match
scripts/p037_fact_shapes.py check 11/11 shapes, both engines
ruff / mypy clean, 56 source files
tests/test_stage3_surfaces.py rc=0, 66 invocations, 26 explicit
Refs #304.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01376o4syzCem5aQ6NssxwX2
Replace the impossible source_commit==HEAD idea with a checkable freshness predicate: clean evidence, source ancestry, unchanged measurement closure, and an exact path/blob manifest for every C# input under the declared roots. The implementation closure is one shared source for verdict and MOS snapshots. A self-checkable runtime-path list makes an omitted dependency a red control rather than an undocumented assumption. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Reuse the existing P-022 capture protocol on both engines: one extractor capture, Python repro capture, and the existing own-shadow-engine adapter. There is one summaries-layer reader for the common envelope and no second Rust dump path. Compare the whole summaries document, not only transfer, over the labelled corpus and the independent repository C# tree. Fact bytes may move in A2; Python MOS, Rust MOS, and after-state cross-engine parity may not. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Upgrade the verdict snapshot to the shared A2.0 provenance contract and add an explicit verify mode. Pin the freshness predicate with live controls for closure coverage, input-manifest tampering, and the evidence bit. Wire those controls plus the whole-MOS reader selftest into the existing Linux P-037 dogfood step. The expensive verdict and two-source MOS sweeps remain per-step evidence, not per-push CI. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Use the provenance path/blob manifest as the MOS sweep denominator instead of filesystem rglob. A prior dotnet build can leave generated obj/**/*.cs files in the checkout; measuring those while the evidence manifest names only committed blobs would make the run and its provenance describe different input sets. Fail closed when a committed input named by git is absent from the working tree. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Address the first CI signal from the provenance module. Use collections.abc for Iterable and the repository's preferred unpacking form. The Stage-3 surface census also treats a literal own-check launcher name in executable Python as a possible invocation. The provenance closure only stores that path as data, so construct the same runtime string from two literals instead of weakening the launcher census or adding a fake engine declaration. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Follow the repository's Ruff import ordering for the new level-2 tool. No behaviour or evidence contract changes. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Keep the two level-2 evidence sources semantically distinct. Corpus files are independent labelled programs and remain one document per file. The repository source is one program, so all 81 tracked C# files under frontend/ and audit/ enter one Roslyn compilation and one summaries document. This preserves inter-file summary edges and matches the repository-tree source that caught A1's syntax-shape gap instead of silently replacing it with 81 different programs. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Strengthen the runtime-dependency self-check: every path declared as part of the measurement must not only be covered by SUBJECT_PATHS, it must resolve to an actual git object in HEAD. A matching typo in both lists can therefore no longer protect an empty path. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
The repository discovers every test_*.py module through tests/run_tests.py and requires a run() entry point. Keep one implementation of the control and expose main() through that adapter so both direct dogfood invocation and the full Python matrix execute the same checks. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
Make the A2 evidence tools fail closed before any heavy baseline is recorded. - compare only honest evidence in ancestor order, with the same inputs, measurement closure and toolchain identity; - drive verdict snapshots from the exact git path/blob denominator instead of filesystem rglob; - build the Rust executable from HEAD with cargo --release --locked and record its digest plus Python/.NET/Rust toolchain identities; - re-check tree cleanliness after measurement; - put both new evidence tools under strict mypy and ignore .mypy_cache; - restore the verdict snapshot executable bit and fix Ruff import ordering. Keep the Stage-3 launcher census strict without a string-splitting escape hatch: the literal own-check path is provenance data, declared after all subprocess helpers so it cannot be mistaken for a nearby invocation. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com> AI-Session: ChatGPT current conversation
Strict mypy now covers both A2 evidence tools and caught the same shape in both: the input_manifest comprehension attempted iteration before its isinstance guard could narrow the value. Reject a non-list manifest first, then build the path set from the narrowed list. No evidence has been recorded on the red head, so this moves no baseline. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com> AI-Session: ChatGPT current conversation
The A2.1 claim is "richer facts, zero MOS/verdict movement". A differential can
carry it only when before and after measured ONE frozen population with ONE
measuring instrument in ONE qualified execution environment, and only the
treatment moved. This makes that contract executable and hostile-tested; it
takes no baseline and starts nothing of A2.1.
scripts/p037_evidence.py (the contract, shared by both snapshot tools)
instrument vs treatment INSTRUMENT_PATHS (engines, launcher, capture and
compare plumbing, these tools) may not move across
a before/after pair; TREATMENT_PATHS (extractor,
spec) may. Neither may move between a record's
source commit and the HEAD it is a claim about.
snapshot validity record_problems: self-consistent at its own commit.
freshness provenance_problems: ...and fresh at a given HEAD.
differential eligibility comparison_problems: before self-valid (it predates
the treatment change on purpose), after fresh at
HEAD, ancestry, identical instrument, identical
frozen population and support closure, identical
execution profile.
frozen population population_commit; primary .cs blobs and their
semantic support closure (sibling .xaml of
*.xaml.cs, FodyWeavers.xml on the ancestor chain)
derived from git at that commit and materialized
with the same adjacency into the ignored,
deterministic .p037-population/<commit>/<digest>/
(one directory per population, so the corpus and
the repo populations of one commit never share a
tree), re-hashed through git, never read from the
working tree. cwd stays ROOT so the extractor's
Rel(path) is identical on both sides of a pair.
population integrity the extractor reads its side inputs LAZILY (the
weaver walk runs after parsing), so a population
rewritten under a run changes facts without failing
anything visible; found live, when two concurrent
takes sharing one directory produced a confident
is_evidence=true snapshot with one extra summary.
A take now holds a lease on its population and
re-hashes every materialized file after measuring;
a population that did not stay intact is never
evidence.
external contamination the extractor's FodyWeavers.xml walk continues above
the population to the filesystem root; every
ancestor there must prove the file absent
(present or uninspectable -> REFUSED), mirroring
File.GetAttributes semantics exactly.
qualified artifacts cargo build --locked --release --message-format=json;
the executable path comes from the compiler-artifact
message, its digest, size, toolchain, host, source
commit and Cargo.lock identity are recorded, and the
file is re-hashed right before it runs. own-cli is
handed to the launcher as OWEN_RUST_CORE explicitly;
own-shadow-engine's identity must equal the build's.
execution profile python -VV, .NET SDK + runtimes, rustc -vV, cargo,
host: an eligibility field, equal across a pair.
reference environment OWN_EXTRA_REF_DIRS removed from every child
environment AND the extractor's stderr read for the
"+N extra references" attestation: prevention plus
the observation that prevention held.
cleanliness clean before, clean after (post_run_dirty is never
evidence), and --out refused inside the checkout
unless git-ignored: baselines are written outside
the tree and enter docs/evidence by a record commit.
scripts/p037_{mos,verdict}_snapshot.py
take --population-commit, compare/verify --against; measured files are the
materialized population and must equal its manifest; schema bumped
(mos/2, verdict/3); older snapshots are refused, they carry none of this.
tests/test_p037_evidence.py: 14 negative controls + 1 positive
01-04 non-evidence / dirty on either side (+ post-run dirty)
05 before not an ancestor of after
06-07 manifest tampered on either side (+ digest)
08 after stale at HEAD: instrument moved, treatment moved, verify alone
09 frozen population drift, and drift is the ONLY refusal raised;
population rewritten or a file vanished under the run; the lease is
exclusive
10 executable digest != qualified build; artifact from another commit or
a dirty tree
11 execution profile mismatch / missing
12 support closure drift: blob changed, file dropped
13 OWN_EXTRA_REF_DIRS removed from the child env; the attestation line is
read; observed extra references and an inherited env are refused
14 external ancestor FodyWeavers.xml present / uninspectable
+ old before, current after, an intentional treatment change between,
same instrument/population/closure/profile -> ADMITTED
History-shaped controls (05, 08, 09, positive) run over real synthetic
commits in a temporary depth-1 clone, never in the checkout's .git, and pass
from a shallow checkout. Two fitness pins: every filesystem read site in the
extractor is classified against the four explicit-file mechanisms (counting
File.ReadAllText would guard spelling; this guards the contract), and the
repo population's support closure is exactly the three files it is today,
with the linked project's FodyWeavers.xml explicitly NOT among them
(WeaverOwnedFiles is a .csproj-mode mechanism, explicit-file mode never
runs it).
tests/test_stage3_surfaces.py
The launcher literal in p037_evidence.py is data. Instead of a string split or
a position dodge it now carries a narrow marker that the census accepts only
for a bare assignment of that literal, in that file, on that line or the one
beside it, exactly once; the marker's own self-test proves a subprocess call
cannot wear it.
CI: the Linux dogfood step builds both qualified executables and derives,
materializes, re-hashes and removes the frozen repo population, where cargo,
git and dotnet are real. .gitignore: .p037-population/.
Refs #304.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfeUtLRm6KFRWcTGQZxQQb
… after the run Hostile review of 1113188 found the executable had the same TOCTOU the population had just been cured of. Both tools qualified target/release/<bin> once, before the run, and then executed that shared, mutable path for the whole measurement. A cargo build landing mid-run (anyone's, any crate that rebuilds the bin) replaces the file under the launcher; the population stays intact, the tree stays clean, the snapshot says is_evidence=true, and artifacts.sha256 still names the build that did not run. The fix is the same shape as the population's: prevention plus attestation. build_rust_artifact the package's own artifacts are cleaned first and the compiler-artifact message must report the executable as NOT fresh: cargo judges freshness by source fingerprints, never by the output's bytes, so a target/ file rewritten by anyone before the take would otherwise be qualified as is. Only the package is recompiled and linked; dependencies stay cached (both bins: ~20 s). seal_artifact after qualification the executable is copied into a private, unique take directory outside the checkout, the copy is re-hashed against the qualified digest, and the run executes ONLY the copy: own-shadow-engine through engine_identity(sealed), own-cli as OWEN_RUST_CORE=sealed. finalize_run the one post-run attestation both tools call before writing: population intact, every sealed executable still the qualified build, tree clean. Any failure is not evidence and the reason is kept on the record. record_problems every artifact must carry an executed attestation whose digest equals the qualified digest and whose post_run_intact is true. Control 10d, in the real lifecycle rather than on a dict: a qualified build is sealed; the shared target/ file is rewritten and the run is unaffected; the sealed copy is rewritten and finalize_run drops is_evidence, marks the artifact tampered, and record_problems refuses the snapshot. Records with an executed digest other than the qualified one, without post_run_intact, or never sealed are refused as well. The artifacts CLI seals and attests on the CI runner too. Refs #304. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfeUtLRm6KFRWcTGQZxQQb
Four sequential baseline snapshots (MOS repo, MOS corpus, verdict/python, verdict/rust) taken at 4a8e658 in a pinned measurement environment (CPython 3.11.15, .NET SDK 8.0.425, Rust 1.94.1), each independently verified against T. Evidence-only: no instrument, treatment, spec, or CI file changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011wKL9pEApQTK4dE3eXv8g8
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Repository: PhysShell/Own.NET/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Sep 19, 2026
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.
Summary
terminal-green candidate T (
4a8e6582e10222403cd40adc9e95db7e0228a1c2).5fd6bfa6abd4c2af7e53712af300cf66c97f2f50): the four baselineevidence records (MOS repo, MOS corpus, verdict/python, verdict/rust)
taken at T in a pinned measurement environment, each independently
verifyd. Evidence-only commit — no instrument/treatment change.dce26ed+2cbd6c6,currently on
claude/p037-a2.1-after-dce26ed), which was brancheddirectly from T and needs T+R already in
main's history so its ownbefore/after comparison lands cleanly rather than referencing baseline
files that don't yet exist in
main.Test plan
5fd6bfa): 30/30 checksverify --against Tandverify --against Rgit difffrom T to R touches onlydocs/evidence/**Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_011wKL9pEApQTK4dE3eXv8g8
🤖 Generated with Claude Code