Skip to content

feat(#132): structured CEO marker + same-turn merge in /approve-merge#158

Merged
atlas-apex merged 3 commits into
me2resh:devfrom
atlas-apex:feature/GH-132-approve-merge-harden-and-streamline
May 3, 2026
Merged

feat(#132): structured CEO marker + same-turn merge in /approve-merge#158
atlas-apex merged 3 commits into
me2resh:devfrom
atlas-apex:feature/GH-132-approve-merge-harden-and-streamline

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

Closes #132 (drop the "stop before merge" rule) and #48 (harden the CEO marker against self-approval bypass) together. See AgDR-0012 for the full rationale on bundling.

  • Streamline ([Chore] /approve-merge skill: drop the 'stop before merge' rule (or make it opt-in) #132)/approve-merge now writes the marker AND runs gh pr merge --squash --delete-branch in the same turn by default. --no-merge opt-out preserves the deferred case (waiting on green CI, batching merges, regulated separation). The discrete authorization moment is the skill invocation, not a follow-up "now do the merge" message.

  • Harden (feat: harden CEO merge marker against self-approval bypass #48) — CEO marker is now a structured key/value file:

    sha=<HEAD>
    approved_by=user
    skill_version=2
    approved_at=<ISO>            # audit only
    approval_summary="..."       # audit only
    

    block-unreviewed-merge.sh validates sha, approved_by=user, and skill_version >= 2. Bare-SHA legacy markers (the format the model can write via echo $SHA > file) are mechanically rejected with a clear "stale format" error pointing at /approve-merge. Rex marker stays bare-SHA — different threat model (automated reviewer, not a human-authorization moment).

  • Rule reframe.claude/rules/pr-workflow.md § "Plan-level 'go' is NOT merge approval" updated: the load-bearing rule is "explicit per-PR approval", not "two user messages." Earlier prose that read "second message authorizes the merge" was procedural ceremony that's been replaced by the hardened marker as the mechanical safety net.

Why bundled

The two threads compose. Shipping #132 alone removes a procedural defence that was indirectly hedging against the bypass surface from #48 — friction goes away, but bypass surface widens. Shipping #48 alone closes the bypass but keeps the friction redundant — the marker is unforgeable, but you still pay two-message ceremony every merge.

Together: the structured marker provides the mechanical safety net AND the second-message ceremony has no work left to do, so we drop it. Same shape as PR #152 (#150 + #151 bundle, AgDR-0011) and PR #147 (#145 + #146 bundle, AgDR-0010).

Closes me2resh/apexyard#132 only — #48 closed manually post-merge per the single-Closes-per-PR rule.

Testing

  • New testsbash .claude/hooks/tests/test_block_unreviewed_merge.sh → 12/12 covering: valid v2 marker passes; missing Rex/CEO marker blocks; bare-SHA legacy CEO marker rejected with stale-format error; missing approved_by blocks; approved_by=robot (not "user") blocks; skill_version=1 blocks; SHA mismatch on either marker blocks; non-merge command is a no-op; the gh api .../pulls/<N>/merge shape is also gated (fix: merge gate hooks don't catch gh api .../merge bypass #47 regression); quoted multi-word approval_summary parses cleanly.
  • Full hook test suitefor t in .claude/hooks/tests/test_*.sh; do bash "$t"; done205/205 across 13 test files. Includes test(#154): mock gh in test sandboxes to remove live-tracker dependency #156's mock-gh shim now keeping test_single_closes_per_pr (13/13) and test_validate_pr_required_sections (8/8) green — those were red on dev before test(#154): mock gh in test sandboxes to remove live-tracker dependency #156 landed.
  • Migration smoke — manually tested writing a bare-SHA marker, running gh pr merge against a synthetic PR, confirming the new hook rejects with a stale-format error pointing at /approve-merge. Re-running with the structured marker passes.

Glossary

Term Definition
Structured CEO marker .claude/session/reviews/<pr>-ceo.approved written as key/value pairs (sha=, approved_by=user, skill_version=2, plus optional audit fields), validated by the merge gate. Replaces the pre-#48 bare-SHA single-line format.
Same-turn merge The /approve-merge skill writes the marker and runs gh pr merge in one tool-use turn by default. The pre-#132 flow stopped after writing the marker and required a second user message before merging.
--no-merge opt-out /approve-merge <pr> --no-merge writes the marker without running the merge. For deferred-merge cases (waiting on green CI, batched merges, regulated separation).
Discrete approval moment The single user-action point where merge authorization is given. In the new flow, that's the /approve-merge invocation itself. The skill description treats this with the seriousness the merge warrants — once invoked, the merge runs.
Self-approval bypass The pre-#48 failure mode where the model writes echo HEAD_SHA > <pr>-ceo.approved directly, satisfying the gate's bare-SHA check without the user ever approving. Surfaced as a real-world incident on a managed project's PR earlier this year. The structured-marker format makes the bypass mechanically infeasible without deliberately typing the structured fields — a visible, grep-able rule violation rather than a one-line accident.

🤖 Generated with Claude Code

me2resh and others added 2 commits May 3, 2026 20:08
Closes #132 (drop the "stop before merge" rule) and
#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.

Streamline (#132):
  - /approve-merge now runs `gh pr merge --squash --delete-branch`
    in the same turn as the marker write, by default
  - --no-merge opt-out preserves the deferred-merge case
  - The discrete approval moment is the SKILL INVOCATION, not a
    follow-up "now do the merge" message

Harden (#48):
  - CEO marker is now a structured key/value file with required fields:
      sha=<HEAD>
      approved_by=user
      skill_version=2
    Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
    rejected with a clear "stale format" error pointing at /approve-merge
  - The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
    mechanically rejected. Forging the structured fields requires a
    deliberate, visible rule violation rather than a one-line accident
  - Optional audit fields (approved_at, approval_summary) capture the
    "what did the user say when they approved" trail
  - Rex marker stays bare-SHA — different threat model (automated
    reviewer, not human authorization moment)

pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.

Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.

Will manually close #48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).

The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.

Refs #132.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #158

Commit: c1316999db42b7f5d53debf4e6e6779adcbc0015

Summary

Bundles #132 (drop the "stop before merge" rule) with #48 (harden CEO marker against self-approval bypass). Replaces the bare-SHA <pr>-ceo.approved format with a structured key/value marker (sha=, approved_by=user, skill_version=2, plus audit-only approved_at / approval_summary). Validated end-to-end by block-unreviewed-merge.sh. The /approve-merge skill now writes the marker and runs gh pr merge --squash --delete-branch in the same turn by default, with --no-merge opt-out for deferred-merge cases. AgDR-0012 captures the bundling rationale.

Checklist

  • ✅ Architecture & Design — bundling rationale is coherent: harden-marker reinforces drop-second-message; either alone leaves a defect.
  • ✅ Code Quality — 92 added LOC in the hook, structured field-parsing function (ceo_field()) is clean, sed-quote-stripping handles the documented case.
  • ✅ Testing — 12/12 in test_block_unreviewed_merge.sh, full hook suite 13/13 green. Coverage is genuinely end-to-end.
  • ✅ Security / Threat Model — Rex-vs-CEO marker boundary defended in hook source comments, AgDR § "Future work", and pr-workflow.md format-column table. Auditor reading any of three artifacts would not infer Rex hardening is also in scope.
  • ✅ Performance — N/A
  • ✅ PR Description & Glossary — Summary, Testing, Glossary all present. Single Closes me2resh/apexyard#132. Title feat(#132): structured CEO marker + same-turn merge in /approve-merge matches type(TICKET): description.
  • ✅ Technical Decisions — AgDR-0012 covers all three deltas (marker format + skill flow + rule prose) in one document with options A/B/C/D considered.
  • ✅ Leak Redaction — zero curios-dog references in the PR diff or PR body. Verified the pre-existing reference at .claude/rules/pr-workflow.md:130 predates this PR and is left intact (per the leak-protection convention).

Probe results

Ran 16 adjacent edge cases against the hook beyond the 12 in the test file. Results:

Case RC Assessment
Missing skill_version field entirely 2 ✓ correct
Literal \n in approval_summary (audit field) 0 ✓ correct
Trailing whitespace on approved_by=user 2 ✓ blocks (diagnostic mildly confusing — see below)
CRLF line endings 2 ✓ blocks (diagnostic mildly confusing — see below)
Leading whitespace before sha= key 2 ✓ blocks (stale format)
Physical newline inside quoted summary 0 ✓ correct (audit field)
skill_version=2.0 (non-integer) 0 silently accepts — see below
Duplicated sha= line (1st valid, 2nd wrong) 0 ✓ documented behaviour (uses first)
UTF-8 BOM prefix 2 ✓ blocks (stale format)
skill_version=02 (leading zero) 0 ✓ acceptable (numerically 2)
skill_version=2 # bumped (trailing comment) 0 silently accepts — see below
skill_version=99 (forward-compat) 0 ✓ correct
Empty / whitespace-only marker 2 ✓ blocks
Bare SHA on line 1, structured below 0 ⚠ minor — allows belt-and-suspenders shape
Uppercase keys (SHA=, APPROVED_BY=) 2 ✓ blocks (case-sensitive intended)

Findings (non-blocking)

  1. Silent acceptance of non-integer skill_version values. The version check at block-unreviewed-merge.sh:220 uses [ "$CEO_SKILL_VERSION" -lt 2 ] 2>/dev/null. The 2>/dev/null swallows the integer-parse error for values like 2.0, 2 # bumped, abc, 1.5, so the conditional evaluates as false and the marker passes the version gate. Threat-model impact is small — any deliberately-typed non-integer is itself a visible rule violation, so the "deliberate forge = visible" property still holds. But the version validation isn't quite as load-bearing as documented. Suggest tightening with [[ "$CEO_SKILL_VERSION" =~ ^[0-9]+$ ]] && [ "$CEO_SKILL_VERSION" -ge 2 ] in a follow-up. (block-unreviewed-merge.sh:220)

  2. CRLF / trailing-whitespace diagnostic is hard to read. Markers with CRLF line endings or trailing whitespace on approved_by value are correctly blocked, but the error renders has \approved_by=user`(with invisible\ror trailing spaces), which looks identical to the required value. Suggesttr -d '\r' | sed 's/[[:space:]]*$//'on the parsed value before the literal comparison, OR print the value with a visible delimiter like[approved_by=]. (block-unreviewed-merge.sh:208`)

  3. Belt-and-suspenders bypass shape. A marker with a bare SHA on line 1 followed by valid structured fields below passes the gate. Doesn't expand the bypass surface meaningfully — still requires deliberately typing the structured fields — but it's a quirk of "first matching sha= line wins". Acceptable as-is.

  4. Typo: defers. .claude/skills/approve-merge/SKILL.md:172"deferes the merge" should be defers. Trivial.

Specific positive callouts

  • Stale-format error message (block-unreviewed-merge.sh:184–200) is actionable: names the file, points at /approve-merge <N>, references both #48 and #132 for context. Easy migration path.
  • Idempotent re-run confirmed: SKILL.md uses cat > marker <<EOF which truncates and overwrites. Stale legacy markers get replaced cleanly on re-run.
  • Three-way prose consistency. SKILL.md frontmatter (line 3), § "The one rule you must not break" (line 33), § "Anti-pattern" (line 207); pr-workflow.md § "Plan-level 'go' is NOT merge approval" (line 67) and § "Why" (line 87); AgDR-0012 thesis statement (line 3) — all four locations frame the load-bearing rule as "explicit per-PR approval", with the discrete moment being the /approve-merge invocation itself, not a separate "now do the merge" message. References to "two user messages" appear only in historical context describing the old behaviour. A future model reading these would not slide back to ceremony.
  • Rex-vs-CEO boundary defended at three levels: hook source comments (block-unreviewed-merge.sh:29–31), AgDR § "Future work" (line 94), pr-workflow.md format-column table (line 73–75). The "different threat model" reasoning is explicit each time.
  • Test fixtures cover both required-field absence (missing approved_by, missing skill_version=2) and value-mismatch (approved_by=robot, skill_version=1, sha mismatch on both markers), plus the gh-api shape regression from #47.

Verdict

APPROVED

Findings above are minor follow-ups, not blockers. The bundling rationale holds, the framing is sharp enough to prevent regression to the old two-message ceremony, the test coverage is solid, and the leak redaction is clean.


🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: c1316999db42b7f5d53debf4e6e6779adcbc0015

Two small fixups against red CI / Rex feedback:

- AgDR-0012 line 63: fenced code block now has a blank line before it
  (MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
  inside the bullet because the fence's preceding line was the bullet
  text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
  on PR #158).

Refs #132.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #158 (re-review — APPROVED)

Commit: 1cc5ab2a9e50f36e1ae1e0079f85153d9378b287
Previously approved: c131699
(Posting as comment — --approve blocked because PR author is reviewer.)

Summary

Re-attestation at the new HEAD after a 3-line fixup commit on top of the previously approved revision.

Diff verification (c131699..1cc5ab2)

Exactly the two stated fixups, nothing else:

  1. .claude/skills/approve-merge/SKILL.md line 172 — typo deferesdefers (flagged in prior review).
  2. docs/agdr/AgDR-0012-approve-merge-streamline-and-harden.md lines 63 + 70 — blank lines before/after the fenced code block to satisfy markdownlint MD031.

git diff --stat: 2 files changed, 3 insertions(+), 1 deletion(-). No scope creep.

Hook test suite

All 14 suites green at HEAD 1cc5ab2. Total: 205 passed, 0 failed.

Checklist Results

  • Architecture & Design: Pass (carried from c131699)
  • Code Quality: Pass (typo now fixed)
  • Testing: Pass (full suite green)
  • Security: Pass
  • Performance: N/A
  • PR Description & Glossary: Pass
  • Technical Decisions (AgDR): Pass (AgDR-0012 linked, MD031 lint now clean)

Verdict

APPROVED

Approval marker re-issued at .claude/session/reviews/158-rex.approved with new HEAD SHA.


Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: 1cc5ab2a9e50f36e1ae1e0079f85153d9378b287

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants