FLT-75 — replay the Allyson incident against the fixed pipeline¶
Add ONE new test to FounderIntake.Tests (new file AllysonIncidentReplayTests.cs, same harness
pattern as TrustHardeningTests.RealThreadReadyConfirmCreatesExactlyOneTicketAndReusesOriginalEnvelope
— real ConversationStore, ReplyOutbox, EgressSanitizer, RequestStateMachine, IdempotencyStore,
ConversationResponder; fake IJiraTicketCreator and a recording ICorrelatedThreadReplyWriter/outbox
consumer; no real Slack/Jira network calls) that reproduces the actual incident shape from
work-wingman-intake on 2026-07-22, driven through ONE shared ConversationResponder instance¶
(same as the real single-host deployment):
Scenario (three concurrent threads, one host, one batch)¶
- Thread A (
slack:C1:100.1): requester Allyson (Slack idU_ALLYSON, display name "Allyson"), message: "Please help me compare the benefits of amazon bedrock vs microsoft foundry for running an up and coming Ai based application" - Thread B (
slack:C1:200.1): requester Andrew (Slack idU_ANDREW, display name "Andrew"), message: "Test intake: can someone help me draft a one-pager for the pilot?" - Thread C (
slack:C1:300.1): requester Andrew again, SAME requester as B but a DIFFERENT thread, message: "Test intake #2: please help me outline the pilot one-pager."
All three EnqueueJob calls happen before any ProcessOneBatchAsync call, so the responder handles
them within the same batch/pass — this is what actually happened the night of the incident (multiple
threads in flight on one host at once).
Assertions (this is the acceptance test from the original council verdict)¶
- No misaddressing: the reply enqueued for thread A greets/addresses Allyson only — assert her reply text contains "Allyson" and does NOT contain "Andrew". The replies for threads B/C address Andrew only and do NOT contain "Allyson".
- No cross-thread contamination: assert the CONTENT of thread A's reply/writeup has zero overlap with B/C's subject matter (no mention of "one-pager", "pilot", draft outline) and vice versa — each reply is about only its own requester's own message.
- Exactly one ticket per confirmed thread, no duplicates: drive each thread through its own
READY_TO_FILE -> CONFIRM cycle (use a fake backend returning
READY_TO_FILE:immediately, matching the existingReadyBackendfake). After all three confirms, the fake Jira creator'sCreateCountmust be exactly 3 (one per thread), never more, even if a confirm is replayed/duplicated for one thread (add a duplicate confirm for thread A and assert the count is still 3). - No internal leak: none of the three replies' enqueued text may contain any of:
127.0.0.1,BOARD.md,[Jira FLT-,WT-followed by hex, or the harness names (Jenny,Cody,Clahadore,Gronktayvius). If you want to prove the sanitizer's role concretely, inject one thread's fake backend response with an internal string (e.g. append"\n[Jira FLT-3](https://workwingman.atlassian.net/browse/FLT-3)"to its READY_TO_FILE text) and assert that specific reply is HELD (not enqueued aspending) rather than delivered. - Single-writer sanity: run the whole scenario through TWO
IntakeLeaderLease-guarded responder instances pointed at the same DB/lease store (mirroringIntakeLeaderCoordinator), where only the leader is allowed to actually drain replies — assert the non-leader instance produces zero outbound sends.
Constraints¶
- C# only, xUnit, follow the existing style in TrustHardeningTests.cs (helper fakes already defined
there —
RecordingJira,ReadyBackend,GovernorClientusage — reuse them; only add what's missing). - No real network I/O anywhere in the test.
dotnet build FounderIntake.slnxanddotnet test FounderIntake.slnxmust be clean/green (including all pre-existing tests).- Do not commit. Leave changes uncommitted for review.
- If any assertion FAILS against the current code (i.e. the replay finds a real regression or gap), do NOT weaken the assertion to make it pass — report the failure exactly as found and stop; that is a real finding, not a test-authoring problem.
- End with: pass/fail per assertion, and the full
dotnet testoutput tail.