Home/Concepts/Testimony and epistemic dependence in software engineering teams
Testimony and epistemic dependence in software engineering teams
Every knower larger than its own senses depends on testimony. That is not a limitation of machines but of any finite observer, human included. Given dependence, only three…
The testimony a codebase runs on
No engineer perceives most of the system they are responsible for. They did not watch the dependency get published, did not read the commit that fixed the null check three services upstream, did not observe the CVE get assigned. They inherit all of it as testimony: someone else's claim, someone else's timestamp, someone else's assertion that a build passed. Epistemic dependence is not a special condition of large teams. It is the baseline condition of anyone who did not personally write every line running in production, which is to say everyone.
The question is not whether a software engineering team depends on testimony. It is what shape that dependence takes: a corpus frozen at some past moment, a dashboard showing only what is live right now, or a standing set of beliefs that keeps updating as informants keep talking.
What arrives
Four kinds of testimony land continuously. Commit streams report what changed, by whom, and supposedly why, though the "why" is only as good as the message the author bothered to write. CI results report whether a build and its tests passed, at a specific commit hash, under specific runner conditions that themselves drift — a flaky test today is not the same informant as the same test three weeks ago on a different base image. Incident feeds report what broke, when, and what the on-call engineer believed the cause to be at 3 a.m., which is testimony from an exhausted witness under time pressure and revised later once the postmortem lands. Dependency advisories report that some package, somewhere in the graph, has a newly disclosed weakness — testimony from a maintainer, a security researcher, or an automated scanner that may itself be wrong.
None of these four streams is perception. The team does not see the vulnerability; it sees a CVE number, a severity score assigned by someone else's rubric, and a package name that may or may not resolve to a dependency actually reachable at runtime. Everything is inherited assertion, arriving continuously, from sources with names, timestamps and track records.
What is held
The naive architecture treats each of these as an event to react to and then discard: a Slack alert fires, someone triages it, the thread scrolls away. That is a corpus with a very short half-life rather than a standing epistemic state. The alternative is to hold a belief, per dependency, per service, with attributes that persist: last known version, last audit date, source of that audit (automated scan, manual review, upstream advisory), confidence, and a decay function. A dependency last checked eighteen months ago by an automated scanner that itself had a known false-negative rate is a different epistemic object from one checked last week by a human reviewer cross-referencing three advisory databases. A frozen list of "approved dependencies" cannot represent that difference. It has one bit — approved or not — where the actual epistemic state has a history.
This is where the characteristic failure of the domain lives. A transitive dependency — not the package the team chose, but a dependency of a dependency three layers down, pulled in by a logging library nobody on the team has looked at directly — carries a vulnerability. It shipped in a release six weeks ago. The team's last full audit was two months before that. Nobody lied. Nobody perceived anything false. The corpus of "known good dependencies" was simply not updated between audits, and the belief that the dependency tree was clean quietly went stale without anyone noticing it had gone stale, because staleness was never represented as a first-class property of the belief. It sat there as an unmarked assumption instead of a decaying claim with a visible age.
What triggers revision
Revision has to be triggered by disagreement between informants, not by the arrival of any single claim taken at face value. A new advisory naming a package used somewhere in the dependency graph is one informant. Confirmation that the vulnerable code path is actually reachable at runtime — via static analysis of the call graph, or dynamic tracing in a staging environment — is a second, independent informant. A patched version being available and passing the existing test suite is a third. Only when at least two of these align does the belief "this dependency is currently a live risk" get upgraded from possible to actionable. A single advisory with no reachability confirmation should raise the flag but not itself trigger a production rollback; that would be trusting one witness because it spoke first. Waiting for perfect confirmation before acting at all is the opposite failure, and it is exactly how six weeks pass. The trigger condition has to be calibrated between those two errors, and getting that calibration wrong is itself a decision someone has to own.
The same logic runs on the commit and CI streams. A test that fails once might be a flaky runner; a test that fails on three unrelated commits by three unrelated authors is testimony that has been corroborated by independent witnesses and now warrants a different kind of attention. The team is not perceiving that the test suite is unreliable. It is inferring it from a pattern in reports that individually proved nothing.
What the operator sees, and who answers for it
The staff engineer responsible for the dependency graph does not see the vulnerability directly. They see a belief state: a table of dependencies with ages, sources and confidence scores, sorted by decay. What they are accountable for is not catching every CVE the day it publishes — no team does that — but keeping the decay function honest, so that a two-month-old audit shows as two months old rather than as a green tick indistinguishable from yesterday's. When the transitive dependency incident is reconstructed afterward, the postmortem question is never "why didn't anyone know." It is "why did the belief still read as current when it was six weeks stale." That is a provenance failure, not a perception failure, and it is the staff engineer's name against the field that should have shown the staleness.
What it costs
Holding live, provenanced belief instead of either a frozen approved-list or a bare real-time dashboard costs continuous attention that a corpus never demands and a snapshot dashboard never rewards, because the dashboard only ever shows the present moment and forgets it was ever anything else. Someone has to maintain the reliability scores of the advisory sources themselves — some scanners run 20% false positive rates on transitive flags, and a belief system that treats every scanner as equally credible drowns the real signal in noise within a month. Someone has to decide the decay rate per artefact type: a lockfile can be re-verified automatically on every build almost for free, but a manual security review has a cost that makes short decay intervals impractical, and the belief state has to reflect that asymmetry honestly rather than pretending both kinds of check age the same way.
Two objections worth taking seriously
The reductionist objection lands squarely here: ingesting every advisory feed, every scanner output and every maintainer's disclosure gives the team no independent grounds to trust most of them. Volume of intake is not warrant. This is correct, and the answer is that warrant has to be built rather than assumed. A scanner that has flagged 4,000 packages over two years, of which 3,850 were later confirmed exploitable by independent review, has an earned track record; a brand-new scanner with no history has none, and a well-run belief system weights the two differently. That weighting is only possible because the chain stayed open long enough to accumulate a record. An approved-list frozen at last quarter's audit never had the chance to earn or lose that trust — it simply stopped listening before the evidence came in.
The adversarial-testimony objection is sharper in this domain than most. Dependency ecosystems are attacked directly: typosquatted package names, compromised maintainer accounts, advisories filed in bad faith to pressure a maintainer into a rushed release. An always-listening intake inherits every one of these along with the genuine signal, and the attack surface is the intake itself. This is a real cost, not a flaw in the framing. Openness is what makes poisoning possible, but it is also the only condition under which poisoning is detectable — a compromised package is caught by its behaviour disagreeing with everything else known about it, which requires there to be other things known about it to disagree with. A closed corpus does not avoid this problem; it just cannot see a compromise that happened after the freeze, which is worse, since the team keeps trusting a poisoned dependency indefinitely rather than for the weeks it takes cross-corroboration to catch it.
Neither objection is defeated. Both are absorbed as engineering obligations: score the sources, decay the beliefs, corroborate before acting. That is the actual discipline the third position demands, and it is why the position is terminal rather than merely fashionable — there is no fourth way to relate to a still-speaking informant beyond keeping the line open and writing down who is on it.