Skip to content

/premerge output is unmergeable by /merge — add a distinct premerge trust tier #716

Open

Description

@TheFJK

The gap

/premerge always produces a PR that /merge structurally cannot land.

Hit live on stack PR #681. /merge gate-failed with trust_trail_label_missing:

  • PATH_1 — reviewDecision is empty; no GitHub approval.
  • PATH_2 — no uberdev-approved label, no Reviewed-by: uberdev/review-pr@<sha> trailer.

Neither is an accident. tests/premerge.test.sh P10 asserts both absences over
commands/premerge.md and skills/premerge-pipeline/SKILL.md. So every /premerge
run ends at a parked PR whose only landing paths are a manual CLI merge or a
hand-forged trail — and the manual path is itself blocked by the PreToolUse deny
hook that ~/.claude/CLAUDE.md §8 relies on.

P10 is correct — do NOT "fix" this by emitting the existing trail

The trailer reads Reviewed-by: uberdev/review-pr@<sha>. Under /premerge,
/review-pr did not run. The review was the built-in code-review skill, which is
a different instrument:

  • no seven-lens post-impl-review fanout
  • no Phase 3 CI-health phase
  • no verify pass at all on Opus-family xhigh (every finding arrives with verdict absent)
  • no Phase 2.5 findings-to-issues halt semantics
  • no review-pr-verdict.json in the shape trust-trail-evaluator parses

/goal reads the same signal for convergence, so a forged trail would not merely
mislead /merge — it would tell /goal a stack converged when nothing verified it.

Proposed: a distinct tier, not a borrowed one

today proposed
label — premerge-approved
trailer — Reviewed-by: uberdev/premerge@<sha>
evidence .uberdev/premerge/<RUN_ID>/ already written trail points at gate-NN.json
/merge no path accepts it PATH_3, gated on VERDICT=green

Most of the evidence already exists: manifest.json, classified.json, and — as of
#680 — per-attempt gate-NN.json carrying the clean-gate verdict and its reasons.
What is missing is the emission and a /merge path that reads it.

Acceptance criteria

  1. /premerge emits the trail only when its Phase 3 clean gate returned RC=0.
    A not_green run emits nothing and parks exactly as today.
  2. The trailer prefix is uberdev/premerge@, never uberdev/review-pr@. The label is
    premerge-approved, never uberdev-approved. P10 is retargeted to assert the
    review-pr forms stay absent, not that no trail exists — it must still catch a
    regression that forges the review-pr tier.
  3. /merge gains PATH_3 with its own TRUST_ANCHOR_ENUM member
    (e.g. uberdev_premerge_trail) so audit consumers can tell the tiers apart. PATH_3
    re-reads gate-NN.json and refuses on anything but a green verdict — the label and
    trailer alone must never be sufficient, because they are bare literals with no
    payload (the same reasoning behind the Reviewed-base: endpoint in fix(review-pr): trust trail records no base identity, so a GREEN survives a base swap #440).
  4. The trail is SHA-bound like PATH_2's: a push after the gate invalidates it.
  5. /goal is unaffected — it owns its own review→merge loop and must not start
    accepting a premerge trail as convergence evidence.

What this does NOT solve

It would not have landed #681. A correct PATH_3 is gated on a green clean gate, and
that stack's gate was not_green with 10 open blockers (#701–#715). A properly
implemented premerge tier would have refused it exactly as PATH_2 did. The blocker
there was that the stack was not clean, not that the trail was missing — this issue
closes a structural dead-end, it does not make unclean stacks landable.

Ordering

Implementable cleanly only after #681 lands, since the gate-NN.json artifact the
trail points at arrives with #680.


Steer (2026-08-24) — relax the gate, do not only add a parallel tier

This supersedes the emphasis of "Proposed: a distinct tier, not a borrowed one" above.
The proposal there is additive: PATH_1 and PATH_2 keep their current strictness and a
fourth instrument would need a fourth path. The direction to implement is that the
strictness itself is what shrinks
— a generalised gate, which is the same work as
PATH_3 done once instead of once per instrument.

PATH_2 today has sub-conditions (a), (a.5), (b), (b.5), (c) and (d) — label,
release-anchor resolution, trailer, base-identity equivalence, an agent-dispatched
verdict, and audit-JSON shape — plus eight trust-resolution members in
GATE_FAIL_REASON_ENUM. Every one is a way for a genuinely-reviewed stack to be refused.

Three moves:

  1. Make the trail's instrument a field, not a hardcoded prefix.
    Reviewed-by: uberdev/<command>@<sha> with the command as data, and one label family,
    instead of ^Reviewed-by: uberdev/review-pr@([a-f0-9]{40})$ matched by literal regex and
    uberdev-approved matched as a bare string. /premerge then emits the same artifact
    shape with a different value. tests/premerge.test.sh P10 keeps its job: assert
    /premerge never claims the review-pr instrument.

  2. Keep every structural check that answers "is this evidence about THIS code."
    Head-SHA binding, base-identity equivalence (fix(review-pr): trust trail records no base identity, so a GREEN survives a base swap #440), force-push detection. Those are not
    strictness about which reviewer ran; they are the entire reason a trail means anything,
    and relaxing them is out of scope.

  3. Drop or soften the checks that only encode "review-pr's own phases ran."
    The Phase 2.5 halt/override semantics, review-pr-verdict.json's exact shape, and the
    --accept-blocker-deferred / --accept-critical-deferred / --i-know-what-im-doing
    triad. /premerge has equivalent evidence of its own — gate-NN.json, manifest.json,
    classified.json — and no way to produce those.

Invariant that survives either design: /merge still never merges without the
operator's explicit command for that specific PR. Relaxing the trust gate changes which
PRs are eligible, never who pulls the trigger. Do not add an auto-merge path, an
auto-chain from /premerge, or a flag that implies one.

Ordering note above is stale. #681 and #680 have landed; gate-NN.json exists.

Siblings: #788 (severity invisible on premerge output), #789 (default review level),
#790 (folded originals stay live discovery candidates), #785 (the simplify phase never runs).

Activity

  1. TheFJK commented on Aug 24, 2026

    @TheFJK
    CollaboratorAuthor

    Steer from @TheFJK: /merge should accept /premerge output — relax the strict trust trail rather than only adding a parallel tier.

    That changes the emphasis of this issue's proposal, so writing the difference down:

    • The proposal above adds a third trust path (premerge-approved + Reviewed-by: uberdev/premerge@<sha> + PATH_3 gated on VERDICT=green). That is additive: PATH_1 and PATH_2 keep their current strictness and a fourth instrument would need a fourth path.
    • The steer is that the strictness itself is the thing to reduce. /merge's GATE_FAIL_REASON_ENUM currently carries eight trust-resolution reasons and PATH_2 alone has sub-conditions (a), (a.5), (b), (b.5), (c) and (d) — label, release-anchor resolution, trailer, base-identity equivalence, an agent-dispatched verdict, and audit-JSON shape. Every one of those is a way for a genuinely-reviewed stack to be refused.

    The two are not incompatible — a generalised gate is the same work as PATH_3, done once instead of per-instrument. Concretely:

    1. Make the trail's instrument a field rather than a hardcoded prefix. Reviewed-by: uberdev/<command>@<sha> with the command as data, and one label family, instead of uberdev/review-pr@ matched by a literal regex and uberdev-approved matched as a bare string. Then /premerge emits the same artifact shape with a different value, and P10 keeps its job — asserting /premerge never claims the review-pr instrument.
    2. Keep the structural checks that answer "is this evidence about THIS code" — head SHA, base-identity equivalence (fix(review-pr): trust trail records no base identity, so a GREEN survives a base swap #440), force-push detection. Those are not strictness about which reviewer ran; they are the whole reason a trail means anything.
    3. Drop or soften the checks that only encode "review-pr's own phases ran" — the Phase 2.5 halt/override semantics, review-pr-verdict.json's exact shape, the --accept-blocker-deferred / --accept-critical-deferred / --i-know-what-im-doing triad. /premerge has its own equivalent evidence (gate-NN.json, manifest.json, classified.json) and no way to produce those.

    The invariant that must survive either way: /merge still never merges without my explicit command for that specific PR. Relaxing the trust gate is about which PRs are eligible, never about who pulls the trigger.

    Related: #788 (severity is invisible on premerge output), #789 (default review level), #790 (folded originals stay live candidates), #785 (the simplify phase never runs).

  2. added
    uberdev:activeIssue currently being worked on by a /solve or /turbo dispatcher. Auto-managed; do not edit.
    on Aug 24, 2026
  3. self-assigned this
    on Aug 24, 2026
  4. TheFJK commented on Aug 24, 2026

    @TheFJK
    CollaboratorAuthor

    uberdev:active — claimed for /solve or /turbo dispatch

    User: @TheFJK
    Host: MacBook-Pro
    Branch: worktree-solve-issue-716
    Tier: medium
    Started: 2026-08-24T14:40:16Z

    Auto-clears on /merge or issue close. If this claim is stale, override with:
    /turbo 716 --force (or /solve 716 --force)

  5. TheFJK commented on Aug 24, 2026

    @TheFJK
    CollaboratorAuthor

    Parked behind #792 (12 commits, branch worktree-turbox-issue-716).

    The PR says Refs #716, not Closes — deliberately. Two blockers survive and both mean a /premerge stack PR is still unlandable on the default path, which is the condition this issue exists to remove:

    1. 5a buries the trail. The anchor is emitted at 5-trail, then chore(release) lands on top. /merge can only read a trailer under a release commit when release-anchor.sh returns tolerated; measured against real history it returns none on six for six of this repo's release commits (2c6db441 → diff_too_large, others → changelog_shape / non_version_paths). Emitting after the bump is foreclosed too — the gate's HEAD binding returns head_moved.
    2. A deferred-CRITICAL trail can land without --accept-critical-deferred, whenever the audit JSON is not locally discoverable. Orthogonal to this issue's scope; it arrived bundled with the instrument generalisation.

    Eight further contract-drift findings are listed in the PR body. All ten ship unfixed — the fix round could not be dispatched because the review budget hit its ceiling, partly because a session-limit outage charged five review seats twice.

    Everything else verified: full 132-row authoritative suite green, the new gate/emit fences extracted and executed under bash and zsh rather than transcribed, and every new assertion mutation-tested to confirm it can actually fail.

  6. added
    parkedWork is done and sitting on an open PR awaiting /merge. No agent should re-pick this issue.
    and removed
    uberdev:activeIssue currently being worked on by a /solve or /turbo dispatcher. Auto-managed; do not edit.
    on Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

parkedWork is done and sitting on an open PR awaiting /merge. No agent should re-pick this issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions