From 5cc2c2156afcfc27f226b449eaa8232b76b84692 Mon Sep 17 00:00:00 2001 From: sotashimozono Date: Tue, 22 Sep 2026 11:42:29 +0000 Subject: [PATCH] Never claim loaded-matches-disk (0.8.6) An end-to-end run on archeion-demo showed the claim overreaches: a report recipe defined in a script got loaded-matches-disk, because Base._included_files records no run-time include, and review found more holes (a method added to Base from Main changes what a package runs with no trace in any cache header). Showing that the code which ran was the snapshot's cannot be done from inside the process, so binding_of now returns unverified where it returned a match, with the reason. loaded-differs-from-disk stays: it is a fact, not a claim of absence. The per-root loaded status is still recorded. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 9 ++++++--- Project.toml | 2 +- src/provenance/observe.jl | 27 ++++++++++++++++++--------- test/vault/test_observe.jl | 9 ++++++--- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 53f5a56..7adf20c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,9 +38,12 @@ maps a `DataKey` to file storage and tracks what's done. project-agnostic. See - **`observe_sources(vault) -> token` keeps two claims apart.** The SNAPSHOT (`sources/src1-/`, id = SHA-256 of its `files.tsv`) is content only — no HEAD, no host — so equal trees get equal ids. The OBSERVATION (`observations/.toml`) holds when/where/HEAD/dirty and the - **binding**: `loaded-matches-disk` only when packages loaded from the config's repo were - checked against the snapshot via their precompile cache headers (size + CRC32c, an internal - API — anything unreadable is `unknown`, never a match). Nothing under `.datavault/` may be + **binding**, which never claims a match: `loaded-differs-from-disk` when a loaded package's + precompile-cache sources (size + CRC32c, an internal API — anything unreadable is `unknown`) + differ from the snapshot, otherwise `unverified`. Loaded packages matching does not show the + code that ran was theirs (a script, a closure, a method added to Base from `Main` leave no + trace; `Base._included_files` records no run-time include), so `loaded-matches-disk` is never + written; an old record's is read as `unverified`. Nothing under `.datavault/` may be named `*.log.toml`: discovery walks the tree for that suffix. - **`.done` is `key=value` lines, `done_version=2`.** Readers take the keys they know and ignore the rest; keys are only ever added. Every v2 field is written on every call (`unknown` rather diff --git a/Project.toml b/Project.toml index 9948a9c..526c7d2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "DataVault" uuid = "23f5f8f6-b4da-40ee-8c72-c53b6c5de94f" -version = "0.8.5" +version = "0.8.6" authors = ["sota shimozono "] [deps] diff --git a/src/provenance/observe.jl b/src/provenance/observe.jl index cb65db6..98fb0a0 100644 --- a/src/provenance/observe.jl +++ b/src/provenance/observe.jl @@ -4,8 +4,11 @@ # # * a SOURCE SNAPSHOT: every file of each source root as (path, type, mode, size, SHA-256), # identified by the SHA-256 of that inventory, `src1-`, and stored once per vault; -# * its BINDING to the code the process runs: `unverified`, unless every package the process -# loaded from a root was checked against the snapshot through its precompile cache header. +# * its BINDING to the code the process runs, which never claims a match: `unverified`, or +# `loaded-differs-from-disk` when a loaded package's cached sources are known to differ from +# the snapshot. A match is not claimed because it cannot be shown from inside the process: +# code defined in a script, a closure, or a method added to Base from `Main` runs without +# leaving a trace in any cache header (`Base._included_files` records no run-time include). # # Nothing here says the snapshot is the code that ran. It says what was on disk, when, and how far # the process's loaded code was checked against it. File names under `.datavault/` must never end @@ -299,12 +302,13 @@ end """ binding_of(status, revise_loaded, main_files_in_roots) -> (binding, reasons) -The binding an observation can claim, from each root's loaded status. `loaded-matches-disk` only -when the config's repository (where the study's code lives) was loaded from and matched, every -other root that was loaded from matched, Revise is not loaded, and no file included into `Main` -lies inside a root (code defined there cannot be checked). -`loaded-differs-from-disk` when a loaded package's sources differ from the snapshot. Otherwise -`unverified`, with the reasons. +The binding an observation can claim, from each root's loaded status: `loaded-differs-from-disk` +when a loaded package's sources differ from the snapshot (a fact: the bytes it was built from are +not on disk), and otherwise `unverified`, with the reasons. It never returns +`loaded-matches-disk`: that every loaded package matched does not show that the code which ran +was theirs, since code defined outside any package (a script, a closure, a method added to Base +from `Main`) leaves no trace to check. A reader treats a `loaded-matches-disk` written by an +earlier version as `unverified`. """ function binding_of(status::AbstractDict, revise_loaded::Bool, main_files_in_roots) differs = sort([k for (k, v) in status if v == "differs"]) @@ -327,9 +331,14 @@ function binding_of(status::AbstractDict, revise_loaded::Bool, main_files_in_roo "$f is included into Main, where its code cannot be checked" for f in main_files_in_roots ) - return isempty(reasons) ? "loaded-matches-disk" : "unverified", reasons + isempty(reasons) && push!(reasons, NO_MATCH_CLAIMED) + return "unverified", reasons end +const NO_MATCH_CLAIMED = + "every loaded package matched the snapshot, but a match is not claimed: code defined " * + "outside a package (a script, a closure, a method added from Main) cannot be checked" + # ── the observation ─────────────────────────────────────────────────────────────────────────── function _root_record(root, loaded::String)::Dict{String,Any} diff --git a/test/vault/test_observe.jl b/test/vault/test_observe.jl index c5031af..473beda 100644 --- a/test/vault/test_observe.jl +++ b/test/vault/test_observe.jl @@ -106,8 +106,9 @@ end mod = Base.require(Main, Symbol(t.name)) origin = Base.pkgorigins[Base.PkgId(mod)] checkable = DataVault._cached_sources(origin.cachepath) !== nothing - @test config_root(record(t, observe_sources(t.vault)))["loaded"] == - (checkable ? "matches" : "unknown") + r0 = record(t, observe_sources(t.vault)) + @test config_root(r0)["loaded"] == (checkable ? "matches" : "unknown") + @test r0["binding"] == "unverified" # a match is recorded, never claimed # The loaded code stays as it was; the file on disk changes under it. write( joinpath(t.pkg, "src", "$(t.name).jl"), @@ -124,7 +125,9 @@ end @testset "binding_of: what an observation may claim" begin ok = Dict("config" => "matches", "pkg:A:1" => "matches") - @test DataVault.binding_of(ok, false, String[]) == ("loaded-matches-disk", String[]) + # Everything checkable matched, and still no match is claimed. + @test DataVault.binding_of(ok, false, String[]) == + ("unverified", [DataVault.NO_MATCH_CLAIMED]) @test DataVault.binding_of(Dict("config" => "differs"), false, String[])[1] == "loaded-differs-from-disk" for (status, revise, main) in (