Skip to content

docs: document the separate-transactions rule across user-facing surfaces#190

Merged
NikolayS merged 3 commits intomainfrom
claude/docs-separate-tx-rule
May 5, 2026
Merged

docs: document the separate-transactions rule across user-facing surfaces#190
NikolayS merged 3 commits intomainfrom
claude/docs-separate-tx-rule

Conversation

@NikolayS
Copy link
Copy Markdown
Owner

@NikolayS NikolayS commented May 3, 2026

Documents the snapshot-based separate-transactions rule across user-facing surfaces. Pure docs, no code change.

Why

While implementing PR #179 (#134), I hit the rule myself: three operations in one do$$ block silently returned 0 rows on the next receive(). Auditing the codebase showed the rule is consistently called out in test headers (test_api_receive.sql, test_core_events.sql, test_core_retry.sql, test_core_batch_retry.sql, etc.) but invisible everywhere a user actually reads:

  • docs/tutorial.md promises ("when transaction boundaries matter, the text calls that out") but never does.
  • docs/pgq-concepts.md, docs/reference.md, docs/examples.md — no mention.
  • clients/{python,go,typescript}/README.md — no mention. The Go README explicitly invites the footgun ("call pgque.send inside your own pgx.Tx") with no warning that mixing send + receive in one Tx breaks.

Followed by CLAUDE.md Key Design Rule #7 added in PR #179, this PR makes the same rule visible to humans.

What

The rule lives once, in docs/pgq-concepts.md#snapshot-rule, with the three operation chains and the asymmetry vs. receive → process → ack. Every other surface gets a short callout that links there.

File Change
docs/pgq-concepts.md New "Snapshot rule" subsection (canonical)
docs/tutorial.md Callout under Step 5 ("Force a tick, then receive")
docs/examples.md Anti-example block under "Exactly-once processing" — send + force_tick + ticker + receive in one begin/commit returns 0 rows
docs/reference.md Anchor + paragraph at top of "Consuming"; inline note on maint_retry_events and force_tick
clients/python/README.md New "Transactions" section. Notes pgque.connect defaults to autocommit=False; explains Consumer's autocommit + explicit-tx-around-receive+ack pattern.
clients/go/README.md New "Transactions" section. Flags Client.Pool() as the footgun (its existing doc invites pgque.send inside your own pgx.Tx — correct only if consumer side runs in a separate tx).
clients/typescript/README.md New "Transactions" section. Flags client.rawPool similarly.

Out of scope

Test plan

  • No code change — no SQL/test files touched
  • Renders as GitHub-flavored Markdown (links resolve to the new anchor)
  • Maintainer review for wording

Refs PR #179.


Generated by Claude Code

@NikolayS NikolayS marked this pull request as ready for review May 3, 2026 22:52
NikolayS pushed a commit that referenced this pull request May 3, 2026
…omments

Per review feedback on PR #190.

- Halve the prose on every per-surface callout (tutorial Step 5,
  examples anti-example, reference snapshot-rule paragraph). The
  canonical long-form lives in pgq-concepts.md#snapshot-rule and is
  unchanged.
- Cut the three client README "Transactions" sections to ~3 sentences
  each. The previous versions repeated points already made on the
  canonical page.
- Add inline -- separate transaction comments next to pgque.ticker(),
  pgque.force_tick(), and follow-up pgque.receive() calls in the
  tutorial and the fan-out example, so the rule is visible at the
  point of use rather than only in surrounding prose.
@NikolayS
Copy link
Copy Markdown
Owner Author

NikolayS commented May 5, 2026

REV review — PR #190

BLOCKING ISSUES (2)

MEDIUM docs/pgq-concepts.md:95 / docs/reference.md:118 — Python default transaction mode is described too broadly.

These sections say the default modes of every shipped client, including psycopg(autocommit=True), satisfy the rule transparently. But the public Python entry point is pgque.connect(dsn, autocommit=False) by default, and PgqueClient explicitly leaves transaction management to the caller. The Python README gets this right; the shared concepts/reference docs should match it, e.g. “Go/TS pool methods and the Python Consumer satisfy this transparently; Python pgque.connect() requires commit boundaries or autocommit=True.”

MEDIUM PR mergeability — GitHub currently reports mergeStateStatus=DIRTY / mergeable=CONFLICTING.

Rebase or merge main and resolve conflicts before this can be merged.

NON-BLOCKING

None found.

EVIDENCE

  • Reviewed the 7 changed documentation files for the snapshot-rule wording and client-specific transaction claims.
  • Checked Python source: connect(dsn, *, autocommit: bool = False) and PgqueClient does not auto-commit; Consumer uses psycopg.connect(..., autocommit=True) plus explicit batch transactions.
  • git diff --check origin/main...HEAD passes.
  • GitHub CI is green across SQL matrix, Go/Python/TypeScript clients, pg_tle, and verify jobs.

VERDICT

Request changes. The core direction is good and useful, but the shared docs should not imply Python’s default pgque.connect() is safely autocommit, and the PR needs conflict resolution before merge.

NikolayS pushed a commit that referenced this pull request May 5, 2026
…omments

Per review feedback on PR #190.

- Halve the prose on every per-surface callout (tutorial Step 5,
  examples anti-example, reference snapshot-rule paragraph). The
  canonical long-form lives in pgq-concepts.md#snapshot-rule and is
  unchanged.
- Cut the three client README "Transactions" sections to ~3 sentences
  each. The previous versions repeated points already made on the
  canonical page.
- Add inline -- separate transaction comments next to pgque.ticker(),
  pgque.force_tick(), and follow-up pgque.receive() calls in the
  tutorial and the fan-out example, so the rule is visible at the
  point of use rather than only in surrounding prose.
NikolayS pushed a commit that referenced this pull request May 5, 2026
Per REV review on PR #190.

The shared concepts/reference docs implied the Python client satisfies
the snapshot rule transparently like Go/TS pool methods. That is wrong
for the bare client: pgque.connect(dsn) is autocommit=False by default,
so producers must commit explicitly between send and consumer side. The
high-level Python Consumer already handles this internally; the bare
PgqueClient leaves transaction management to the caller.

Brings pgq-concepts.md and reference.md in line with the per-driver
README and the actual psycopg default.
@NikolayS NikolayS force-pushed the claude/docs-separate-tx-rule branch from 24f04bc to aee895a Compare May 5, 2026 06:44
@NikolayS
Copy link
Copy Markdown
Owner Author

NikolayS commented May 5, 2026

REV review — final sanity pass

Verdict: REV OK for v0.2.0. No blockers found.

Checked:

  • PR is non-draft, clean/mergeable against current main.
  • CI is green: SQL matrix PG 14–18, pg_tle install path, verify, Python/Go/TypeScript client tests.
  • Transaction-boundary guidance is consistent across concepts, tutorial, examples, reference, and all three client READMEs.
  • Snapshot rule is documented correctly: send and ticker need separate transactions for visibility.
  • Retry pump caveat is documented correctly: nack/retry processing/ticker visibility require their own transaction boundaries.
  • Rotation step1 → step2 separate-transaction requirement is documented correctly.
  • Python correction is right: bare pgque.connect() is non-autocommit by default; high-level Consumer manages its own transaction scope.
  • The receive → process → ack same-transaction pattern is correctly preserved as intentional consume-side behavior.

Optional nit, not blocking: docs/pgq-concepts.md says combining chains can produce “empty batches and dropped messages.” “Empty batches” is clearly right; “dropped messages” is a bit strong/scary unless we want that phrasing. “Invisible to that batch” / “missed until a later valid tick” would be more precise.

Coordination note: #206 touches nearby docs and introduces force_next_tick; if it merges first, this PR may need a light rebase/use of the new canonical name where user-facing examples mention the old helper. Not a blocker for this PR as-is.

claude added 3 commits May 5, 2026 08:27
…aces

The "send → ticker → receive" snapshot rule and the related
"maint_retry_events → ticker → receive" rule were called out in test
files (test_api_receive.sql header etc.) but invisible everywhere else.
A user reading the tutorial, the function reference, or any client
README had no warning that wrapping these calls in one explicit tx
silently produces empty batches.

This commit adds the rule once in pgq-concepts.md and links to it
from every other surface that names the involved functions:

- docs/tutorial.md: short callout under Step 5 ("Force a tick, then
  receive") explaining why each select runs in its own transaction.
- docs/pgq-concepts.md: new "Snapshot rule" subsection with all three
  operation chains (producer → consumer; retry pump; rotation step1 →
  step2) and the asymmetry vs. receive→process→ack.
- docs/examples.md: anti-example block under "Exactly-once processing"
  showing the wrong send+receive-in-one-tx pattern.
- docs/reference.md: anchor + paragraph at the top of "Consuming";
  inline reminders on maint_retry_events and force_tick entries.
- clients/python/README.md: new "Transactions" section explaining
  default psycopg autocommit behavior, the Quickstart's commit
  pattern, and the Consumer's internal transaction scope.
- clients/go/README.md: new "Transactions" section flagging
  Client.Pool() as the footgun (its doc-comment invites you to
  pgque.send inside your own pgx.Tx — correct only if consumer side
  runs in a separate tx).
- clients/typescript/README.md: new "Transactions" section flagging
  client.rawPool similarly.

No code or behavior change. Source SQL header comments
(sql/pgque-api/{send,receive,maint}.sql) intentionally left for a
later contributor-doc pass.
…omments

Per review feedback on PR #190.

- Halve the prose on every per-surface callout (tutorial Step 5,
  examples anti-example, reference snapshot-rule paragraph). The
  canonical long-form lives in pgq-concepts.md#snapshot-rule and is
  unchanged.
- Cut the three client README "Transactions" sections to ~3 sentences
  each. The previous versions repeated points already made on the
  canonical page.
- Add inline -- separate transaction comments next to pgque.ticker(),
  pgque.force_tick(), and follow-up pgque.receive() calls in the
  tutorial and the fan-out example, so the rule is visible at the
  point of use rather than only in surrounding prose.
Per REV review on PR #190.

The shared concepts/reference docs implied the Python client satisfies
the snapshot rule transparently like Go/TS pool methods. That is wrong
for the bare client: pgque.connect(dsn) is autocommit=False by default,
so producers must commit explicitly between send and consumer side. The
high-level Python Consumer already handles this internally; the bare
PgqueClient leaves transaction management to the caller.

Brings pgq-concepts.md and reference.md in line with the per-driver
README and the actual psycopg default.
@NikolayS NikolayS force-pushed the claude/docs-separate-tx-rule branch from aee895a to e87630b Compare May 5, 2026 08:28
@NikolayS
Copy link
Copy Markdown
Owner Author

NikolayS commented May 5, 2026

Rebased on current main

Rebased #190 on current main after #203/#204/#206 landed.

Conflict resolution notes:

Validation:

  • git diff --check
  • docs-only rebase; no generated SQL changes.

Waiting on CI for rewritten head.

@NikolayS NikolayS merged commit 1581f39 into main May 5, 2026
11 checks passed
@NikolayS NikolayS deleted the claude/docs-separate-tx-rule branch May 5, 2026 08:33
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