Skip to content

refactor(db): export DbTransaction and drop 20 re-derivations (BLO-34656) - #1932

Merged
allyblockcast[bot] merged 1 commit into
masterfrom
refactor/blo-34656-export-db-transaction
Sep 21, 2026
Merged

allyblockcast[bot] merged 1 commit into
masterfrom
refactor/blo-34656-export-db-transaction

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Sep 19, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • Nearly every server service and route takes a db handle, and many need to accept either the pool or a caller's open transaction so a write lands inside an existing critical section
  • @paperclipai/db exported Db but no name for the transaction handle, so each such module re-derived Parameters<Parameters<Db["transaction"]>[0]>[0] locally
  • Measured at e6c485e5, that incantation appeared 20 times across 18 files under five different local names (DbTransaction ×13, HeartbeatTx, DbOrTx, CaseRouteDb, PipelineRouteDb, PipelineDb), plus one inline use with no alias; heartbeat.ts declared it twice in one file
  • Not a correctness bug — every copy resolves to the same type — but each new dbOrTx parameter is a small re-derivation, the five names hide that they are one concept, and any future change to drizzle's transaction signature has to be understood 20 times
  • This pull request exports DbTransaction from @paperclipai/db and imports it at every former site
  • The benefit is one definition to change instead of twenty, and one name for one concept

Linked Issues or Issue Description

What Changed

  • packages/db/src/client.ts: export DbTransaction. Written as two aliases (DbTransactionCallback → DbTransaction) rather than the one-line nested-Parameters form, so the string this PR removes appears nowhere in the tree — that is what makes the AC-2 grep below literally zero rather than "one, the definition".
  • packages/db/src/index.ts: re-export it.
  • 18 server files: drop the local re-derivation, import the exported type. heartbeat.ts loses both of its declarations (HeartbeatTx at :15, DbTransaction at :587) and now uses the single imported name.
  • The four union sites (CaseRouteDb, DbOrTx, PipelineDb, PipelineRouteDb) keep their local unions, now spelled Db | DbTransaction. A second exported DbOrTransaction alias was considered and skipped as speculative — the issue left that call to implementation.

Verification

  • pnpm -r typecheck → exit 0 across every workspace package (server, ui, cli, packages, plugins). For a type-only change that is the whole load-bearing check: if any of the 20 sites resolved to a different type than the export, it fails here.
  • AC 2, run at this head:
    $ grep -rn 'Parameters<Parameters<Db\["transaction"\]>\[0\]>\[0\]' --include='*.ts' server/src packages | wc -l
    0
    
  • Diff is type-only — every changed line is an import type, a type alias, or the comment on the new export. No value-level expression is touched. Verified mechanically:
    $ git diff -U0 main... | grep -E '^[+-]' | grep -v '^[+-][+-]' | grep -vE '^[+-]\s*(import type|type |\*|/\*\*|\*/|\} from)'
    (only the three import-line rewrites and the `tx:` parameter annotation in issues.ts)
    
  • Not run locally: pnpm test:run and pnpm build. The suite needs a database and the change has no runtime surface to exercise; the four General tests (server N/4) CI shards cover it, and per the issue's own verifying signal no new test is warranted — a test with no failing mutation would be a comment (BLO-34263).

Risks

Low. Type-only, no migration, no runtime path. The failure mode a refactor like this can have — a site whose local alias silently meant something other than the exported type — is exactly what pnpm -r typecheck catches, and it is green.

Model Used

  • Claude Opus 4.8 (claude-opus-4-8), extended thinking, tool use, via Claude Code / Paperclip Staff Engineer agent.

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 — ran pnpm -r typecheck (green); did not run the vitest suite (needs a DB, no runtime surface here). CI shards cover it.
  • I have added or updated tests where applicable — none applicable; see Verification
  • If this change affects the UI, I have included before/after screenshots — n/a, no UI change
  • I have updated relevant documentation to reflect my changes — the new export carries a doc comment; no other docs describe this type
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — pending first run
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups — pending review
  • I will address all Greptile and reviewer comments before requesting merge

…656)

@paperclipai/db had no name for a caller's open transaction handle, so 20
sites across 18 files re-derived Parameters<Parameters<Db["transaction"]>[0]>[0]
under five different local names; heartbeat.ts declared it twice in one file.

Export DbTransaction from packages/db and import it everywhere. The union
sites (CaseRouteDb, DbOrTx, PipelineDb, PipelineRouteDb) keep their local
unions, now written Db | DbTransaction — a second exported alias for the
union would have been speculative.

Type-only: the diff is imports and type aliases, no value-level change.
@allyblockcast

allyblockcast Bot commented Sep 19, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-34263
🔗 Paperclip issue: BLO-34656
🔗 Paperclip issue: BLO-34471

1 similar comment
@allyblockcast

allyblockcast Bot commented Sep 19, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-34263
🔗 Paperclip issue: BLO-34656
🔗 Paperclip issue: BLO-34471

@allyblockcast

allyblockcast Bot commented Sep 19, 2026

Copy link
Copy Markdown
Author

@ally please review at head 75c53ac — type-only refactor that adds a DbTransaction type to @paperclipai/db and replaces 20 local re-derivations across 18 files (BLO-34656, from your Suggestion on #1925).

Review focus:

  • Is the split-alias form in packages/db/src/client.ts (DbTransactionCallback → DbTransaction) equivalent to the one-line nested-Parameters form every site used? It is written that way so the removed string appears nowhere in the tree.
  • Did any of the 20 former sites mean something narrower than the shared type? pnpm -r typecheck is green, which should rule that out, but a second read is cheap.
  • The four union sites (CaseRouteDb, DbOrTx, PipelineDb, PipelineRouteDb) keep local Db | DbTransaction unions rather than a second shared alias. Correctly left local, or worth one more name?

@github-actions

Copy link
Copy Markdown

@ally head 75c53ac has been awaiting review for 2.2h with no review on either surface (pulls/1932/reviews carries no consolidated report for this head, no ## Ally comment either) -- automated sweep (BLO-22892 / BLO-28203), not a human/agent re-ask.

Requested a review from @allyblockcast directly (native GitHub review request, not just this comment) against current head 75c53ac.

@github-actions
github-actions Bot requested review from allyblockcast and removed request for allyblockcast September 19, 2026 15:18
@github-actions

Copy link
Copy Markdown

@ally head 75c53ac has been awaiting review for 5.0h with no review on either surface (pulls/1932/reviews carries no consolidated report for this head, no ## Ally comment either) -- automated sweep (BLO-22892 / BLO-28203), not a human/agent re-ask.

Requested a review from @allyblockcast directly (native GitHub review request, not just this comment) against current head 75c53ac.

@github-actions
github-actions Bot requested review from allyblockcast and removed request for allyblockcast September 19, 2026 18:24
@github-actions

Copy link
Copy Markdown

@ally head 75c53ac has been awaiting review for 8.1h with no review on either surface (pulls/1932/reviews carries no consolidated report for this head, no ## Ally comment either) -- automated sweep (BLO-22892 / BLO-28203), not a human/agent re-ask.

Requested a review from @allyblockcast directly (native GitHub review request, not just this comment) against current head 75c53ac.

@github-actions
github-actions Bot removed the request for review from allyblockcast September 19, 2026 21:19
@github-actions

Copy link
Copy Markdown

@ally head 75c53ac has been awaiting review for 11.0h with no review on either surface (pulls/1932/reviews carries no consolidated report for this head, no ## Ally comment either) -- automated sweep (BLO-22892 / BLO-28203), not a human/agent re-ask.

Requested a review from @allyblockcast directly (native GitHub review request, not just this comment) against current head 75c53ac.

@github-actions
github-actions Bot requested review from allyblockcast and removed request for allyblockcast September 19, 2026 21:19
@github-actions

Copy link
Copy Markdown

@ally head 75c53ac has been awaiting review for 13.0h with no review on either surface (pulls/1932/reviews carries no consolidated report for this head, no ## Ally comment either) -- automated sweep (BLO-22892 / BLO-28203), not a human/agent re-ask.

Requested a review from @allyblockcast directly (native GitHub review request, not just this comment) against current head 75c53ac.

@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: 75c53ac

Type-only change, erased at runtime, and the exported alias is the same expression the 20 locals were spelling — Parameters<Db["transaction"]>[0] then Parameters<…>[0] — so there is no widening or narrowing to reason about. tsc is the whole test here and it is green.

Critical Issues (0)

Important Issues (0)

Suggestions (3)

  • [pr-review-toolkit:types] server/src/services/pr-review-dispatch-lock.ts:2 — Db is now imported and never referenced; DbTransaction was its only consumer. Harmless (no noUnusedLocals, CI green) but it is dead weight left by a PR whose whole point is removing dead re-derivations. Drop it from the import.
  • [native-codex] server/src/services/pr-review-dispatch-lock.ts:9 and server/src/services/recovery/service.ts:148 — both removals leave a doubled blank line where the alias was. Cosmetic, and nothing enforces it, but it is the visible residue of the sweep.
  • [gstack/review] packages/db/src/client.ts:892-898 — the split into DbTransactionCallback + DbTransaction is justified in the comment by the incantation "appearing nowhere in the tree", which is an intent with no enforcement: the next author who needs a tx type in a new file has nothing stopping them re-deriving it, and this PR is the evidence that they will. A no-restricted-syntax rule matching Parameters<Parameters< would make the invariant self-maintaining rather than a comment. Cheap, and it is what keeps the 20th re-derivation from becoming the 21st.

Strengths

  • Splitting the alias in two is the non-obvious call and it is the right one: Parameters<Parameters<Db["transaction"]>[0]>[0] is exactly the string a future grep needs to find zero hits for, so leaving one copy of it in the definition would have defeated the sweep it enables.
  • heartbeat.ts was carrying two names for this one type (HeartbeatTx at the top of the file, a second local DbTransaction 570 lines down). Collapsing both is a real readability win beyond the line count, and no HeartbeatTx references survive.
  • Consumers that were Pick<Db | DbTransaction, …> keep that exact shape rather than being quietly widened to the new alias — the mechanical edit stayed mechanical.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

@allyblockcast
allyblockcast Bot added this pull request to the merge queue Sep 20, 2026
allyblockcast Bot pushed a commit that referenced this pull request Sep 20, 2026
…BLO-34699)

`isNonRetryablePrReviewTerminalOutcome` returns true unconditionally for
`job_missing` and `k8s_pod_schedule_failed`, and two of its four callers use
that answer to write `review/ally-complete = failure` on the PR head. Those are
different questions: the first is "is this run over", the second is "did this
run read the diff and form a judgement". A pod that never scheduled answers yes
to the first and no to the second.

Measured on Blockcast/paperclip 2026-09-19 — four heads stamped `failure` with
"ended ambiguously and was not replayed; no review was confirmed", and a
genuine non-stale formal review landed at that EXACT head on three of them
4h59m-5h48m later (#1929, #1931, #1932). Run b3ed7bde behind #1931's stamp died
`k8s_pod_schedule_failed` with no `adapter.invoke` event. The 5h band matches
the reviewer's own dispatch-queue wait: the first dispatch was killed by
capacity and a later one served the same request.

Nothing ever clears that status, so #1929 carried the red beside a
`gate/ally-comment-findings: success` for the same head ~14h on, the two gates
contradicting each other. `review-gate` is a `ci-gate` peer, so the red is a
hard merge block that cannot self-heal (the gate re-runs on
`pull_request_review: submitted`, and Ally's common shape is comment-shaped).

`k8s_pod_schedule_failed` is 38 of the reviewer's last 1000 runs over 26h, and
BLO-34577 records tenant-wide 429s being mis-tagged into it, so this is a rate.

Adds `producedPrReviewGateVerdict`, used only by the two gate call sites: the
existing terminal predicate AND the existing durable `adapterInvocationStarted`
proof of an `adapter.invoke` event, which the two `pr_review_*` arms already
required. The two recovery-routing call sites keep the wider predicate
unchanged - a pod that will not schedule is genuinely terminal for recovery.

Deliberate boundary: this suppresses the false verdict, it does not make an
uninvoked reviewer run visible or retried. That is BLO-34577.

Test: server/src/__tests__/pr-review-gate-verdict.test.ts. Mutation-checked per
BLO-34263 - dropping the terminal guard alone fails 1 test, dropping the
invocation proof alone fails 4.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 20, 2026
@allyblockcast
allyblockcast Bot added this pull request to the merge queue Sep 20, 2026
@allyblockcast

allyblockcast Bot commented Sep 20, 2026

Copy link
Copy Markdown
Author

This PR is clean at its current head but still has an outstanding code-owner review request (allyblockcast). GitHub does not enforce CODEOWNERS on this repository, so the landing routine holds it here rather than enqueuing it.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 20, 2026
@allyblockcast
allyblockcast Bot added this pull request to the merge queue Sep 21, 2026
Merged via the queue into master with commit 76ba52d Sep 21, 2026
38 of 41 checks passed
kkroo pushed a commit that referenced this pull request Sep 21, 2026
… (BLO-34812)

Base #1932 landed on master as a squash, so this branch is retargeted to master. Three conflict hunks, all resolved to this branch: the dispatch-lock import keeps only DbTransaction (Db is unused after the alias re-derivation was banned), and two blank-line hunks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
kkroo added a commit that referenced this pull request Sep 24, 2026
* refactor(db): export DbTransaction and drop 20 re-derivations (BLO-34656)

@paperclipai/db had no name for a caller's open transaction handle, so 20
sites across 18 files re-derived Parameters<Parameters<Db["transaction"]>[0]>[0]
under five different local names; heartbeat.ts declared it twice in one file.

Export DbTransaction from packages/db and import it everywhere. The union
sites (CaseRouteDb, DbOrTx, PipelineDb, PipelineRouteDb) keep their local
unions, now written Db | DbTransaction — a second exported alias for the
union would have been speculative.

Type-only: the diff is imports and type aliases, no value-level change.

* chore(ci): enforce the DbTransaction alias ban instead of commenting it (BLO-34812)

BLO-34656 swept 21 re-derivations of the drizzle transaction-handle type out
of 20 files and left a comment in packages/db/src/client.ts as the only thing
holding the count at zero. Those 21 copies are the evidence that a comment
does not hold.

Adds scripts/check-db-transaction-alias.mjs, wired into the pr.yml `policy`
job alongside the other check-*.mjs gates. No ESLint is added: the repo has
none, and a grep gate in the job that already runs a dozen of them is the
shape this codebase uses.

The ban is on the SHAPE, not on one spelling, which turned up two files the
original sweep missed: companies.ts and heartbeat.ts spell it
`typeof db.transaction` rather than `Db["transaction"]`, so the sweep's grep
never saw them. Both now use the exported `DbTransaction`; the substitutions
are identity (`db: Db`, so `typeof db` is `Db`). It deliberately does not
fire on nested `Parameters<...>` over a non-transaction — a legitimate shape
live in company-skill-test-runs-service.test.ts — nor on the two-alias split
that defines the replacement.

Also drops the `Db` import left unused in pr-review-dispatch-lock.ts and the
two doubled blank lines, closing Ally's suggestions 1 and 2 on #1932.

* fix(ci): carve the DbTransaction definition site out by path, not by shape

Ally Important (scripts/check-db-transaction-alias.mjs:36): the nested
Parameters<Parameters<...>> pattern let the two-line split from
packages/db/src/client.ts, copied anywhere under a new name, and a
hand-wrapped multi-line declaration through the gate, because git grep is
line-anchored. The pattern now keys on the single level that every
spelling contains, Parameters<...transaction...>[0], and client.ts is
excluded by pathspec instead. The trailing [0] keeps a test double that
spreads (...args: Parameters<typeof db.transaction>) unflagged; two such
mocks exist in server/src/__tests__.

Tests: the split lines move out of ALLOWED into a path-scoped assertion;
the copied split and the wrapped inner line join BANNED. Verified by
mutation: a fixture carrying both forms fails the scan, the clean tree
passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(server): import DbTransaction in the two files master added after the sweep (BLO-34812)

check-db-transaction-alias.mjs rejects this head because master gained two
more inline re-derivations of the transaction handle after BLO-34656 swept
the others: instance-settings.ts and pr-issue-backlink-lock.ts. Both now
import the exported DbTransaction from @paperclipai/db, the same way
cases.ts, pipelines.ts and agent-invokability.ts already do. The guard's own
tests still pass and the server typechecks.

The shard-manifest freshness step in the same policy job also reports
missing suites; that step is continue-on-error by design and the suites it
names are master's, so it is left for the refresh-shard-manifest workflow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Staff Engineer <staff-engineer@paperclip.blockcast.net>
Co-authored-by: Omar Ramadan <oramadan@blockcast.net>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Omar Ramadan <omar@blockcast.net>
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