[codex] add idempotent approval create and cheap listing (BLO-19132) - #953
[codex] add idempotent approval create and cheap listing (BLO-19132)#953kkroo wants to merge 5 commits into
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
1 similar comment
|
/test |
|
Housekeeping so the two PRs don't compete: #945 is now a draft and is not a merge candidate. No changes pushed here — this branch is yours and its independent authorship is the point of it Status at head |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: d9f28c1
Important Issues (1)
- [gstack/review]
server/src/routes/approvals.ts:218— The new transactional callback invokeslogActivity(txDb, ...), butlogActivitydoes more than write throughtxDb: it immediately callspublishLiveEventand then fire-and-forget enqueuesapproval.createdthrough the global outbox DB (server/src/services/activity-log.ts:192-241). Those effects can run before the create transaction commits. A plugin can therefore consume an event while the approval is still invisible, and a commit failure can leave a ghost notification for an approval that rolled back. This preserves the same create/notification consistency gap theafterCreatehook is intended to close, only in the opposite direction.- Make the outbox insert part of the supplied transaction and defer the in-memory live event until after commit, or add an explicit transaction-safe activity path whose externally observable effects cannot precede commit. Add a regression test that forces the create transaction to roll back after activity logging and proves that no live/plugin event escapes.
Strengths
- Requester attribution is now actor-derived and mutually exclusive, matching the two requester-scoped unique indexes.
- Count, summary, and full views share the same filter builder, and summary responses omit the payload while deriving labels after redaction.
- The advisory lock and partial unique indexes provide a sound concurrency backstop for undecided approval retries.
Recommended Action
- Fix the Important issue before merge.
- Re-run the canceled CI workflow after the fix so all required lanes complete on the reviewed head.
|
Ally's Important issue is correct. I verified both legs at The finding is confirmed, and it reaches plugins (not just the UI)
Leg 3 is the one worth being explicit about, because it's easy to talk yourself out of it. Net: if the create transaction rolls back after One extra hazard Ally didn't name: the outbox insert is fire-and-forget with a swallowed error ( This is pre-existing, which I think should shape the fix
So #953 is the fourth site, not the origin. The file's own doc comment ( Fix shape I'd suggest
That keeps the property #953 is actually after — a replay must not emit a second card, and a first filing must not be silently card-less — without letting either effect precede commit.
CIAlly's Recommended Action #2 — I've done this part. The Why I'm not pushing this myself#953 exists because #945 was authored by So: happy to implement exactly the above and hand you a patch or a branch you can cherry-pick, if you'd rather not write it — just say. I haven't re-requested Ally, since there's no new head and a re-request would burn another ~50-minute pass for nothing. Refs: BLO-19132. |
|
Not a review — a CI status correction, because the last word on this PR is now wrong and has been for ~15 hours. The re-run kicked off at 12:01Z (30738540290) has since finished, and head
Everything else on that run passed — So the "no known test failure at Three things are outstanding here, all yours to sequence:
Flagging rather than acting: this branch is yours and its independent authorship is the whole reason it exists, so nothing has been pushed here. CEO — refs BLO-19132. |
…e's own style (BLO-20886) Review follow-up on the owning-reference rule. The body tier anchored the Fixes:/Closes:/Resolves:/Refs: keyword to the start of the line, but .github/PULL_REQUEST_TEMPLATE.md renders "## Linked Issues or Issue Description" as a bullet list, so the repo's house style for an owning reference is `- Refs: BLO-1`. PR #953 -- the live misroute this rule exists to fix -- writes exactly `- Refs: [BLO-19132](...)`. Replaying #953's verbatim body against the resolver showed the body tier matching nothing: it resolved correctly only because the PR title happened to carry `(BLO-19132)`. Any PR whose title omits the ref and whose body uses the template's bullet style would have failed closed to `no_owning_reference`, silently dropping an author wake that should have been delivered to its owner. The pre-existing test did not catch this because its fixture synthesizes a bare `Refs: BLO-19132` line rather than the bulleted shape the live payload actually has. Allow an optional leading list marker (-, *, +, or `1.`). `Related:` still never resolves as owning, bulleted or not. Tests: github-webhook.test.ts 112 passed (new bulleted-reference case, including #953's verbatim body); heartbeat-context-summary.test.ts 83 passed; server tsc --noEmit clean. Co-Authored-By: Claude <noreply@anthropic.com>
|
@kkroo — two updates that should shrink your remaining work on this to a single action. Not repeating the checklist from my 03:24Z comment above; this supersedes part of it. 1. Same artifact class as the 2. Ally's Important finding is being fixed upstream, not here. I opened #1024 against I have deliberately not pushed to this branch. Its independent authorship is the whole reason it exists — a commit from the bot identity that reviews it would destroy exactly the property that got you a real review — so that call stays yours, and the fix went somewhere it doesn't cost you anything. Your remaining action, once #1024 merges: rebase onto master. That single step clears three things at once — it resolves the current If you'd rather not wait on #1024, say so and I'll hand you the patch as a branch you can cherry-pick under your own authorship. Happy either way — flag whichever you prefer. |
…sting The board-approval queue had a create/read asymmetry: filing a duplicate ask was cheaper than checking whether one already existed, and a pending approval emitted nothing back to its requester, so retrying was the only way to learn anything. Three asks for one PR review landed inside 73 minutes because of it. - `createApprovalSchema` gains an optional `idempotencyKey`. A retry with the same key, from the same requester, while the original is still undecided, replays the original (HTTP 200, `deduplicated: true`) instead of filing a second card. Scope is (company, requester, key) over the undecided statuses only — once the board answers, the key is released, because re-asking after a decision is a legitimately new request. - Race safety via `pg_advisory_xact_lock` before the lookup, matching the issue-create path; two partial unique indexes are the backstop. - The replay response carries a `statusReadback` line, so a requester learns the original is still pending without re-filing to find out. - `GET /companies/:id/approvals` gains `view=count` and `view=summary` plus `type` / `issueId` / `requestedByAgentId` / `idempotencyKey` filters. Summary omits the payload body and derives an always-populated `label`, so a pre-file existence check costs a fraction of a re-file. Measured on the live 63-row pending queue: full 213.2 KB, summary 22.5 KB (9x), count 12 bytes. - The derived label falls back through title -> summary -> description -> type plus short id, and treats a literal `***REDACTED***` as absent, so an approval whose title was blanked by the field-name redactor still renders something a human can triage. The redactor itself is BLO-20810, not this change. Default `view=full` behaviour is unchanged. Refs BLO-19132 Co-Authored-By: Claude <noreply@anthropic.com>
…sites Self-review follow-up on the dedupe change. The create-side lookup used a locally-declared status set while the partial unique indexes hardcoded the same set in SQL, in two places (the drizzle schema and migration 0208). If those ever drift, an idempotent replay stops matching and the insert hits a raw unique violation — a 500 where the whole point was to return the original. - `APPROVAL_UNDECIDED_STATUSES` in shared constants is now the single source of truth, consumed by `approvalService`'s `canResolveStatuses`. - The migration is frozen history and the drizzle schema must mirror it verbatim, so neither imports the constant. An eager cross-package import at schema module scope would also take the entire db schema down if it ever failed to resolve, which is a worse failure than the drift it prevents. - Instead two tests read the migration SQL and the schema file and assert both agree with the constant. Mutation-checked: widening either site alone fails its own test and only its own test. No behaviour change. Refs BLO-19132 Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
d9f28c1 to
4b00481
Compare
|
@ally please re-review exact head |
allyblockcast
left a comment
There was a problem hiding this comment.
Approved exact head 4b00481. Rebased onto current master, migration renumbered to 0210, and the prior transactional approval-created finding is addressed by deferring logActivity publication until after createWithIdempotency returns. Local focused route/service/shared tests and server typecheck passed; no unresolved review threads.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 4b00481
Looks good. The prior transactional publication blocker is fixed, and no new Critical or Important issues remain.
Prior Findings Dispositioned (1)
- prior:d9f28c1 important 1 — fixed —
server/src/routes/approvals.ts:219—logActivitynow records withdeferPublish: trueinside the create transaction, and the returned publisher is flushed only aftercreateWithIdempotencyreturns at line 252; rollback therefore cannot emit a phantomapproval.createdevent.
Suggestions (1)
- [pr-review-toolkit]
server/src/services/approvals.ts:64— Consider bounding the derived summary label length so an unusually large payload title cannot make the cheap summary response unexpectedly large.
Strengths
- Requester-scoped advisory locking and partial unique indexes provide coherent retry behavior under concurrency.
- Count, summary, and full views share one filter builder, while summary labels pass through approval-type redaction and omit raw payloads.
- Route regressions cover post-commit publication and rollback without publication.
Recommended Action
- Merge after the required CI checks complete successfully.
…, not an arbitrary Related: backlink (BLO-20886) extractPaperclipIdentifiers treated every BLO-#### token in a PR's branch/title/body as equally-weighted, so the author-directed wake loop (prRole: "author") fired for the assignee of EVERY matched issue -- including ones named only under an informational `Related:` list. Live incident: PR #953 carried `Refs: BLO-19132` (the true owner) plus `Related: BLO-20810, BLO-20129, BLO-19079`; CTO, assignee of BLO-20129 (the third Related: entry), got a wake asserting "a reviewer just posted findings on YOUR pull request" and instructing a push -- to a human contributor's PR with zero recorded reviews. Fix: - paperclip-identifiers.ts: resolveOwningPaperclipIdentifiers() resolves the PR's owning issue(s) via branch > title > labeled Fixes:/Closes:/Resolves:/ Refs: body line (colon optional, matching this repo's existing PR-body convention). A bare/Related: mention is never owning. - github-webhook.ts: the author-wake loop is now restricted to the owning issue(s) only. `matched` (the full identifier set) is untouched for the back-link comment and merged-PR forward-capture, which are informational and correctly link every mentioned issue. When no owning issue resolves, the wake is dropped with a logged suppressionReason (no_owning_reference) instead of falling through to a lower-priority or Related: mention. - heartbeat.ts: the author directive text ("YOUR pull request" / "push a follow-up commit") now only renders for wakeReasons that structurally guarantee review content exists (github_pr_review_submitted, github_pr_review_feedback). github_pr_review_requested and plain PR lifecycle events get a neutral directive stating what's actually known, with an explicit instruction not to push on unconfirmed feedback. Selection rule (per BLO-20886's acceptance criteria): branch ref outranks title ref outranks a labeled Fixes:/Closes:/Resolves:/Refs: body line; Related: and unlabeled mentions never count as owning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e's own style (BLO-20886) Review follow-up on the owning-reference rule. The body tier anchored the Fixes:/Closes:/Resolves:/Refs: keyword to the start of the line, but .github/PULL_REQUEST_TEMPLATE.md renders "## Linked Issues or Issue Description" as a bullet list, so the repo's house style for an owning reference is `- Refs: BLO-1`. PR #953 -- the live misroute this rule exists to fix -- writes exactly `- Refs: [BLO-19132](...)`. Replaying #953's verbatim body against the resolver showed the body tier matching nothing: it resolved correctly only because the PR title happened to carry `(BLO-19132)`. Any PR whose title omits the ref and whose body uses the template's bullet style would have failed closed to `no_owning_reference`, silently dropping an author wake that should have been delivered to its owner. The pre-existing test did not catch this because its fixture synthesizes a bare `Refs: BLO-19132` line rather than the bulleted shape the live payload actually has. Allow an optional leading list marker (-, *, +, or `1.`). `Related:` still never resolves as owning, bulleted or not. Tests: github-webhook.test.ts 112 passed (new bulleted-reference case, including #953's verbatim body); heartbeat-context-summary.test.ts 83 passed; server tsc --noEmit clean. Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts: # packages/db/src/migrations/meta/_journal.json # packages/db/src/schema/approvals.ts # server/src/__tests__/approvals-service.test.ts # server/src/services/approvals.ts
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: a1127d4
Looks good. The merge conflict resolution preserves the previously reviewed approval behavior, integrates the new linkedAgentId schema from master, and renumbers the idempotency migration and its drift test consistently to 0211.
Suggestions (1)
- [native-codex]
server/src/services/approvals.ts:354— Consider detecting reuse of an idempotency key with a different approval type or normalized payload. The current contract deliberately replays the original and permits additive issue links; a request fingerprint plus409 Conflictfor mismatched immutable fields would make accidental key collisions easier to diagnose.
Strengths
- Requester-scoped advisory locking and partial unique indexes provide coherent retry behavior under concurrency.
- The prior transactional publication blocker remains fixed: activity publication is deferred until the create transaction succeeds, with rollback/no-flush regression coverage.
- Count, summary, and full views share one filter builder, while summary labels pass through type-aware redaction and omit raw payloads.
- The synchronized head resolves the migration conflict cleanly: master owns
0210_approval_linked_agent, and this change consistently uses0211_approval_create_idempotency.
Recommended Action
- Merge after the required CI checks complete successfully.
allyblockcast
left a comment
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: a1127d4
Looks good. The merge conflict resolution preserves the previously reviewed approval behavior, integrates the new linkedAgentId schema from master, and renumbers the idempotency migration and its drift test consistently to 0211.
Suggestions (1)
- [native-codex]
server/src/services/approvals.ts:354— Consider detecting reuse of an idempotency key with a different approval type or normalized payload. The current contract deliberately replays the original and permits additive issue links; a request fingerprint plus409 Conflictfor mismatched immutable fields would make accidental key collisions easier to diagnose.
Strengths
- Requester-scoped advisory locking and partial unique indexes provide coherent retry behavior under concurrency.
- The prior transactional publication blocker remains fixed: activity publication is deferred until the create transaction succeeds, with rollback/no-flush regression coverage.
- Count, summary, and full views share one filter builder, while summary labels pass through type-aware redaction and omit raw payloads.
- The synchronized head resolves the migration conflict cleanly: master owns
0210_approval_linked_agent, and this change consistently uses0211_approval_create_idempotency.
Recommended Action
- Merge after the required CI checks complete successfully.
…, not an arbitrary Related: backlink (BLO-20886) extractPaperclipIdentifiers treated every BLO-#### token in a PR's branch/title/body as equally-weighted, so the author-directed wake loop (prRole: "author") fired for the assignee of EVERY matched issue -- including ones named only under an informational `Related:` list. Live incident: PR #953 carried `Refs: BLO-19132` (the true owner) plus `Related: BLO-20810, BLO-20129, BLO-19079`; CTO, assignee of BLO-20129 (the third Related: entry), got a wake asserting "a reviewer just posted findings on YOUR pull request" and instructing a push -- to a human contributor's PR with zero recorded reviews. Fix: - paperclip-identifiers.ts: resolveOwningPaperclipIdentifiers() resolves the PR's owning issue(s) via branch > title > labeled Fixes:/Closes:/Resolves:/ Refs: body line (colon optional, matching this repo's existing PR-body convention). A bare/Related: mention is never owning. - github-webhook.ts: the author-wake loop is now restricted to the owning issue(s) only. `matched` (the full identifier set) is untouched for the back-link comment and merged-PR forward-capture, which are informational and correctly link every mentioned issue. When no owning issue resolves, the wake is dropped with a logged suppressionReason (no_owning_reference) instead of falling through to a lower-priority or Related: mention. - heartbeat.ts: the author directive text ("YOUR pull request" / "push a follow-up commit") now only renders for wakeReasons that structurally guarantee review content exists (github_pr_review_submitted, github_pr_review_feedback). github_pr_review_requested and plain PR lifecycle events get a neutral directive stating what's actually known, with an explicit instruction not to push on unconfirmed feedback. Selection rule (per BLO-20886's acceptance criteria): branch ref outranks title ref outranks a labeled Fixes:/Closes:/Resolves:/Refs: body line; Related: and unlabeled mentions never count as owning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e's own style (BLO-20886) Review follow-up on the owning-reference rule. The body tier anchored the Fixes:/Closes:/Resolves:/Refs: keyword to the start of the line, but .github/PULL_REQUEST_TEMPLATE.md renders "## Linked Issues or Issue Description" as a bullet list, so the repo's house style for an owning reference is `- Refs: BLO-1`. PR #953 -- the live misroute this rule exists to fix -- writes exactly `- Refs: [BLO-19132](...)`. Replaying #953's verbatim body against the resolver showed the body tier matching nothing: it resolved correctly only because the PR title happened to carry `(BLO-19132)`. Any PR whose title omits the ref and whose body uses the template's bullet style would have failed closed to `no_owning_reference`, silently dropping an author wake that should have been delivered to its owner. The pre-existing test did not catch this because its fixture synthesizes a bare `Refs: BLO-19132` line rather than the bulleted shape the live payload actually has. Allow an optional leading list marker (-, *, +, or `1.`). `Related:` still never resolves as owning, bulleted or not. Tests: github-webhook.test.ts 112 passed (new bulleted-reference case, including #953's verbatim body); heartbeat-context-summary.test.ts 83 passed; server tsc --noEmit clean. Co-Authored-By: Claude <noreply@anthropic.com>
Thinking Path
Linked Issues or Issue Description
What Changed
idempotencyKeyto approval create so a retry from the same authenticated requester replays the undecided approval instead of filing a duplicate.view=count|summary|fullapproval listing support with consistentstatus,type,issueId,requestedByAgentId, andidempotencyKeyfiltering across every view.labelin application code after the same approval-payload redaction path used by full responses.requestedByAgentId; user actors populaterequestedByUserId; body-supplied requester IDs do not create dual-identity rows.approval.createdactivity logging inside the create transaction through anafterCreatehook. Idempotent replays still attach newly supplied issue links to the original approval without emitting another activity card.Review Fixes Since #945
countandfullviews.Verification
Risks
requestedByAgentId; requester identity is now the authenticated user. This is intentional to keep idempotency scope and persisted attribution coherent.Model Used
GPT-5 Codex (
gpt-5-codex), using local repo inspection,gh, and focused test/typecheck runs.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template