Repository navigation
A verdict taught one path or one hash, so the operator's rebuilds were strangers forever - #58
Open
opencdlee-dotcom wants to merge 4 commits into
Open
opencdlee-dotcom wants to merge 4 commits into
opencdlee-dotcom wants to merge 4 commits into
Conversation
… where it was The live queue was 27 open incidents. Eighteen were "Suspicious running process", and the operator had already ruled benign-positive on several of the exact binaries underneath them -- three separate times on one uv-managed CPython alone. None of those verdicts counted. A process verdict accumulates under `process:<path>:<trust>`, and this machine reproduces one binary into venvs, uv build tmpdirs, pipx envs, per-agent worktrees, staging and release dirs, DMG scratch mounts and Downloads. So 30 verdicts spread over 28 identities, every one below the floor of three: the process sensor held zero tolerance identities on the reference Mac. Trust is churn of its own -- the same uv binary graded 'broken' when it was dismissed and 'adhoc' when it reopened as #514. Content becomes a SECOND identity a verdict accumulates under, never a replacement. A vendor app updating in place has a stable path and churning bytes; the operator's own build output does the exact reverse, so dropping either identity strands one of those populations forever. It is strictly narrower than the identity it joins -- it pins the exact bytes, so replacing a vouched binary mints an identity that has earned nothing -- and every existing guard still applies. Old verdicts carry forward on their own, recovered from the pre-custody `process:<path>:<trust>:<sha>` key shape: no migration, no one-time closer. Measured on the live store after triage: 0 process identities learned before, 3 after. The same pass closes a hole the content-keyed key opened. `process:sha:<sha>` fed to the hash-stripper yields `process:sha` -- two components naming no subject, one bucket shared by every content-keyed process incident on the machine, so three benign-positive verdicts on three UNRELATED binaries would have tolerized the sensor outright. A stripped identity must still name a subject, which is three components. Beacon subjects now carry their content hash too (free -- _graded_sha is already memoised per scan because a process, its listener and its beacon all ask for it). That does NOT qualify the path-keyed beacon identity, which _tolerance_identity still refuses for a never-normalized path; the bytes get their own identity instead. Separately: an incident's evidence list reprinted the finding TITLE once per row, and the title is constant by construction -- it is part of what groups them. #505 printed "Suspicious running process" twenty times while eight distinct interpreter paths sat in the stored events. Rows now fold on the per-observation descriptor and show what differs. Triage of the queue this came from: 23 benign-positive (the operator's own bioREADr/Zotero builds, RNAfold Deck, Playwright's Firefox, uv/Homebrew toolchain, and five Zotero sync beacons to AWS us-east-1), 2 false-positive (#452 read a locally-built bundle's absent quarantine flag as "side-loaded, bypassed Gatekeeper"; #521 reported a PATH-resolution change between two different npm binaries as a contents change, on a file unmodified since Aug 12). #450 and #503 were deliberately left open: their stored preview shows only the shell prologue, so the matched idioms are not visible and they cannot honestly be adjudicated. Verified: 1965 passed / 0 failed locally; simbody diff clean against the merge base on both the win and linux legs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ytes' into agent/precision-s6/integration
…e strangers forever 72 hand verdicts across 45 classes on the live store, then 109 new incidents in a class the operator had already judged. Tolerance was keyed on process:<path>:<trust> and, since #51, on the exact bytes; every rebuild, new worktree, runner self-update and translocated copy mints both anew. The custody ladder already knows more about a binary than where it sits, and none of it reached the layer that learns. A verdict now also teaches the CLASSES the ladder verified, each spelled by one function, _finding_classes, which both the memory builder and _signal_decision call: signer:<team> publisher verdict + team id 1 verdict package:<manager>:<name> package-managed + receipt 1 verdict buildrepo:<repo root> build-output + self-committed 3 verdicts supervisor:<parent sha> supervised 3 verdicts Consulted after the exact and content identities. Every guard stands: never CRITICAL, never attack-defined, never above the reviewed severity, never a _NEVER_TOLERATE_PREFIXES fingerprint, and a dispute on any incident in a class revokes the class. Two refusals are new and class-specific: only process and net-beacon findings (a finding about a binary) carry a class, and an interpreter or script host carries none -- a signed python or pwsh is whatever script it runs. The facts a class is read from (team, authority, package, build_repo, supervisor) are attached at emission by _class_facts, from answers the scan already holds; _reobserve attaches them the same way so the replay can score them. `incident <id> benign-positive` and `family` print what the verdict taught, one line per class, and a class crossing its floor is written to actions.jsonl; `families` shows what each family would teach; the replay's teaching line counts classes taught. Measured, and the honest part: no finding in the live store carries these facts yet (8LAYR367YV is in sigcache and on btm events, never on a process or beacon finding), so the live store teaches 0 classes today and the replay's corpus numbers do not move. Classes accumulate from the first verdicts given after install. Includes #51 (tolerance follows bytes), merged without conflict. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#51 gave a binary's bytes their own tolerance identity, and left it at the floor of three that path identities keep. That floor exists because a path- or class-shaped identity reaches bytes nobody reviewed. An exact-bytes identity reaches nothing the operator did not judge, so asking three times about one fact was the teaching-evaporates defect in its purest form. The content identity (process:content:<sha>, beacon:content:<sha>:<ip>:<port>) now tolerates on ONE verdict. The floors are one table, _TOLERANCE_FLOOR: exact bytes = 1, signer / package = 1, build repo / supervisor / producer = 3, path identities = 3. Every other guard stands: never CRITICAL, never attack-defined, never above the reviewed severity, never _NEVER_TOLERATE_PREFIXES, and a dispute on any incident holding those bytes revokes it (_disputed_identities already carried the content identity). _tolerance_memory is split into _tolerance_verdicts (buckets with a width) and the floor it applies, so the verdict lesson counts what the decision sees. `incident <id> benign-positive` now prints "Learned: these exact bytes (sha256 1a2b3c4d…) — anywhere (1 verdict, tolerates now)", and `families` previews it. The class width named "content" in the previous commit is renamed "derived", so "content" means only the exact bytes. #51's TestVerdictsConvergeAcrossPaths pinned three for content and is rewritten for one; test_two_verdicts_are_not_enough became test_the_path_identity_still_needs_three. Measured on the live store, same snapshot, backtest replay --reobserve: tolerated identities 10 -> 46, process interrupts 23 -> 6, noise re-opened 125 -> 112 of 214 (benign-positive 35 -> 22 of 56), open cases 131 -> 116, assay recall 9/21 unchanged, new interrupts from corpus 13 -> 13 (same list). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
What this fixes
On the live store, 72 hand verdicts across 45 classes were followed by 109 new incidents in classes the operator had already judged. Tolerance was keyed on
process:<path>:<trust>, plus the exact bytes since #51. Every rebuild, new worktree, runner self-update and translocated copy produces a new path and new bytes, so each one arrived as a stranger. The custody ladder already knows more about a binary than where it sits, but none of that reached the layer that learns.The change (plan step S6, Phase 3)
A benign-positive verdict now also teaches the classes the ladder has verified. One function,
_finding_classes(f), spells every class key, and both the memory builder (_class_memoryvia_incident_classes) and_signal_decisioncall it:signer:<team>publisher_sig(trust)and a 10-char team idpackage:<manager>:<name>package-managed+ receipt (version dropped)buildrepo:<repo root>build-output, git said self-committedsupervisor:<parent sha>supervised_signal_decisionchecks classes after the exact and content identities. Every existing guard still applies: never CRITICAL, never attack-defined, never above the reviewed severity, never a_NEVER_TOLERATE_PREFIXESfingerprint._disputed_identitiesadds its class keys.process,net-beacon) carry a class. A persistence job signed by team X can run anything X ships, with arguments nobody reviewed._INTERPRETERS, versioned names likepython3.13, and pwsh/cmd/wscript/java/deno/bun. A signed python or pwsh is whatever script it runs; this is the S3 chain-leg rule applied to the widest join there is._class_factsattachesteam/authority/package/build_repo/supervisor(_path)at emission, from answers the scan already holds. It runs incheck_processesand both_beacon_from_sightingsemitters._supervised_rungis split into_supervising_parent, which returns which parent was vouched._reobserveattaches the same facts, so the replay can score classes.incident <id> benign-positiveandfamily … benign-positiveprint one line per class, e.g.Learned: anything signed by team 8LAYR367YV (Corporation for Digital Scholarship) — wherever it runs (1 verdict, tolerates now)orLearned: build output of <repo> (1 of 3 verdicts).class-tolerance-grantedinactions.jsonl.familiesandfamily <n>show awould teach:line.false-positiveteaches nothing._suppression_memory, and the teaching line printsN class(es) taught.tolerance-follows-bytes) first, with no conflict.Measured, including what did not move
python3 aegis.py backtest replay --days 30 --reobserve, on the live store, read-only:Why 0 classes are taught: no finding in the live store carries the class facts yet. For example,
8LAYR367YVappears insigcache.jsonand onbtmevents, but never on a process or beacon finding. The brief assumed it did.Backfill not done: I measured what a sha-bound backfill from disk would recover for the existing verdicts. It came to 2 classes:
buildrepo:…/codex-apple-integrate(#501, #502, still below its floor of 3) andpackage:uv-python:cpython-3.13(#505, which the content identity from #51 already covers). The signer classes the verdicts imply cannot be bound safely:claudebinaries they name are gone from disk.Deriving a team from a path without binding it to bytes would let one replaced file teach a whole publisher, at a floor of 1. So classes start accumulating with the first verdicts given after install. The corpus numbers above are unchanged, and assay recall did not drop.
Cost:
_suppression_memorygoes from ~75 ms to ~80–110 ms CPU per scan on the live store._incident_classesreads only the newest observation per signal.Verification
tests/test_class_tolerance.py: 43 tests, all pass. They cover:_finding_classesspells a class key. I confirmed separately that the mutation test goes red when either the memory side or the decision side bypasses_finding_classes.tests/test_tolerance_follows_bytes.py(Thirty verdicts on one binary taught nothing, because tolerance asked where it was #51): pass (66 together with the above).pytest tests/ -k "toleran or identity or famil or dismiss": 242 passed.2220 passed, 6 skipped, 30 xfailed, 41 subtests passed in 1576.00s, run on the committed tree (sha-checked).integration..HEAD: no leaks. Every author is the noreply identity.conftest.PUBLISHER_TRUST/SUSPICIOUS_TRUST, not macOS verdict literals.Follow-up (fe8f9ad): exact bytes tolerate on one verdict
The verifier decided that #51's content identity tolerates on one verdict, not three:
process:content:<sha>beacon:content:<sha>:<ip>:<port>An exact-bytes identity reaches nothing the operator did not judge. Asking three times about one fact was the teaching-evaporates defect in its purest form.
One table,
_TOLERANCE_FLOOR:_PRODUCER_MIN_SIBLINGSis now read from it.Guards unchanged:
_NEVER_TOLERATE_PREFIXES_disputed_identitiesalready carried it)Other changes:
_tolerance_memoryis split into_tolerance_verdicts(buckets, each with a width) plus the floor applied to them. The verdict lesson now counts what the decision sees.incident <id> benign-positiveprintsLearned: these exact bytes (sha256 1a2b3c4d…) — anywhere (1 verdict, tolerates now), andfamiliespreviews it.contentin the first commit is renamedderived, so "content" now means only the exact bytes.#51 tests changed (
tests/test_tolerance_follows_bytes.py::TestVerdictsConvergeAcrossPaths, which pinned three for content):_teach_three_pathsbecame_teach_one.test_three_paths_one_binary_reaches_the_floorbecametest_one_verdict_reaches_the_floor.test_two_verdicts_are_not_enoughbecametest_the_path_identity_still_needs_three. The path identity still needs three: two verdicts on two builds at one path do not cover a third.test_the_copies_at_the_other_paths_open_pre_closed.test_the_next_sighting_at_a_fourth_path_is_tolerated,test_different_bytes_at_a_taught_path_still_alert,test_false_positive_verdicts_teach_nothing_hereandtest_critical_is_never_toleratednow teach one verdict.test_a_dispute_reaches_the_content_identitykeeps its INVESTIGATING dispute. It now opens the disputed copy before the single verdict.New tests (
tests/test_class_tolerance.py::ExactBytesTolerateAtOneVerdict):Learned:lineMeasured — live store, one snapshot,
backtest replay --days 30 --reobservedf4c8d4andfe8f9adwere replayed against the same store state:The corpus grew between my first measurement (62,647 events) and this one (62,707), so the first-commit column here is a fresh run on the new snapshot.
Verification (follow-up)
tests/test_class_tolerance.py+tests/test_tolerance_follows_bytes.py: 76 passed.pytest tests/ -k "toleran or identity or famil or dismiss": 252 passed.2230 passed, 6 skipped, 30 xfailed, 41 subtests passed in 908.96s. The tree sha was checked before and after the run.~/.aegis/custody.jsonlheld 151 lines before the suite and 151 after. S7's conftest guard is not on this base.🤖 Generated with Claude Code