refactor: dedupe outbox rules between real and fake clients#109
Merged
Conversation
…ing.py Move is_future_dated, resolve_next_attempt_client_side and validate_activate_args into a new stdlib-only leaf module so the real and fake publish paths share one copy. publish_batch now calls the resolver instead of inlining it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One parametrized scenario module pins the shared AbstractOutboxClient surface (fetch / delete_with_lease / mark_pending_with_lease + DLQ) against both the in-memory fake and real Postgres (auto-skipped without a DB). A per-adapter harness hides substrate differences; scheduling is seeded as server-side make_interval offsets, matching the existing predicate-parity idiom. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_DLQ_PROJECTION (+ _DLQ_INJECTED_COLUMNS) in schema.py is now the single source the real DLQ CTE and the fake's delete_with_lease both build from, replacing the hand-kept parity (the P9 comment) between the two substrates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the basic single-adapter fetch/delete/mark scenarios and the manual fake-vs-real predicate-parity test that test_client_contract.py now subsumes on both adapters. Real-specific coverage (SKIP LOCKED concurrency, autocommit round-trip, DB-clock retry timing, DLQ CTE) stays. Coverage holds at 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promote the conclusions into CLAUDE.md (User-owned schema / DLQ / Test broker)
and architecture/{dlq,test-broker}.md alongside the code, and mark the
client-rules-kernel change shipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 23, 2026
lesnik512
added a commit
that referenced
this pull request
Jun 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AbstractOutboxClienthas two real adapters —OutboxClient(SQL/Postgres) andFakeOutboxClient(in-memory) — and the outbox rules (eligibility, lease, retry timing, scheduling, the DLQ projection) were written down twice: once as SQLAlchemy inclient.py/producer.py, once as Python intesting.py, kept in parity by hand and by comment (P9,B10). This change removes the duplication where the implementations can be shared and replaces hand-parity with a machine-checked contract where they cannot.Spec/plan:
planning/changes/2026-06-23.01-client-rules-kernel/.What changed
_scheduling.py(new stdlib-only leaf) —is_future_dated,resolve_next_attempt_client_side,validate_activate_args, the pure activate-args helpers shared by the real and fake publish paths (previously scattered acrossproducer.py/broker.py, withpublish_batchinlining one of them)._DLQ_PROJECTIONinschema.py— one declarative(outbox_col, dlq_col)map that both the real DLQ CTE and the fake build their column lists from. A DLQ column change is now one edit, not hand-kept parity in two languages.tests/test_client_contract.py— one parametrized scenario module pins the sharedAbstractOutboxClientsurface (fetch/delete_with_lease/mark_pending_with_lease+ DLQ) against both adapters: fake everywhere, real Postgres auto-skipped when unreachable. Drift fails a test instead of shipping.Scope decisions made during execution
cancel_timerandtimer_idinsert-dedup are broker/producer concerns, not onAbstractOutboxClient, so they stay intest_integration.py/test_fake.py.make_intervalclock authority stays a documented invariant, not an assertion.Verification
just test: 543 passed, 100% coverage.just lint-ci: ruff + ty clean.🤖 Generated with Claude Code