Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/backlog-hygiene.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Backlog status hygiene — stop `docs/BACKLOG.md` from lying about build state.
#
# WHY. Work ships, the item's banner is never updated, and the backlog goes on describing finished
# work as open. A 2026-07-09 audit found 11 items misfiled as open — including #60 (turnkey DR),
# which shipped with ADR 0049 and a working `backup` / `restore-verify` CLI while its banner still
# read "PRE-RESERVED, owner-gated". That stale banner was then repeated as fact in a merged PR. The
# same rot left the Corepoint gap analysis ~22% obsolete. A doc that lies about build state is worse
# than no doc: it silently misdirects planning.
#
# TWO HALVES.
# * The STRUCTURAL half ("every item declares exactly one status") is enforced by pytest —
# `tests/test_backlog_status_check.py` runs the checker against the real file on every PR, so it
# rides the existing test matrix and needs no job here.
# * The BEHAVIOURAL half is here, because only the PR context can see it: if a PR claims to
# implement a backlog item (`BACKLOG #N` in its title or body) and touches engine/IDE code, then
# it must also update `docs/BACKLOG.md`. That is the step whose omission caused #60.
#
# Read-only. No secrets. Workflow expressions are hoisted into `env` and never interpolated into a
# `run:` body (zizmor: a PR title/body is attacker-controlled on a fork PR and must arrive as data).
name: backlog-hygiene

on:
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: backlog-hygiene-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
banner-on-implementation:
# QUOTED: an unquoted YAML scalar ends at " #" (comment start), which silently truncated this to
# "a PR that implements BACKLOG". The job name is the required-status-check *context* string, so
# a truncated name is what you would have to add to branch protection.
name: "a PR that implements BACKLOG #N must update BACKLOG.md"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false

- name: Require a backlog update when a PR claims to implement an item
env:
# Hoisted, never interpolated into the script body (zizmor: template injection).
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail

# Does this PR *claim* to implement a backlog item? Only the explicit `BACKLOG #N` token
# counts — a bare `#123` is ambiguous in this repo (it is usually a PR number).
claim="$(printf '%s\n%s\n' "$PR_TITLE" "$PR_BODY" | grep -oiE 'BACKLOG #[0-9]+' | head -1 || true)"
if [ -z "$claim" ]; then
echo "No 'BACKLOG #N' claim in this PR — nothing to enforce."
echo "(If this PR completes a backlog item, say so with 'BACKLOG #N' and update its banner.)"
exit 0
fi

changed="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")"
touches_code=false
case "$changed" in
*messagefoundry/*|*ide/*|*messagefoundry_webconsole/*) touches_code=true ;;
esac

if [ "$touches_code" != true ]; then
echo "PR claims '$claim' but changes no engine/IDE code — no banner update required."
exit 0
fi

if printf '%s\n' "$changed" | grep -qx 'docs/BACKLOG.md'; then
echo "OK — PR claims '$claim', touches code, and updates docs/BACKLOG.md."
exit 0
fi

n="$(printf '%s' "$claim" | grep -oE '[0-9]+')"
cat >&2 <<EOF
ERROR: this PR says it implements '$claim' and changes engine/IDE code, but it does not
touch docs/BACKLOG.md.

Update item #$n's status banner in the same PR. If the work is complete:

> ✅ **SHIPPED in <version> (<ADR / PR>).** <one line of evidence>

and remove its '🔢 Re-scored' banner (an item must declare exactly one status).

This gate exists because #60 shipped while its banner still said "PRE-RESERVED", and that
stale banner was later repeated as fact. A doc that lies about build state silently
misdirects planning.

If this PR only *partially* implements the item, keep the open banner and say so in the
item body — then drop the 'BACKLOG #$n' token from this PR's title/body.
EOF
exit 1
16 changes: 13 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,23 @@ scripts/security/scan-tokens.local.txt
# DELIBERATELY NOT LISTED: tests/test_scan_forbidden.py, tests/test_anon_core.py and
# .github/dependabot.yml. Those were deny-listed too, but this change set publishes synthetic/public
# versions of them -- ignoring them here would silently drop content that is meant to ship.
/CLAUDE.md
# PUBLISHED by this change (removed from the list above): CLAUDE.md, docs/BACKLOG.md and
# docs/WORKTREES.md. All three were scanned and carry no customer/estate tokens (BACKLOG.md needed a
# two-line redaction). CLAUDE.md in particular MUST be tracked: it is gitignored-by-default's worst
# case here, because `git worktree add` cannot deliver an untracked file, so every worktree silently
# came up with ZERO project conventions loaded.
/.claude/
/TRANSCRIPTS.md
/docs/security/
/docs/reviews/
/docs/marketing/
/docs/BACKLOG.md
/docs/WORKTREES.md
# Deliberately still private, each for a different reason:
# docs/security/ — 32 files of posture/risk-register detail; an attacker roadmap.
# docs/reviews/ — review findings, same reason.
# docs/CI-TOPOLOGY.md — STALE: it documents the retired private-repo/public-mirror split and
# scripts/publish/, which the cutover deleted. Publishing it would actively
# mislead. Rewrite for the post-cutover topology or delete it.
# docs/Secure_Development_Standards.md — scans clean, but it was deliberately pulled from the public
# PyPI sdist (#1020); reversing that is an owner call, not a side effect.
/docs/CI-TOPOLOGY.md
/docs/Secure_Development_Standards.md
Loading
Loading