Skip to content

Defense-stance votes that fail no guard leave no defense_degraded record #178

Open

Description

Summary

#177 made the defense wave account for its losses: a stance entry failing an integrity guard is dropped and recorded in red_team.defense_degraded instead of aborting the run. Two entry shapes still evaporate without a record, because they never reach an integrity guard. Both are observability-only — no verdict, metric, or rendered finding is wrong today — and both were adjudicated as accepted pre-existing behavior in #177. This issue exists so that adjudication and the constraints on fixing it survive the PR.

Line references are against team-review.workflow.mjs as merged by #177.

Case 1 — withdrawn entry naming an unknown finding_id

A defender's withdrawal vote is tallied into withdrawVotes without resolving the id, and the tally is consulted only against c.kept (the c.kept.filter fold, ~line 2089). A withdrawal naming an id that resolves to no record casts a vote nothing ever reads, and nothing records that the defender produced it.

Constraint on the fix: withdrawal votes are consulted against kept only, so the naive fix — record every withdrawal whose id is absent from kept — misrecords a legitimate withdrawal of a finding currently sitting in contested (moved there earlier in the same fold, or contested since consensus) as an integrity drop. Resolution has to check kept and contested (and arguably the red-team new_findings ids) before classifying the entry as malformed.

Case 2 — single-vote unknown id in adopted_new / re_adopted

Adoption and re-adoption votes are tallied by castVote (~line 2062) without resolution; ids are resolved only when a promotion reaches the two-vote floor, where promote drops and records an unresolvable id via dropDefense (~line 2120). A single defender citing an unknown id therefore parks a vote that never reaches promote, and no defense_degraded entry is written — the same malformed-payload class #177 started recording, absorbed silently one vote below the floor.

Constraint on the fix: validating at castVote time needs resolveOriginal's inputs — in particular the red-team newFindingsByPath map — earlier than they are currently in scope (~line 2111, after the per-defender loop). That is a small restructuring, not a one-liner; an adversary-introduced id is legitimate in adopted_new and must not be classified malformed just because it lives in neither kept nor contested.

Suggested direction

Route both cases into the existing channel: resolve each vote's id at ingestion against the full identity space (kept, contested, withdrawn originals, red-team new findings), and on failure drop the entry via dropDefense with a guard name distinguishing it from the #177 cases. No new result fields; defense_degraded's shape already fits. The report render (references/report-format.md CAUTION block) needs no change.

Severity

Low. Diagnostics only: today a defender hallucinating ids one vote below the promotion floor is indistinguishable from a healthy defense wave. Nothing downstream is corrupted.

Related: #173 (the crash that established the drop-and-record path), #177 (where both gaps are documented under Limits).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions