Skip to content

Commit d43bfca

Browse files
committed
docs(skill): add committed-binary gate and store-and-forward invariants to review-pr
Add a committed-binary gate (Critical at every level, since native libs are built from source in CI) and a store-and-forward & pool startup invariants checklist (drainer must never impose a reconnect budget or hard-fail on a transient outage; lazy_connect vs default startup boundaries) to the review-pr skill. Mirror the changes across the .claude and .pi copies.
1 parent b96914f commit d43bfca

2 files changed

Lines changed: 170 additions & 6 deletions

File tree

.claude/skills/review-pr/SKILL.md

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,18 @@ Capture the PR identifier in `$PR` (the part of `$ARGUMENTS` left after strippin
5151
PR='<PR number or URL from $ARGUMENTS, with any --level=N / -lN / bare-digit level token removed>'
5252
gh pr view "$PR" --json number,title,body,labels,state
5353
gh pr diff "$PR"
54+
gh pr diff "$PR" --numstat # binary files show as `-<TAB>-<TAB><path>`
5455
gh pr view "$PR" --comments
5556
```
5657

58+
**Committed-binary gate (runs at every level).** Scan the `--numstat` output for
59+
any added/modified file git reports as binary (`-`/`-` in the added/deleted
60+
columns). This repo builds its native/C libraries from source in CI and does not
61+
commit build outputs, so any such file is a **Critical** finding regardless of
62+
review level — report it even at level 0. See the "Committed build artifacts"
63+
checklist for the rationale and the acceptable-exception (genuine test-input
64+
fixtures only).
65+
5766
## Step 2: PR title and description
5867

5968
Check against CLAUDE.md conventions:
@@ -144,7 +153,7 @@ Every agent receives:
144153

145154
Launch the following agents in parallel.
146155

147-
**Agent 1 — Correctness & bugs:** NULL handling, edge cases, logic errors, off-by-one, operator precedence, error paths. Cross-reference every changed symbol against its callsite inventory and verify the new behavior is correct at each callsite.
156+
**Agent 1 — Correctness & bugs:** NULL handling, edge cases, logic errors, off-by-one, operator precedence, error paths. Cross-reference every changed symbol against its callsite inventory and verify the new behavior is correct at each callsite. When the diff touches the store-and-forward sender, the async drainer / send loop, primary reconnect/failover, or pool startup (`lazy_connect` / `initial_connect_retry` / `SenderPool` / `QueryClientPool`), also verify the "Store-and-forward & pool startup invariants" checklist — a running drainer that propagates a transport error to the caller, imposes a reconnect time budget, or hard-fails on a transient outage is a Critical (data-loss) finding.
148157

149158
**Agent 2 — Concurrency:** Race conditions, shared mutable state, missing volatile, lock ordering, thread-safety of data structures. Use the implicit contract list (lock order, thread-affinity) and check every callsite from 2.5b for violations of the new contract.
150159

@@ -154,7 +163,7 @@ Launch the following agents in parallel.
154163

155164
**Agent 5 — Test coverage:** Coverage gaps, error path tests, NULL tests, boundary conditions, regression tests exist, `assertMemoryLeak()` usage. Cross-reference 2.5d: every cross-context exposure should have a test that exercises the changed symbol from that context. Missing tests for cross-context callsites is a high-priority finding. Test *efficacy* (whether those tests actually exercise the change and could fail) and test-*code* quality are handled by Agents 11-13 — here focus only on whether coverage exists for every new or changed path.
156165

157-
**Agent 6 — Code quality & standards:** Code smell, member ordering, naming conventions, modern Java features, dead code, third-party dependencies.
166+
**Agent 6 — Code quality & standards:** Code smell, member ordering, naming conventions, modern Java features, dead code, third-party dependencies. Also scan the diff for any committed compiled binary / build artifact (run `git diff --numstat`/`--stat` and flag files git reports as binary) — the native/C libraries are built from source in CI, so a committed binary is a **Critical** finding (see the "Committed build artifacts" checklist).
158167

159168
**Agent 7 — PR metadata & conventions:** Title format, description quality, commit messages, labels, SQL style in tests.
160169

@@ -278,6 +287,26 @@ Review the diff for:
278287
- Code smell: overly complex methods, deep nesting, unclear intent, dead code
279288
- No third-party Java dependencies on data paths
280289

290+
### Committed build artifacts
291+
- **A newly committed compiled binary is always Critical.** This repo builds its
292+
native/C libraries from source in CI (`rebuild_native_libs.yml`,
293+
`build_native.yaml`, guarded by `check-glibc-floor.sh`) and does not commit
294+
build outputs. A binary added or modified in the diff cannot be reviewed,
295+
audited, or reproduced from source, can smuggle in unaudited or malicious
296+
code, and bloats the repo history irreversibly — so it blocks the merge.
297+
- Detect it structurally, not by extension alone: run `git diff --stat` /
298+
`git diff --numstat` on the PR and flag every added/modified file git reports
299+
as binary (`numstat` shows `-`/`-` for added/deleted lines; `--stat` shows a
300+
`Bin … -> … bytes` marker). Typical offenders: `.so`, `.dylib`, `.dll`, `.a`,
301+
`.o`, `.lib`, `.exe`, `.class`, `.jar`, `.war`, `.wasm`, `.node`, `.bin`.
302+
- The finding stands even when the binary "looks" legitimate (e.g. a rebuilt
303+
`libquestdb.*`): the correct source of these artifacts is the CI native-build
304+
pipeline plus release packaging, never a PR diff. The only acceptable binaries
305+
are genuine test-input fixtures/resources (data a test reads), not build
306+
outputs — and even those must be justified.
307+
- Suggested fix: drop the binary from the PR, confirm a `.gitignore` entry
308+
covers it, and let CI native-build + release packaging produce it.
309+
281310
### QuestDB coding standards
282311
- Class members grouped by kind (static vs instance) and visibility, sorted alphabetically
283312
- Boolean names use `is...` / `has...` prefix
@@ -288,6 +317,56 @@ Review the diff for:
288317
- try-with-resources used where applicable
289318
- Native memory freed correctly
290319

320+
### Store-and-forward & pool startup invariants (QWP facade)
321+
Apply this whenever the diff touches the SF sender, the async drainer / send
322+
loop, primary reconnect/failover, `SenderPool` / `QueryClientPool` startup,
323+
`lazy_connect`, or `initial_connect_retry`. A violation here is a **Critical**
324+
finding: the whole point of store-and-forward is that a running producer never
325+
loses data and never hard-fails on a transient outage.
326+
327+
**Drainer (steady state — once the pool is running).**
328+
- Once the pool is running, an async drainer thread ships buffered SF data to
329+
the server. It MUST NOT propagate server / transport errors back to the
330+
client (`Sender` producer calls, `flush()`, the pooled handle). The ONLY
331+
error a running drainer may surface to the caller is **SF out of space** (the
332+
on-disk / backing buffer is full and can accept no more rows). Flag any other
333+
failure class (connect-refused, DNS, unreachable/black-hole, TLS/cert, auth,
334+
role-reject, upgrade/protocol timeout, reset) that can escape the drainer
335+
onto a producer or borrow call.
336+
- Primary reconnect MUST be fully contained inside the drainer thread and MUST
337+
have **no time limit** — no `reconnect_max_duration_millis`-style budget, no
338+
deadline, no "give up and latch terminal after N ms". A budget that latches
339+
the sender terminal on a long outage is a Critical violation: it drops a
340+
producer that store-and-forward promised to keep alive. Flag any bounded
341+
reconnect loop, `deadlineNanos` / `while (now < deadline)`, or terminal
342+
`SenderError` reachable from the running drainer's reconnect path.
343+
- The drainer must retry with **exponential backoff** and handle every connect
344+
failure class gracefully, without a hard fail — it keeps buffering and keeps
345+
retrying until the wire is back. The per-attempt backoff may be capped (a max
346+
delay between attempts), but the RETRY LOOP ITSELF must be unbounded. Flag a
347+
capped total retry duration or an attempt-count cap on the steady-state
348+
drainer.
349+
350+
**Pool startup — two modes; the mode decides who sees connectivity errors.**
351+
- `lazy_connect=true`: `build()` MUST succeed with **no server present**. The
352+
producing `Sender` must work immediately (writes buffer via SF), and once the
353+
server comes up the read side must also connect and read (reads are deferred,
354+
not disabled). Verify `build()` does not fail-fast, the sender does not throw
355+
on the first write while the server is down, and a later `borrowQuery()`
356+
succeeds once the server is up.
357+
- `lazy_connect=false` (default): `build()` / the initial connect MUST expose
358+
connectivity problems to the caller — DNS errors, connect-refused /
359+
unreachable, TLS/cert, authentication/authorization, and connect/upgrade
360+
timeouts must all surface as a thrown exception at startup, not be swallowed.
361+
Verify each of those failure classes reaches the user during initialization.
362+
- **In BOTH modes the boundary is the same:** connectivity errors are only
363+
ever the caller's problem DURING initialization. Once the client has
364+
connected and is past initialization, the running drainer reverts to the
365+
steady-state contract above — it must NEVER expose transport problems, NEVER
366+
impose a reconnect time budget, and NEVER hard-fail on a transient outage.
367+
Anything that undermines the store-and-forward guarantee past init is
368+
Critical.
369+
291370
### SQL conventions (if tests or SQL involved)
292371
- Keywords in UPPERCASE
293372
- `expr::TYPE` cast syntax preferred over CAST()
@@ -340,7 +419,10 @@ Review the diff for:
340419
Present ONLY verified findings (false positives are excluded). Structure as:
341420

342421
### Critical
343-
Issues that must be fixed before merge. Each must include:
422+
Issues that must be fixed before merge. **A newly committed compiled binary or
423+
other build artifact (see the "Committed build artifacts" checklist) is always
424+
Critical, no matter how legitimate it looks — native/C libraries are built from
425+
source in CI, so a binary in the diff is never acceptable.** Each must include:
344426
- Exact file path and line numbers (including out-of-diff files)
345427
- Whether the finding is **in-diff** or **out-of-diff**
346428
- Code path trace showing why the bug is real

.pi/skills/review-pr/SKILL.md

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,18 @@ Capture the PR identifier in `$PR` (the part of `$ARGUMENTS` left after strippin
6060
PR='<PR number or URL from $ARGUMENTS, with any --level=N / -lN / bare-digit level token removed>'
6161
gh pr view "$PR" --json number,title,body,labels,state
6262
gh pr diff "$PR"
63+
gh pr diff "$PR" --numstat # binary files show as `-<TAB>-<TAB><path>`
6364
gh pr view "$PR" --comments
6465
```
6566

67+
**Committed-binary gate (runs at every level).** Scan the `--numstat` output for
68+
any added/modified file git reports as binary (`-`/`-` in the added/deleted
69+
columns). This repo builds its native/C libraries from source in CI and does not
70+
commit build outputs, so any such file is a **Critical** finding regardless of
71+
review level — report it even at level 0. See the "Committed build artifacts"
72+
checklist for the rationale and the acceptable-exception (genuine test-input
73+
fixtures only).
74+
6675
## Step 2: PR title and description
6776

6877
Check against CLAUDE.md conventions:
@@ -155,7 +164,7 @@ Launch the reviewers below with the `subagent` tool in `context: "fresh"` mode,
155164

156165
Launch the following reviewers in parallel.
157166

158-
**Reviewer 1 — Correctness & bugs:** NULL handling, edge cases, logic errors, off-by-one, operator precedence, error paths. Cross-reference every changed symbol against its callsite inventory and verify the new behavior is correct at each callsite.
167+
**Reviewer 1 — Correctness & bugs:** NULL handling, edge cases, logic errors, off-by-one, operator precedence, error paths. Cross-reference every changed symbol against its callsite inventory and verify the new behavior is correct at each callsite. When the diff touches the store-and-forward sender, the async drainer / send loop, primary reconnect/failover, or pool startup (`lazy_connect` / `initial_connect_retry` / `SenderPool` / `QueryClientPool`), also verify the "Store-and-forward & pool startup invariants" checklist — a running drainer that propagates a transport error to the caller, imposes a reconnect time budget, or hard-fails on a transient outage is a Critical (data-loss) finding.
159168

160169
**Reviewer 2 — Concurrency:** Race conditions, shared mutable state, missing volatile, lock ordering, thread-safety of data structures. Use the implicit contract list (lock order, thread-affinity) and check every callsite from 2.5b for violations of the new contract.
161170

@@ -165,7 +174,7 @@ Launch the following reviewers in parallel.
165174

166175
**Reviewer 5 — Test coverage:** Coverage gaps, error path tests, NULL tests, boundary conditions, regression tests exist, `assertMemoryLeak()` usage. Cross-reference 2.5d: every cross-context exposure should have a test that exercises the changed symbol from that context. Missing tests for cross-context callsites is a high-priority finding. Test *efficacy* (whether those tests actually exercise the change and could fail) and test-*code* quality are handled by Reviewers 11-13 — here focus only on whether coverage exists for every new or changed path.
167176

168-
**Reviewer 6 — Code quality & standards:** Code smell, member ordering, naming conventions, modern Java features, dead code, third-party dependencies.
177+
**Reviewer 6 — Code quality & standards:** Code smell, member ordering, naming conventions, modern Java features, dead code, third-party dependencies. Also scan the diff for any committed compiled binary / build artifact (run `git diff --numstat`/`--stat` and flag files git reports as binary) — the native/C libraries are built from source in CI, so a committed binary is a **Critical** finding (see the "Committed build artifacts" checklist).
169178

170179
**Reviewer 7 — PR metadata & conventions:** Title format, description quality, commit messages, labels, SQL style in tests.
171180

@@ -289,6 +298,26 @@ Review the diff for:
289298
- Code smell: overly complex methods, deep nesting, unclear intent, dead code
290299
- No third-party Java dependencies on data paths
291300

301+
### Committed build artifacts
302+
- **A newly committed compiled binary is always Critical.** This repo builds its
303+
native/C libraries from source in CI (`rebuild_native_libs.yml`,
304+
`build_native.yaml`, guarded by `check-glibc-floor.sh`) and does not commit
305+
build outputs. A binary added or modified in the diff cannot be reviewed,
306+
audited, or reproduced from source, can smuggle in unaudited or malicious
307+
code, and bloats the repo history irreversibly — so it blocks the merge.
308+
- Detect it structurally, not by extension alone: run `git diff --stat` /
309+
`git diff --numstat` on the PR and flag every added/modified file git reports
310+
as binary (`numstat` shows `-`/`-` for added/deleted lines; `--stat` shows a
311+
`Bin … -> … bytes` marker). Typical offenders: `.so`, `.dylib`, `.dll`, `.a`,
312+
`.o`, `.lib`, `.exe`, `.class`, `.jar`, `.war`, `.wasm`, `.node`, `.bin`.
313+
- The finding stands even when the binary "looks" legitimate (e.g. a rebuilt
314+
`libquestdb.*`): the correct source of these artifacts is the CI native-build
315+
pipeline plus release packaging, never a PR diff. The only acceptable binaries
316+
are genuine test-input fixtures/resources (data a test reads), not build
317+
outputs — and even those must be justified.
318+
- Suggested fix: drop the binary from the PR, confirm a `.gitignore` entry
319+
covers it, and let CI native-build + release packaging produce it.
320+
292321
### QuestDB coding standards
293322
- Class members grouped by kind (static vs instance) and visibility, sorted alphabetically
294323
- Boolean names use `is...` / `has...` prefix
@@ -299,6 +328,56 @@ Review the diff for:
299328
- try-with-resources used where applicable
300329
- Native memory freed correctly
301330

331+
### Store-and-forward & pool startup invariants (QWP facade)
332+
Apply this whenever the diff touches the SF sender, the async drainer / send
333+
loop, primary reconnect/failover, `SenderPool` / `QueryClientPool` startup,
334+
`lazy_connect`, or `initial_connect_retry`. A violation here is a **Critical**
335+
finding: the whole point of store-and-forward is that a running producer never
336+
loses data and never hard-fails on a transient outage.
337+
338+
**Drainer (steady state — once the pool is running).**
339+
- Once the pool is running, an async drainer thread ships buffered SF data to
340+
the server. It MUST NOT propagate server / transport errors back to the
341+
client (`Sender` producer calls, `flush()`, the pooled handle). The ONLY
342+
error a running drainer may surface to the caller is **SF out of space** (the
343+
on-disk / backing buffer is full and can accept no more rows). Flag any other
344+
failure class (connect-refused, DNS, unreachable/black-hole, TLS/cert, auth,
345+
role-reject, upgrade/protocol timeout, reset) that can escape the drainer
346+
onto a producer or borrow call.
347+
- Primary reconnect MUST be fully contained inside the drainer thread and MUST
348+
have **no time limit** — no `reconnect_max_duration_millis`-style budget, no
349+
deadline, no "give up and latch terminal after N ms". A budget that latches
350+
the sender terminal on a long outage is a Critical violation: it drops a
351+
producer that store-and-forward promised to keep alive. Flag any bounded
352+
reconnect loop, `deadlineNanos` / `while (now < deadline)`, or terminal
353+
`SenderError` reachable from the running drainer's reconnect path.
354+
- The drainer must retry with **exponential backoff** and handle every connect
355+
failure class gracefully, without a hard fail — it keeps buffering and keeps
356+
retrying until the wire is back. The per-attempt backoff may be capped (a max
357+
delay between attempts), but the RETRY LOOP ITSELF must be unbounded. Flag a
358+
capped total retry duration or an attempt-count cap on the steady-state
359+
drainer.
360+
361+
**Pool startup — two modes; the mode decides who sees connectivity errors.**
362+
- `lazy_connect=true`: `build()` MUST succeed with **no server present**. The
363+
producing `Sender` must work immediately (writes buffer via SF), and once the
364+
server comes up the read side must also connect and read (reads are deferred,
365+
not disabled). Verify `build()` does not fail-fast, the sender does not throw
366+
on the first write while the server is down, and a later `borrowQuery()`
367+
succeeds once the server is up.
368+
- `lazy_connect=false` (default): `build()` / the initial connect MUST expose
369+
connectivity problems to the caller — DNS errors, connect-refused /
370+
unreachable, TLS/cert, authentication/authorization, and connect/upgrade
371+
timeouts must all surface as a thrown exception at startup, not be swallowed.
372+
Verify each of those failure classes reaches the user during initialization.
373+
- **In BOTH modes the boundary is the same:** connectivity errors are only
374+
ever the caller's problem DURING initialization. Once the client has
375+
connected and is past initialization, the running drainer reverts to the
376+
steady-state contract above — it must NEVER expose transport problems, NEVER
377+
impose a reconnect time budget, and NEVER hard-fail on a transient outage.
378+
Anything that undermines the store-and-forward guarantee past init is
379+
Critical.
380+
302381
### SQL conventions (if tests or SQL involved)
303382
- Keywords in UPPERCASE
304383
- `expr::TYPE` cast syntax preferred over CAST()
@@ -351,7 +430,10 @@ Review the diff for:
351430
Present ONLY verified findings (false positives are excluded). Structure as:
352431

353432
### Critical
354-
Issues that must be fixed before merge. Each must include:
433+
Issues that must be fixed before merge. **A newly committed compiled binary or
434+
other build artifact (see the "Committed build artifacts" checklist) is always
435+
Critical, no matter how legitimate it looks — native/C libraries are built from
436+
source in CI, so a binary in the diff is never acceptable.** Each must include:
355437
- Exact file path and line numbers (including out-of-diff files)
356438
- Whether the finding is **in-diff** or **out-of-diff**
357439
- Code path trace showing why the bug is real

0 commit comments

Comments
 (0)