Skip to content

fix(ci): make grandfathered attribution commits rebase-safe - #1356

Closed
allyblockcast[bot] wants to merge 4 commits into
masterfrom
blo-27142-rebase-safe-attribution
Closed

fix(ci): make grandfathered attribution commits rebase-safe#1356
allyblockcast[bot] wants to merge 4 commits into
masterfrom
blo-27142-rebase-safe-attribution

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Aug 14, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip's PR policy gate protects repository history from commits stamped by the shared GitHub App identity.
  • The gate grandfathered specific pre-cutoff commits by raw SHA because their original authorship cannot be recovered safely.
  • The master merge queue is configured by ruleset 20487141 with merge_queue.merge_method: REBASE.
  • Queue rebases preserve the patch and author but necessarily produce a new SHA, so raw-SHA grandfathering ejects otherwise-valid PRs only after enqueue.
  • This pull request keys the exemption by stable git patch-id plus the exact App author email and adds the requested regression test.
  • The benefit is that the queue and pull-request policy paths produce the same verdict without weakening the fail-closed behavior for unregistered content.

Linked Issues or Issue Description

Refs: BLO-27142

What Changed

  • Replace raw grandfathered SHA matching with patch-id plus author-email matching.
  • Register the existing grandfathered commits under their stable patch IDs.
  • Add a regression test that rebases an App-attributed grandfathered commit and verifies it still clears the gate.
  • Wire the attribution checker and focused test into the PR policy job.

Verification

  • node --check scripts/check-commit-author-attribution.mjs
  • node --test scripts/check-commit-author-attribution.test.mjs
  • Live ruleset query: gh api repos/Blockcast/paperclip/rulesets/20487141 --jq '{name,enforcement,conditions,rules}' returned active merge_method: REBASE.
  • git diff --check

Risks

  • Low runtime risk: only the grandfather path changes, and it still requires both the exact App author email and an enumerated patch ID.
  • A patch-id collision would share an exemption; pairing with the author email and retaining a finite allowlist limits that risk.
  • PR fix(monitor): two-surface PR review gate check (BLO-22574) #1126 and the five sibling PRs were not enqueued by this PR because queue mutation requires explicit shared-infrastructure approval on the issue.

Model Used

OpenAI GPT-5.6 Terra (openai/gpt-5.6-terra), tool-using coding agent with repository inspection, GitHub API access, and local test execution.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • I have considered and documented any risks above
  • All Paperclip CI gates are green
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

Paperclip issue: https://paperclip.blockcast.net/BLO/issues/BLO-27142

CTO and others added 4 commits August 6, 2026 22:31
…858)

`paperclipInboxLite`'s tool description and the OpenAPI summary both claimed
the endpoint returned `in_review` work. The route filters to
`todo,in_progress,blocked` (server/src/routes/agents.ts:2451). An agent whose
only assignments were `in_review` therefore got a correct empty array from a
call that had promised otherwise, read it as a platform failure, and
hand-rolled a checkout-lock-blind `listIssues` sweep to "recover" — which is
how two concurrent runs of the same agent duplicated a full task.

The empty array was right. The description was the bug, and it is the whole
root cause of the incident.

- Describe the real status set in the MCP tool description and OpenAPI summary,
  and say what an empty array means per wake type: exit on an unscoped
  heartbeat; on an issue-named wake, read that issue by id and work it.
- Ban the raw-sweep "recovery" explicitly in both skills — it has no
  checkout-lock awareness.
- Replace SKILL.md Step 4's run-id comparison with checkout-first. Deciding
  ownership by reading `executionRunId`/`executionLockedAt` is TOCTOU-unsafe:
  the lock can be taken between the read and the first write. Those fields are
  the lock's storage, not its API. Checkout is the atomic boundary, and it is
  run-scoped, so a concurrent run of your own agent gets a 409 too. Also drops
  the instruction to comment on a lost race — that mutates an issue another run
  owns and can enter the approval-comment state machine.
- Give `issue-triage` back a status-scoped `in_review` read, since the skill
  advertises triaging a status inbox-lite excludes.
- Document the current `422 issue_in_review_not_checkoutable` gap on unlocked
  `in_review` rows rather than papering over it.

Pins the contract with agent-inbox-lite-status-contract.test.ts, whose
load-bearing assertion is that `in_review` never leaks into the routine inbox.

Docs and tests only — zero runtime behavior change. Split out of #821 per the
scope decision on BLO-18858; the `in_review`-checkoutability runtime change and
its open review findings are tracked separately.
…ses (BLO-18858)

Ally's Important finding on #1116 at b4634b1, verified against the service
rather than taken on faith.

issueService.checkout runs its atomic UPDATE first, matching on
inArray(issues.status, expectedStatuses), and returns early when a row matches
(server/src/services/issues.ts:9187-9235). The typed 422
issue_in_review_not_checkoutable is only reachable when that UPDATE matches
nothing (:9417-9433). So documenting in_review made the 422 unreachable and
silently flipped an unlocked review/approval wait to in_progress - the opposite
of what the surrounding paragraph promised.

The paragraph's justification for including it was also wrong: it claimed
in_review in expectedStatuses is "what makes a locked in_review issue return 409".
It is not. The 422 branch is guarded on checkoutRunId == null && executionRunId
== null, so a locked row falls through to the generic conflict either way.
Omitting in_review therefore preserves both behaviours and makes the documented
422 reachable. The service comment at :9411 already states in_review "is excluded
from every caller's expectedStatuses" - the docs had drifted from that intent.

Adds checkout-expected-statuses-doc-contract.test.ts to pin the documentation
side (the behavioural side is already pinned by the existing typed-422 service
test, which uses exactly this payload). Mutation-checked: reintroducing
in_review fails exactly the new assertion and nothing else.

Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts:
#	packages/skills-catalog/generated/catalog.json
Co-Authored-By: Paperclip <noreply@paperclip.ing>
@allyblockcast
allyblockcast Bot requested a review from kkroo as a code owner August 14, 2026 13:29
@allyblockcast

allyblockcast Bot commented Aug 14, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-27142

1 similar comment
@allyblockcast

allyblockcast Bot commented Aug 14, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-27142

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 2b5b6ef

Critical Issues (0)

Important Issues (1)

  • [native-codex] scripts/check-commit-author-attribution.mjs:177auditRepoCommitAttribution does not compute a patchId for API-fetched commits before calling findAttributionOffenses. Because allowlist is therefore omitted, findAttributionOffenses takes its allowlist === undefined path and flags every non-merge commit authored by the App, including grandfathered commits that should match the patch-id/email allowlist. The new rebase-safe grandfathering consequently works only for local-range checks; --audit-merged will report those same grandfathered commits as offenses. Map each API commit through the same patch-id computation or pass a patch-id-aware allowlist before evaluating offenses, and add a regression test for an allowlisted API commit.

Suggestions (0)

Strengths

  • The local PR-range path computes stable patch IDs and preserves the exact App author-email check.
  • The inbox-lite contract changes are clearly documented and covered by focused route and documentation tests.
  • The workflow wires both the attribution check and its regression tests into the policy job.

Recommended Action

  1. Fix the Important issue before merge.
  2. Re-run the focused attribution tests and merged-PR audit coverage.

@allyblockcast

allyblockcast Bot commented Aug 14, 2026

Copy link
Copy Markdown
Author

Superseded by #1357, which is based directly on current master and contains only the attribution-gate change.

@allyblockcast allyblockcast Bot closed this Aug 14, 2026
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.

0 participants