From b8415971b88cc49ffac15488ee1cfa45bf5f1076 Mon Sep 17 00:00:00 2001 From: Aaron Stainback Date: Fri, 15 May 2026 15:38:18 -0400 Subject: [PATCH] feat(b-0535): wire backlog ID-uniqueness lint to gate.yml (--enforce-duplicate-ids) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Substrate-honest correction to B-0535's framing: the duplicate-ID detection logic ALREADY EXISTS in audit-backlog-items.ts (item 8, added 2026-05-14 via PR #3249 after Copilot caught B-0329 collision on PR #3247). The actual gap was CI wiring, not implementation. This PR adds: 1. CLI flag --enforce-duplicate-ids to audit-backlog-items.ts: - Detect mode unchanged (default, exits 0 with findings reported) - --enforce-duplicate-ids: exit 1 if duplicate-ID groups > 0 - Rejects unknown flags with stderr error + exit 1 2. New gate.yml job lint-backlog-id-uniqueness: - Same shape as lint-section-33-migration-xrefs + lint-archive-header-section33 - Runs `bun tools/hygiene/audit-backlog-items.ts --enforce-duplicate-ids` - Baseline = 0 duplicate-ID groups on main (verified locally) Catches the failure class empirically observed today: - B-0444 (Otto-CLI vs Otto-Desktop, PR #3053 renumber, ~15 min) - B-0532 + B-0533 (Lior PR #3545 vs Otto-CLI mine, ~15 min coordination) Both went undetected by the existing detect-only audit because nothing was wired to fail CI on duplicate-IDs. This PR closes that gap. Composes with: - B-0535 (parent row; the framing was "extend B-0532" but the actual work was "wire existing logic") - audit-section-33-migration-xrefs (PR #3555 — same catch-once-then-lint pattern) - audit-backlog-items item 8 (PR #3249 — the existing detection logic) - PR #3247 (the B-0329 collision that surfaced the need) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/gate.yml | 26 ++++++++++++++++++++++++++ tools/hygiene/audit-backlog-items.ts | 25 ++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index 7ea8a24ad..a1ac78851 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -764,6 +764,32 @@ jobs: - name: Run audit-section-33-migration-xrefs (--enforce) run: bun tools/hygiene/audit-section-33-migration-xrefs.ts --enforce + lint-backlog-id-uniqueness: + # Fail if any B-NNNN ID is claimed by more than one backlog row file. + # Cross-agent ID-allocation collisions (Otto-CLI vs Otto-Desktop on + # B-0444 2026-05-13; Lior vs Otto-CLI on B-0532+B-0533 2026-05-15) cost + # ~15 min coordination effort each at observed ~20% rate. The + # duplicate-ID detection logic was added to audit-backlog-items.ts + # 2026-05-14 (PR #3249, Copilot caught two files claiming B-0329 on + # PR #3247) but ran detect-only — this job is the CI gate that turns + # codified-but-not-enforced into a control. + # + # B-0535 — sibling of lint-section-33-migration-xrefs + lint-archive- + # header-section33: catch-once-then-lint pattern at ID-allocation scope. + name: lint (backlog ID uniqueness) + timeout-minutes: 2 + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install toolchain via three-way-parity script (GOVERNANCE §24) + run: ./tools/setup/install.sh + + - name: Run audit-backlog-items (--enforce-duplicate-ids) + run: bun tools/hygiene/audit-backlog-items.ts --enforce-duplicate-ids + lint-no-empty-dirs: # Fail if a committed directory has no files — almost always a # forgotten artefact (an agent-created skill folder without a diff --git a/tools/hygiene/audit-backlog-items.ts b/tools/hygiene/audit-backlog-items.ts index 00b1d5faf..0c8f3a1b4 100644 --- a/tools/hygiene/audit-backlog-items.ts +++ b/tools/hygiene/audit-backlog-items.ts @@ -34,11 +34,14 @@ // PR #3247; PR #3249 added this audit class). // // Usage: -// bun tools/hygiene/audit-backlog-items.ts +// bun tools/hygiene/audit-backlog-items.ts # detect-only +// bun tools/hygiene/audit-backlog-items.ts --enforce-duplicate-ids +// # exit non-zero on duplicate-ID groups (B-0535 CI gate) // // Exit codes: -// 0 -- survey ran (findings reported in body) -// 1 -- fatal invocation error (e.g., backlog dir missing) +// 0 -- survey ran (findings reported in body); detect-only mode +// 1 -- fatal invocation error (e.g., backlog dir missing) OR +// duplicate-ID groups found AND --enforce-duplicate-ids set import { existsSync, readdirSync, readFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; @@ -601,6 +604,15 @@ async function main(): Promise { return 1; } + const argv = process.argv.slice(2); + const enforceDuplicateIds = argv.includes("--enforce-duplicate-ids"); + for (const arg of argv) { + if (arg !== "--enforce-duplicate-ids") { + process.stderr.write(`error: unknown argument: ${arg}\n`); + return 1; + } + } + const today = nowIso(); const nowEpoch = Math.floor(Date.now() / 1000); @@ -652,6 +664,13 @@ async function main(): Promise { ); console.log(" (typed-edge backlog graph)."); + if (enforceDuplicateIds && duplicateIdGroups > 0) { + process.stderr.write( + `\nerror: ${duplicateIdGroups} duplicate-ID group(s) found; --enforce-duplicate-ids set (B-0535 gate)\n`, + ); + return 1; + } + return 0; }