Lesson 187: AI Red-Team Finding Bind to Mock Audit Deficiency Tickets (2026)
Direct answer: Lesson 180 stores LLM critiques in red_team_finding. Lesson 187 requires every promoted blocker/major finding to create or link a mock_audit_deficiency_ticket row with an allow-listed failure_mode_tag—partner intake replays in mid-2026 reject orphan PDF annexes that cite AI issues without ticket IDs.

Why this matters now (mid-2026 intake bind discipline)
Partners stopped accepting narrative-only AI annexes:
- PDF lists “model found tuple drift” with no
deficiency_ticket_id— fails replay next to Lesson 183 CSV exports. - Teams promote red-team text into FAQ rows but never open a ticket — Lesson 173 trend board shows zero recurrence while annex claims repeat issues.
failure_mode_tagfree text from model output breaks Lesson 172 scoring allow-list — ingest jobs reject the whole sprint feed.
The fix is mechanical: finding_id → ticket_id → tag with a signed bind manifest in the evidence bundle.
Lesson objectives
You will implement:
red_team_finding_deficiency_bind— many-to-one link with audit columnsfailure_mode_tagresolver fromsection_ref+ severity (allow-list only)forbid_promoted_finding_without_ticketapplication guardred_team_bind_manifest.jsonexport perpublish_tuple_hash- Publish-gate extension
red_team_unbound_promoted_finding - Optional auto-ticket stub for
promotedblockers after human sign-off
Prerequisites
- Lesson 180 —
red_team_finding,human_sign_off_promotion, human-only lane - Lesson 172 —
mock_audit_deficiency_ticket,failure_mode_tagallow-list - Lesson 183 — CSV export expects ticket IDs and tags
- Lesson 173 — trend board aggregates by
failure_mode_tag - Lesson 171 — active
publish_tuple_hashon bind rows
failure_mode_tag allow-list (reuse Lesson 172)
Store in governance/failure_mode_tag_registry.json:
{
"tags": [
"tuple_hash_mismatch",
"footer_schema_semver_drift",
"epsilon_policy_breach",
"sla_forecast_red",
"faq_unbound_redline",
"partner_reply_hash_drift",
"ai_red_team_unmapped"
]
}
Rule: model output never writes tags directly. A bind resolver maps (section_ref, severity) → tag. Unknown pairs map to ai_red_team_unmapped and block publish until a human picks a registered tag.
red_team_finding_deficiency_bind
CREATE TABLE red_team_finding_deficiency_bind (
bind_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
finding_id UUID NOT NULL REFERENCES red_team_finding(finding_id),
deficiency_ticket_id UUID NOT NULL REFERENCES mock_audit_deficiency_ticket(deficiency_ticket_id),
failure_mode_tag TEXT NOT NULL,
publish_tuple_hash TEXT NOT NULL,
bound_by_email TEXT NOT NULL,
bound_at_utc TIMESTAMPTZ NOT NULL DEFAULT now(),
bind_manifest_sha256 TEXT,
UNIQUE (finding_id),
CHECK (failure_mode_tag <> '')
);
CREATE OR REPLACE FUNCTION enforce_failure_mode_tag_allowlist()
RETURNS TRIGGER AS $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM failure_mode_tag_registry r
WHERE r.tag = NEW.failure_mode_tag
) THEN
RAISE EXCEPTION 'failure_mode_tag not in allow-list: %', NEW.failure_mode_tag;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Bind workflow (after human promotion)
| Step | Owner | Action |
|---|---|---|
| 1 | Governance owner | Confirm promotion_status = 'promoted' on finding |
| 2 | Scribe | Create or select mock_audit_deficiency_ticket for same cert_window_id |
| 3 | Engineering | Run resolver → propose failure_mode_tag; human confirms if ai_red_team_unmapped |
| 4 | Governance owner | Insert red_team_finding_deficiency_bind |
| 5 | Engineering | Regenerate red_team_bind_manifest.json; attach to packet |
def bind_finding_to_ticket(
finding_id: str,
ticket_id: str,
tag: str,
tuple_hash: str,
actor_email: str,
) -> None:
assert tag in load_allowlist()
assert finding_promoted(finding_id)
insert_bind(finding_id, ticket_id, tag, tuple_hash, actor_email)
manifest = build_bind_manifest(tuple_hash)
write_evidence(manifest)
Forbid orphan promoted findings
-- Publish gate predicate (Lesson 171 extension)
-- block_reason = 'red_team_unbound_promoted_finding'
WHEN EXISTS (
SELECT 1 FROM red_team_finding f
JOIN governance_packet_red_team_run r USING (red_team_run_id)
LEFT JOIN red_team_finding_deficiency_bind b USING (finding_id)
WHERE r.publish_tuple_hash = :active_tuple
AND f.promotion_status = 'promoted'
AND f.severity IN ('blocker', 'major')
AND b.bind_id IS NULL
);
Clears only when every promoted blocker/major has a bind row.
red_team_bind_manifest.json
{
"schema": "red_team_bind_manifest_v1",
"publish_tuple_hash": "c4e8…",
"cert_window_id": "q1_2027_meta",
"binds": [
{
"finding_id": "8f2a…",
"deficiency_ticket_id": "91bc…",
"failure_mode_tag": "tuple_hash_mismatch",
"section_ref": "annex.partner_sla_table",
"bound_at_utc": "2026-11-03T18:22:00Z"
}
],
"manifest_sha256": "…"
}
No PDF-only AI narrative in partner packets without matching binds[] entry.
Pairing with Lesson 183 CSV export
Export column source_red_team_finding_id optional on deficiency_export_v1:
deficiency_ticket_id,failure_mode_tag,source_red_team_finding_id,publish_tuple_hash
91bc…,tuple_hash_mismatch,8f2a…,c4e8…
Lesson 173 board then shows AI-sourced deficiencies beside tabletop-scored rows—same tag vocabulary.
Common mistakes
- Pasting model text into annex PDF without bind row — intake replay failure.
- Letting LLM choose
failure_mode_tagstrings — allow-list CHECK rejects ingest. - Binding before
promoted— ticket exists but gate still red onpending_human. - Duplicate ticket per finding —
UNIQUE (finding_id)prevents double bind; split findings instead. - Orphan tickets with no
finding_id— allowed for tabletop-only issues; do not require reverse bind.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Gate red_team_unbound_promoted_finding |
Missing bind on major | Insert bind or demote finding |
| CSV export rejects row | Free-text tag | Pick registry tag |
| Partner asks for PDF proof | Manifest not attached | Export red_team_bind_manifest.json |
| Duplicate bind error | Re-bind same finding | Update ticket link via new finding row |
Trend board spike on ai_red_team_unmapped |
Resolver gaps | Add mapping row to registry + resolver table |
Verification checklist
- [ ] Promoted blocker without bind blocks publish
- [ ] Unknown tag fails INSERT on bind table
- [ ] Manifest SHA-256 in CI matches regenerated file
- [ ] Lesson 183 export includes
source_red_team_finding_idwhen bound - [ ] Rejected findings do not require binds
Mini exercise (30 minutes)
Promote one major finding from a Lesson 180 dry run. Create deficiency ticket with tag faq_unbound_redline. Insert bind. Export manifest. Deliberately omit bind on second promoted finding—confirm publish gate fires.
Continuity
- Lesson 180 — red-team run and human promotion lane.
- Lesson 172 — ticket schema and tabletop scoring.
- Lesson 183 — machine-readable export for sprint feed.
- Lesson 173 — recurrence uses shared
failure_mode_tag. - Lesson 186 — panel roster quorum separate from AI bind path.
- Next: Lesson 188 — sprint hardening closure receipt vs Lesson 173 variance gate.
FAQ
Do minor/info findings need binds?
No—only promoted blocker/major per publish gate.
Can one ticket bind multiple findings?
Yes—multiple findings, one ticket: use separate bind rows sharing deficiency_ticket_id (drop UNIQUE on ticket side only if product requires; default one finding per ticket).
What about PDF annexes already shipped?
Issue correction packet with manifest + new binds; do not edit historical PDF hashes silently.
Does this replace human tabletop scoring?
No. Tabletop creates tickets; red-team adds or links tickets for AI-discovered gaps.
Mid-2026 partners trust AI assistance when every critique has a ticket ID and a tag they can replay. Bind findings, export the manifest, and retire orphan PDFs.