Project: Covert Cubicles Build Phase 6 / 7 \u00B7 6 of 6 modules \u2014 PHASE 6 CLOSE

MODULE 25 / 31 \u00B7 LAYER 6 \u00B7 DATA_ACCUSATIONOUTCOME \u00B7 FINAL PHASE 6 Per-Session Accusation Outcome Classifier

The simplest Phase 6 aggregator and the final one. Reads the accusation event recorded by M29 END_Accuse (Phase 7) and classifies the outcome as correct / wrong / abstained / no_accusation / malformed. Closes Phase 6 (Research Capture) and brings the build to 25 of 31 modules.

DEPENDENCIES: SM_Game, DATA_Peers
Pending
Phase 6 closure
M29 ACCUSATION-FIELD CONTRACT \u00B7 ADR-077
M25 establishes the shape M29 END_Accuse must populate.
M25 reads from state.session.accusation. M29 (Phase 7, not yet built) will be responsible for writing this field at end-game when the participant accuses a peer or abstains. M25 establishes the contract now, before M29 exists, so M29's interface is constrained by the documented schema. Until M29 is built, M25 always reports no_accusation; the test harness below uses stub-injection to drive M25 through all five outcome states.
state.session.accusation = {
  accusedPeer: 'arthur' | 'liam' | 'sarah' | 'dave' | null,
  abstained:   boolean,            // true iff explicit abstention
  confidence:  number | null,      // 0..1 if provided, otherwise null
  timestamp:   ISO 8601 UTC string
} | null   // null/undefined = no_accusation
Stub-injection harness \u2014 M29 not yet built
Until M29 END_Accuse exists in Phase 7, no real flow can populate the accusation field. The test harness below temporarily overrides SM.getSession() to return a session with the desired accusation shape, exercises M25 against it, then restores the original. This is the same technique used in the smoke test (17/17 PASS) and is what allowed M25 to be verified in advance of M29's existence.

Automated Assertions

#AssertionResultDetail

Interactive Workbench — Stub-Injection Harness

Click a pre-built scenario to drive M25 through one of the five outcome states, or use the manual stub controls to construct an arbitrary accusation field shape. The outcome banner updates immediately. Reset clears the stub and returns M25 to its real (no-accusation) state.

Stub-injection controls
stub modenone (real getSession)
insider
accusation
Live aggregate viewer \u2014 computeAccusationOutcome()
outcome
no_accusation
reason
outcomeVersion
computedAt
accusedPeer
insiderAssigned
match (accused vs insider)
abstainedfalse
confidence
accusationTimestamp
hasAccusationfalse
Output schema v1.0 \u2014 ADR-078 (taxonomy) + ADR-077 (contract)
FieldTypeDescription
Provenance
outcomeVersionstringSchema version (1.0)
computedAtISO 8601 UTCWhen the outcome was computed
Outcome (the headline classification)
outcomeenumOne of: correct / wrong / abstained / no_accusation / malformed
reasonstringDetail flag explaining how the outcome was reached
Surfaced from accusation field
accusedPeerstring | nullPeer ID accused (null on abstention / no-accusation / malformed-null)
insiderAssignedstring | nullActual insider for this session
abstainedbooleanTRUE if explicit abstention recorded
confidencenumber | nullOptional 0..1 confidence rating
accusationTimestampISO 8601 | nullWhen accusation was recorded
hasAccusationbooleanTRUE iff accusation field is populated (any form)
Outcome taxonomy (ADR-078) \u2014 5 mutually exclusive values
correctenum valueaccusedPeer === insiderAssigned
wrongenum valueaccusedPeer set but !== insiderAssigned
abstainedenum valueaccusation.abstained === true (precedence over accusedPeer)
no_accusationenum valueaccusation field absent
malformedenum valueField present but invalid (wrong shape OR accusedPeer not in peer list)
M29 contract (ADR-077) \u2014 future Phase 7 module
state.session.accusationobject | nullField path M29 END_Accuse must populate
accusation.accusedPeerstring | nullOne of arthur/liam/sarah/dave
accusation.abstainedbooleanTRUE iff explicit abstention
accusation.confidencenumber | null0..1 optional
accusation.timestampISO 8601 UTCWhen recorded

API Surface — Manual Verification