fix(auto-dispatch): widen operational verification gate regex (fixes #2866) (#2898)

Three defects in the completing-milestone dispatch guard caused false
positive blocks on valid validation output:

1. Single-line constraint: [^\n]* stopped at newlines, missing verdicts
   on subsequent lines. Fixed with [\s\S]{0,500}? (bounded lazy match).

2. Missing keywords: 'satisfied' and 'partially' were absent from the
   alternation. LLMs commonly write 'PARTIALLY SATISFIED' or 'FULLY
   SATISFIED'. Added both.

3. Markdown bold delimiters: **Operational** blocked [\s:] after the
   word. The new [\s\S] class handles any character including *.

Also adds SATISFIED to the structuredMatch includes check, and  to
the prose regex (overlaps with #2862).

Includes 8 regression test cases covering multi-line formats, satisfied
keyword variants, markdown bold tables, and checkmark emoji.
This commit is contained in:
NilsR0711 2026-03-28 01:10:10 +01:00 committed by GitHub
parent 558b1f2081
commit 79da90edde
2 changed files with 47 additions and 5 deletions

View file

@ -677,13 +677,13 @@ export const DISPATCH_RULES: DispatchRule[] = [
if (validationPath) {
const validationContent = await loadFile(validationPath);
if (validationContent) {
// Accept either the structured template format (table with MET/N/A)
// Accept either the structured template format (table with MET/N/A/SATISFIED)
// or prose evidence patterns the validation agent may emit.
const structuredMatch =
validationContent.includes("Operational") &&
(validationContent.includes("MET") || validationContent.includes("N/A"));
(validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED"));
const proseMatch =
/[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test(validationContent);
/[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test(validationContent);
const hasOperationalCheck = structuredMatch || proseMatch;
if (!hasOperationalCheck) {
return {

View file

@ -23,9 +23,9 @@ import assert from "node:assert/strict";
function hasOperationalEvidence(validationContent: string): boolean {
const structuredMatch =
validationContent.includes("Operational") &&
(validationContent.includes("MET") || validationContent.includes("N/A"));
(validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED"));
const proseMatch =
/[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test(
/[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test(
validationContent,
);
return structuredMatch || proseMatch;
@ -104,6 +104,48 @@ test('prose: "Operational: complete" passes', () => {
assert.ok(hasOperationalEvidence(content));
});
// ─── Issue #2862: checkmark emoji ────────────────────────────────────────────
test('prose: "Operational: ✅" checkmark emoji passes (issue #2862)', () => {
const content = `- **Operational:** ✅ DECISIONS.md documents D009-D013`;
assert.ok(hasOperationalEvidence(content));
});
// ─── Issue #2866: multi-line, "satisfied", markdown bold ─────────────────────
test('multi-line: verdict on next line after Operational heading passes (issue #2866)', () => {
const content = `### Operational Verification
All endpoints responsive. Health checks pass.`;
assert.ok(hasOperationalEvidence(content));
});
test('prose: "PARTIALLY SATISFIED" passes (issue #2866)', () => {
const content = `Operational class: ⚠️ PARTIALLY SATISFIED`;
assert.ok(hasOperationalEvidence(content));
});
test('prose: "FULLY SATISFIED" passes (issue #2866)', () => {
const content = `**Operational**: FULLY SATISFIED — all monitoring in place.`;
assert.ok(hasOperationalEvidence(content));
});
test('structured: Operational + SATISFIED passes (issue #2866)', () => {
const content = `| Criteria | Status |
| Operational | SATISFIED |`;
assert.ok(hasOperationalEvidence(content));
});
test('table with markdown bold: **Operational** passes (issue #2866)', () => {
const content = `| **Operational** | ⚠️ Partially satisfied — monitoring gap noted |`;
assert.ok(hasOperationalEvidence(content));
});
test('multi-line: Operational label and "confirmed" separated by line break passes (issue #2866)', () => {
const content = `## Operational
Smoke tests confirmed all services healthy after deploy.`;
assert.ok(hasOperationalEvidence(content));
});
// ─── Rejection cases ─────────────────────────────────────────────────────────
test("no operational evidence: unrelated content fails", () => {