Skip to content

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
agent/fable-precision/integrationfrom
agent/precision-s6/integration
Open

opencdlee-dotcom wants to merge 4 commits into
agent/fable-precision/integrationfrom
agent/precision-s6/integration

Conversation

@opencdlee-dotcom

@opencdlee-dotcom opencdlee-dotcom commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

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_memory via _incident_classes) and _signal_decision call it:

class when width floor
signer:<team> publisher_sig(trust) and a 10-char team id anchored 1
package:<manager>:<name> custody package-managed + receipt (version dropped) anchored 1
buildrepo:<repo root> custody build-output, git said self-committed content 3
supervisor:<parent sha> custody supervised content 3
  • _signal_decision checks 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_PREFIXES fingerprint.
  • Disputes: a dispute on any incident in a class revokes the whole class. _disputed_identities adds its class keys.
  • Two new refusals, class-specific (my call, not in the brief):
    • Only findings about a binary (process, net-beacon) carry a class. A persistence job signed by team X can run anything X ships, with arguments nobody reviewed.
    • An interpreter or script host carries no class. This covers _INTERPRETERS, versioned names like python3.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.
  • Where the facts come from: _class_facts attaches team/authority/package/build_repo/supervisor(_path) at emission, from answers the scan already holds. It runs in check_processes and both _beacon_from_sightings emitters. _supervised_rung is split into _supervising_parent, which returns which parent was vouched. _reobserve attaches the same facts, so the replay can score classes.
  • Visibility:
    • incident <id> benign-positive and family … benign-positive print one line per class, e.g. Learned: anything signed by team 8LAYR367YV (Corporation for Digital Scholarship) — wherever it runs (1 verdict, tolerates now) or Learned: build output of <repo> (1 of 3 verdicts).
    • A class crossing its floor is logged as class-tolerance-granted in actions.jsonl.
    • families and family <n> show a would teach: line.
    • false-positive teaches nothing.
  • Replay: class memory is the 6th element of _suppression_memory, and the teaching line prints N class(es) taught.
  • Merged Thirty verdicts on one binary taught nothing, because tolerance asked where it was #51 (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:

integration (brief) integration + #51 this PR
process interrupt 24 23 23
noise re-opened 126/213 124/213 124/213
— benign-positive 37/56 35/56 35/56
cases left OPEN 131 130 130
assay recall 9/21 9/21 9/21
classes taught — — 0

Why 0 classes are taught: no finding in the live store carries the class facts yet. For example, 8LAYR367YV appears in sigcache.json and on btm events, 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) and package:uv-python:cpython-3.13 (#505, which the content identity from #51 already covers). The signer classes the verdicts imply cannot be bound safely:

  • the Zotero beacons were recorded with no content hash;
  • the Anthropic claude binaries 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_memory goes from ~75 ms to ~80–110 ms CPU per scan on the live store. _incident_classes reads only the newest observation per signal.

Verification

  • tests/test_class_tolerance.py: 43 tests, all pass. They cover:
    • signer at a floor of 1 on a different path
    • adhoc → no class
    • buildrepo 2 verdicts not enough, 3 enough
    • reopen revokes the class for the rest
    • CRITICAL and attack-defined never tolerated by a class
    • reviewed-severity cap
    • interpreters refused
    • lesson text, action log, families preview, replay teaching line
    • single-source mutation test plus an AST check that only _finding_classes spells 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.
  • Full suite: 2220 passed, 6 skipped, 30 xfailed, 41 subtests passed in 1576.00s, run on the committed tree (sha-checked).
  • gitleaks over integration..HEAD: no leaks. Every author is the noreply identity.
  • Not run: the simbody win/linux diff. The new tests use 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:

identity verdicts needed
exact bytes 1
signer / package 1
build repo / supervisor / producer 3
path identities 3

_PRODUCER_MIN_SIBLINGS is now read from it.

Guards unchanged:

  • never CRITICAL
  • never attack-defined
  • never above the reviewed severity
  • never _NEVER_TOLERATE_PREFIXES
  • a dispute on any incident holding those bytes revokes the identity (_disputed_identities already carried it)

Other changes:

  • _tolerance_memory is 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-positive prints Learned: these exact bytes (sha256 1a2b3c4d…) — anywhere (1 verdict, tolerates now), and families previews it.
  • The class width named content in the first commit is renamed derived, 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_paths became _teach_one.
  • test_three_paths_one_binary_reaches_the_floor became test_one_verdict_reaches_the_floor.
  • test_two_verdicts_are_not_enough became test_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.
  • New: 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_here and test_critical_is_never_tolerated now teach one verdict.
  • test_a_dispute_reaches_the_content_identity keeps its INVESTIGATING dispute. It now opens the disputed copy before the single verdict.

New tests (tests/test_class_tolerance.py::ExactBytesTolerateAtOneVerdict):

  • one verdict tolerates the same bytes at another path
  • never above the reviewed severity
  • different sha at the same path not covered
  • CRITICAL never tolerated
  • reopening a tolerated copy revokes the identity
  • reopening the judged incident revokes the identity
  • the path identity keeps three
  • the Learned: line
  • the floor table

Measured — live store, one snapshot, backtest replay --days 30 --reobserve

df4c8d4 and fe8f9ad were replayed against the same store state:

df4c8d4 (class tolerance) fe8f9ad (exact bytes at 1)
tolerated identities 10 46
process interrupt 23 6
total interrupts — 112
noise re-opened 125/214 112/214
— benign-positive 35/56 22/56
cases left OPEN 131 116
new interrupts from corpus 13 13 (identical list)
assay recall 9/21 9/21

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.
  • Full suite: 2230 passed, 6 skipped, 30 xfailed, 41 subtests passed in 908.96s. The tree sha was checked before and after the run.
  • ~/.aegis/custody.jsonl held 151 lines before the suite and 151 after. S7's conftest guard is not on this base.
  • gitleaks over the pushed range found no leaks.

🤖 Generated with Claude Code

opencdlee-dotcom and others added 4 commits September 20, 2026 06:03
… 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>
…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>
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