Skip to content

docs(knowledge): de-slop instruction surfaces (0.13.9) (#3338) #363

docs(knowledge): de-slop instruction surfaces (0.13.9) (#3338)

docs(knowledge): de-slop instruction surfaces (0.13.9) (#3338) #363

name: silent-revert-canary
# Post-merge canary for #2691: detect a merge that silently deleted content
# another recently-merged commit had just added.
#
# On 2026-08-15 three squash merges each landed a tree that dropped work a
# sibling PR had merged minutes earlier (#2633 dropped both #2644 and #2642,
# #2639 dropped #2635, #2641 dropped #2639). Every check stayed green through
# all three, because each reverting squash removed the code AND its tests in
# the same commit -- no suite can fail for a behavior whose tests are gone.
# Two issues sat CLOSED as COMPLETED with their fixes absent from main.
#
# DETECTION, NOT PREVENTION -- and deliberately so.
#
# * Scanning runs on `push` to main only. There IS a `pull_request` trigger
# below, but it only exercises the detector against its own tests and its
# recorded corpus (see the note above `on:`); the range-resolution and scan
# steps are both gated `if: github.event_name != 'pull_request'`, so no PR
# is ever inspected for silent reverts. By the time the canary speaks, the
# merge has happened; the value is that a human learns within minutes
# instead of during a from-scratch content audit weeks later.
# * It is NOT in ci.yml and is NOT wired into that workflow's `ci-status`
# aggregate, which is the single required check the org ci-gate ruleset
# keys on. Adding it there would make a detection heuristic able to block
# merges, which is how canaries acquire a constituency for switching them
# off. Same posture as link-check.yml: advisory lane, outside ci-status.
# * Nothing here changes any ruleset. The global
# `strict_required_status_checks_policy` stays off under the accepted ADR
# in melodic-software/github-iac
# (docs/adr/0001-relax-strict-required-status-checks.md), and this canary
# is valuable precisely because it catches the class without that churn --
# and, per the evidence in scripts/check-silent-revert.sh, without needing
# it, since all three branches were up to date with main in history and
# stale only in content. `strict` would have passed every one of them.
#
# No cancelling concurrency group on purpose. ci.yml cancels superseded runs to
# save minutes on a PR, but a cancelled canary is a silently missed detection --
# exactly the false-green this lane exists to remove. Pushes to main are
# infrequent enough that letting every run finish costs nothing worth saving.
# The `pull_request` trigger exercises the detector against its OWN unit tests
# and its recorded incident corpus, never against the PR. The range-resolution
# and scan steps are gated off for PR events below, so nothing on a PR ever
# inspects that PR for silent reverts -- detection stays post-merge, per the
# design note above. What a PR gets is ordinary coverage of a shipped script,
# scoped by `paths` to the canary's own files so it is inert on every other PR.
#
# Without it the detector would ship untested until the next push to main, and
# the specific way this script can break is a false GREEN: if the blame parser
# stops matching (awk dialects differ between the runner's mawk and a
# developer's gawk), attribution yields nothing and every commit reports `ok`.
# A canary whose failure mode is silent success is the exact thing #2691 is
# about, so it gets tested before it lands, not after.
#
# This does not make the lane a merge gate. It is not in ci.yml and not in that
# workflow's `ci-status` aggregate, which is the single required check the org
# ci-gate ruleset keys on, so nothing here can block a merge.
on:
push:
branches: [main]
pull_request:
paths:
- 'scripts/check-silent-revert.sh'
- 'scripts/check-silent-revert.test.sh'
- 'scripts/silent-revert-incidents.txt'
- 'scripts/silent-revert-acknowledged.txt'
- '.github/workflows/silent-revert-canary.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
silent-revert-canary:
name: Silent-revert canary
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Full history: the canary blames the lines a merge deleted against
# its parent, and replays the recorded incidents from 2026-08. A
# shallow clone cannot do either, and the script exits 2 rather than
# reporting a clean run it did not earn. fetch-base is off because
# this job never diffs against origin/$BASE_REF.
- name: Deepen history
uses: ./.github/actions/checkout-with-base
with:
fetch-base: "false"
# Self-test first, unconditionally: a broken detector must not be able to
# mask a real regression behind a green canary. Same never-skip,
# self-test-first shape as the ci.yml gates.
- name: Test the silent-revert detector
run: bash scripts/check-silent-revert.test.sh
# The honesty proof. The shipped thresholds are replayed against the
# actual merges from #2691 and must still fire on them -- and must still
# stay quiet on the verified-legitimate commit pinned alongside. A
# detector that only looks plausible is worthless here, because every
# signal a reader normally checks looked healthy during the incident.
- name: Replay the recorded incidents
run: scripts/check-silent-revert.sh --verify-known-incidents
# Resolve the pushed range. `github.event.before` is all-zeros on a first
# push or after a history rewrite, and absent entirely on
# workflow_dispatch; in both cases fall back to the head commit and SAY
# SO, rather than reporting a clean scan of nothing. Values arrive through
# env, never interpolated into the script body.
# Detection is post-merge only. On a pull_request event the two steps
# above have already proven the detector works; scanning stops here so the
# lane never inspects a PR and never has an opinion about merging it.
- name: Resolve the pushed range
id: range
if: github.event_name != 'pull_request'
env:
EVENT_BEFORE: ${{ github.event.before }}
EVENT_AFTER: ${{ github.event.after }}
run: |
set -uo pipefail
zero='0000000000000000000000000000000000000000'
before="${EVENT_BEFORE:-}"
after="${EVENT_AFTER:-$(git rev-parse HEAD)}"
if [ -z "$before" ] || [ "$before" = "$zero" ] ||
! git rev-parse --verify --quiet "${before}^{commit}" >/dev/null; then
echo "::warning::No usable push range (before='${before:-<unset>}');" \
"scanning only the head commit ${after}. Any earlier commit in" \
"this push was NOT scanned."
{
echo "mode=commit"
echo "target=$after"
} >>"$GITHUB_OUTPUT"
else
{
echo "mode=range"
echo "target=${before}..${after}"
} >>"$GITHUB_OUTPUT"
fi
- name: Scan the merged commits for silent reverts
if: github.event_name != 'pull_request'
env:
SCAN_MODE: ${{ steps.range.outputs.mode }}
SCAN_TARGET: ${{ steps.range.outputs.target }}
run: |
set -uo pipefail
if [ "$SCAN_MODE" = "commit" ]; then
scripts/check-silent-revert.sh --commit "$SCAN_TARGET"
else
scripts/check-silent-revert.sh "$SCAN_TARGET"
fi
# The other half of the proof (#2855). The replay above asserts every
# recorded incident is still DETECTED; this asserts the content those
# incidents deleted is on the tree TODAY. Both are needed, and #2828
# is the gap made real: its content sat off main for 31h28m, from
# f603880da to the 534eac138 (#2829) restore. This lane covered only
# the tail -- it merged at 7b47d2253 (#2808), 6h13m out from the
# restore -- and across that tail the replay still printed `ok` and
# exited 0 with the content gone: it answered the other question. Two
# re-lands each missed it, and a hand audit found it, not this lane.
#
# ORDER IS LOAD-BEARING, and this step is deliberately LAST.
#
# A step with no status function carries an implicit `success() &&`, so a
# step that fails SKIPS every later step in the job. Placed before the
# scan, a red restoration assertion would therefore skip the scan itself
# -- and those commits are never re-examined, because the next push's
# range starts at this push's head. That trade is unacceptable in this
# direction: restoration is bound to LIVE content in living files and the
# corpus header says a marker going red on a rename is intended
# loudness, while the scan is the primary detector. A churn-sensitive
# tripwire must never be able to silence it. Running last, this step can
# fail as loudly as it likes and the scan has already spoken.
#
# `success() || failure()` rather than the default, so the reverse
# suppression cannot happen either: a scan that fires (exit 1) must not
# skip the restoration answer. It excludes cancellation, so a cancelled
# run still reports nothing rather than a result it did not earn.
#
# UNGATED on the event, exactly like the self-test and the replay. The
# failure that produced #2828 is "nobody thought to check", so an
# on-demand mode would reproduce it. Running on pull_request events too is
# not a merge gate: this workflow is not in ci.yml and not in that
# workflow's `ci-status` aggregate, which is the single required check the
# org ci-gate ruleset keys on -- and it is `paths`-scoped to the canary's
# own files, so it is inert on every other PR. What it buys is that a PR
# editing this corpus proves its markers resolve before the merge.
#
# No rev argument: it resolves against the checked-out tree, which is the
# question worth asking here -- is the content on main RIGHT NOW. The
# explicit-rev form exists so a reviewer can replay the assertion against
# the historical tree an incident was measured on.
- name: Assert the recorded incidents' content is restored
if: success() || failure()
run: scripts/check-silent-revert.sh --verify-restoration