# STK-1 — a records PR can merge into a branch that is not `main`, report `merged`, and silently deliver nothing: it happened to panel run 42's entire output tonight. Nothing checks a PR's destination — and because the auto-merge workflow reads the GUARD from that same unchecked base ref, AMG-4's invariant rests on it too

status: open · raised panel run 42 (2026-10-21, ~23:45Z, from its own failure) ·
class: **G2 loudness — a success report that is not one** (NORTHSTAR §1 asset 1:
"nothing fails silently") · judgement: **YES** · effort ~0.1 pd · horizon:
decidable immediately; live for every routine that opens a PR

---

**Plain-language summary.** Tonight a routine's whole night of work was
reported as successfully merged and in fact went nowhere. Nothing noticed. The
work was recoverable because the routine happened to re-read `main` twenty
minutes later for an unrelated reason; had it not, four queue records — one of
them a trading-safety finding — would simply not exist, with a green
"merged" against them.

## What happened, CONFIRMED-BY-EXECUTION

Panel run 42 opened #261 with its base set to `research-queue/panel-run-41`,
deliberately, so that run 41's LAB-1 leg 3 and run 42's leg 4 (sequential
appends to the same file) would not conflict. Then:

1. #257 was **squash**-merged to `main` as `a5d38ea`. A squash merge creates a
   *new* commit; the branch's own tip never becomes an ancestor of `main`.
2. The branch was **not deleted**, so GitHub had no reason to retarget #261.
   Retargeting happens on base-branch *deletion*, not on base-branch *merge*.
3. #261 was then merged — **into `research-queue/panel-run-41`** (`09b5369`),
   and GitHub emitted `pull_request.closed` with `outcome: merged`.
4. That branch is not an ancestor of `main`:

```
git merge-base --is-ancestor origin/research-queue/panel-run-41 origin/main   # NO
git cat-file -e origin/main:research/queue/open/dlv-1-...md                   # ABSENT
git cat-file -e origin/research-queue/panel-run-41:research/queue/open/dlv-1-...md  # present
```

All four of run 42's files — LAB-1 leg 4, DLV-1, A4S-1, SHR-1 — were stranded,
along with a correction commit that withdrew a claim #265 had falsified. Main
was left carrying #264's commit message *crediting* "panel run 42 as LAB-1 leg
4" while the row contained no leg 4.

## Why this is the system's and not only the author's

Half of it is mine: **do not stack on a base that will be squash-merged.** That
is a lesson, and it is recorded here.

The other half is that nothing looks at the destination.
`scripts/automerge_guard.py` grades the **diff** — which paths changed, whether
anything touches `research/queue/approved/`, whether a queue move is
sanctioned. Its only `base`-shaped input is `--queue-before`, *"path to the
base version of the queue file"* (`:289`) — the file's pre-image, not the PR's
base branch. So a records PR aimed anywhere at all passes the same gate, and
the only signal the author gets is the word `merged`. Every routine in the shop
opens PRs; any of them can be handed a false success this way, and the
observable consequence is indistinguishable from the work never having been
done.

**And the sharper half, which I did not see on the first pass.** The
*workflow* does have the destination in hand — `.github/workflows/auto-merge-records.yml:62`
binds `BASE: ${{ github.event.pull_request.base.ref }}` — and it uses it as a
**trust anchor** rather than checking it:

```yaml
if ! git show "origin/$BASE:scripts/automerge_guard.py" > /tmp/automerge_guard_base.py   # :99
verdict=$(python3 /tmp/automerge_guard_base.py --files "$files" ...)                      # :105
```

That is AMG-4's fix, and the workflow header states its reasoning (`:22-23`):

> *"the guard that grades a PR is read from the BASE ref, so a PR's own rewrite
> of the guard never grades that PR."*

**That invariant holds only if the base ref is trusted, and nothing enforces
that it is.** `$BASE` is never compared to the default branch. For #261 the
base was `research-queue/panel-run-41`, so the guard that graded run 42's PR
was *that branch's* copy of the guard — not main's. A PR based on a branch its
author can push is therefore graded by a guard its author can write, which is
precisely the key AMG-4 was closing, reopened through the base-ref door.

**Labelled INFERRED-STATICALLY, and deliberately not executed.** Confirming it
would mean pushing a deliberately permissive guard to a branch and opening a
PR against it, which is exactly the act the deny set exists to prevent. I read
the workflow instead and am reporting the shape. Whoever fixes this should
verify it in a scratch repo, not here. Note it is *not* reachable for a PR
whose base is `main`, which is every PR anyone intended to open — the exposure
arrives with the mistake, which is why the one-line base check closes both
halves at once.

It is also the second false success of the same evening: the `auto-merge` check
was red for an unrelated account-level reason, which trains a reader to
discount that signal exactly when it would have mattered.

## Remedy, ~0.1 pd

The guard already runs on every records PR and already knows how to refuse.
Add the destination to what it grades:

- **assert `$BASE` is the default branch**, and refuse otherwise with a message
  naming the actual base. The variable is already bound at `:62`; this is one
  comparison. It closes both halves — the stranded-merge half and the
  guard-provenance half — because both are consequences of an untrusted base.
- in the routine prompts / `ops/` conventions, one line: **base a records PR on
  `main`**; where two runs would conflict on the same file, resolve it by
  merging `main` in, not by stacking.

Neither touches trading, config, a pinned rule or frozen text.

## Pre-registered kill criterion

It dies if the workflow or the guard is shown to compare the PR's base against
the default branch anywhere in a refuse path — it does not today: the four
`base` hits in `scripts/automerge_guard.py` are `basename` and the
`--queue-before` file pre-image, and the workflow's `$BASE` is only ever an
argument to `git show`. It also dies if the repo rules that stacked records PRs
are a wanted pattern **and** some other mechanism reports a merge whose content
did not reach `main`. In that second case the ruling carries a corollary: name
that mechanism, because tonight nothing did.

The guard-provenance half dies if the workflow is shown to resolve the guard
from the default branch rather than from `$BASE` — on today's text it does not.

It does **not** die because the content was recovered. It was recovered by
luck — a re-read for an unrelated reason — and a defect whose detection
depends on an unrelated coincidence is undetected.

## Slot arithmetic, stated honestly

`main`'s `open/` is 13 before this PR; DLV-1 and STK-1 take it to **15** against
a cap of 12. That is 3 over, and triage's mechanical overflow rule applies at
the 2026-10-26 sweep. **I am not choosing the evictions** — the ranking is
triage's, and the pre-declared one (DGX-1) still rests on a rationale #249
falsified. For what it is worth: this row is cheap, mechanical, and in the
class NORTHSTAR puts at P0; DLV-1 bites every Friday the auto-merge gate is
dark. Both would survive a ranking I did not perform.

## Prior art checked

`grep -rniE "base.ref|stacked|base branch|non-main|retarget"` across all four
`research/queue/` dirs → nothing. The nearest rows are a different surface:
`amg-4-guard-graded-by-own-copy` and `amg-5-sanctioned-move-fail-open` (both
built) concern what the guard grades *within* the diff, not where the PR is
aimed. `rpl-1-replace-path-bypasses-gate` (dismissed) is a path-matching
question inside the diff. No overlap.
