pgbus 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.bun-version +1 -0
  3. data/.claude/commands/architect.md +100 -0
  4. data/.claude/commands/github-review-comments.md +237 -0
  5. data/.claude/commands/lfg.md +271 -0
  6. data/.claude/commands/review-pr.md +69 -0
  7. data/.claude/commands/security.md +122 -0
  8. data/.claude/commands/tdd.md +148 -0
  9. data/.claude/rules/agents.md +49 -0
  10. data/.claude/rules/coding-style.md +91 -0
  11. data/.claude/rules/git-workflow.md +56 -0
  12. data/.claude/rules/performance.md +73 -0
  13. data/.claude/rules/testing.md +67 -0
  14. data/CHANGELOG.md +5 -0
  15. data/CLAUDE.md +80 -0
  16. data/CODE_OF_CONDUCT.md +10 -0
  17. data/LICENSE.txt +21 -0
  18. data/README.md +417 -0
  19. data/Rakefile +14 -0
  20. data/app/controllers/pgbus/api/stats_controller.rb +11 -0
  21. data/app/controllers/pgbus/application_controller.rb +35 -0
  22. data/app/controllers/pgbus/dashboard_controller.rb +27 -0
  23. data/app/controllers/pgbus/dead_letter_controller.rb +50 -0
  24. data/app/controllers/pgbus/events_controller.rb +23 -0
  25. data/app/controllers/pgbus/jobs_controller.rb +48 -0
  26. data/app/controllers/pgbus/processes_controller.rb +10 -0
  27. data/app/controllers/pgbus/queues_controller.rb +21 -0
  28. data/app/helpers/pgbus/application_helper.rb +69 -0
  29. data/app/views/layouts/pgbus/application.html.erb +76 -0
  30. data/app/views/pgbus/dashboard/_processes_table.html.erb +30 -0
  31. data/app/views/pgbus/dashboard/_queues_table.html.erb +39 -0
  32. data/app/views/pgbus/dashboard/_recent_failures.html.erb +33 -0
  33. data/app/views/pgbus/dashboard/_stats_cards.html.erb +28 -0
  34. data/app/views/pgbus/dashboard/show.html.erb +10 -0
  35. data/app/views/pgbus/dead_letter/_messages_table.html.erb +40 -0
  36. data/app/views/pgbus/dead_letter/index.html.erb +15 -0
  37. data/app/views/pgbus/dead_letter/show.html.erb +52 -0
  38. data/app/views/pgbus/events/index.html.erb +57 -0
  39. data/app/views/pgbus/events/show.html.erb +28 -0
  40. data/app/views/pgbus/jobs/_enqueued_table.html.erb +34 -0
  41. data/app/views/pgbus/jobs/_failed_table.html.erb +45 -0
  42. data/app/views/pgbus/jobs/index.html.erb +16 -0
  43. data/app/views/pgbus/jobs/show.html.erb +57 -0
  44. data/app/views/pgbus/processes/_processes_table.html.erb +37 -0
  45. data/app/views/pgbus/processes/index.html.erb +3 -0
  46. data/app/views/pgbus/queues/_queues_list.html.erb +41 -0
  47. data/app/views/pgbus/queues/index.html.erb +3 -0
  48. data/app/views/pgbus/queues/show.html.erb +49 -0
  49. data/bun.lock +18 -0
  50. data/config/routes.rb +45 -0
  51. data/docs/README.md +28 -0
  52. data/docs/switch_from_good_job.md +279 -0
  53. data/docs/switch_from_sidekiq.md +226 -0
  54. data/docs/switch_from_solid_queue.md +247 -0
  55. data/exe/pgbus +7 -0
  56. data/lib/generators/pgbus/install_generator.rb +56 -0
  57. data/lib/generators/pgbus/templates/migration.rb.erb +114 -0
  58. data/lib/generators/pgbus/templates/pgbus.yml.erb +74 -0
  59. data/lib/generators/pgbus/templates/pgbus_binstub.erb +7 -0
  60. data/lib/pgbus/active_job/adapter.rb +109 -0
  61. data/lib/pgbus/active_job/executor.rb +107 -0
  62. data/lib/pgbus/batch.rb +153 -0
  63. data/lib/pgbus/cli.rb +84 -0
  64. data/lib/pgbus/client.rb +162 -0
  65. data/lib/pgbus/concurrency/blocked_execution.rb +74 -0
  66. data/lib/pgbus/concurrency/semaphore.rb +66 -0
  67. data/lib/pgbus/concurrency.rb +65 -0
  68. data/lib/pgbus/config_loader.rb +27 -0
  69. data/lib/pgbus/configuration.rb +99 -0
  70. data/lib/pgbus/engine.rb +31 -0
  71. data/lib/pgbus/event.rb +31 -0
  72. data/lib/pgbus/event_bus/handler.rb +76 -0
  73. data/lib/pgbus/event_bus/publisher.rb +42 -0
  74. data/lib/pgbus/event_bus/registry.rb +54 -0
  75. data/lib/pgbus/event_bus/subscriber.rb +30 -0
  76. data/lib/pgbus/process/consumer.rb +113 -0
  77. data/lib/pgbus/process/dispatcher.rb +154 -0
  78. data/lib/pgbus/process/heartbeat.rb +71 -0
  79. data/lib/pgbus/process/signal_handler.rb +49 -0
  80. data/lib/pgbus/process/supervisor.rb +198 -0
  81. data/lib/pgbus/process/worker.rb +153 -0
  82. data/lib/pgbus/serializer.rb +43 -0
  83. data/lib/pgbus/version.rb +5 -0
  84. data/lib/pgbus/web/authentication.rb +24 -0
  85. data/lib/pgbus/web/data_source.rb +406 -0
  86. data/lib/pgbus.rb +49 -0
  87. data/package.json +9 -0
  88. data/sig/pgbus.rbs +4 -0
  89. metadata +198 -0
@@ -0,0 +1,122 @@
1
+ ---
2
+ description: "Reviews code for security vulnerabilities. Use when auditing PGMQ operations, connection handling, SQL injection risks, or dashboard authentication."
3
+ model: claude-opus-4-6
4
+ argument-hint: "code, feature, or area to review for security"
5
+ ---
6
+
7
+ # Security Specialist
8
+
9
+ You are the **Security review and vulnerability audit specialist** for pgbus.
10
+
11
+ ## Trigger Contexts
12
+
13
+ Use this skill when:
14
+ - Auditing PGMQ SQL operations for injection risks
15
+ - Reviewing connection pool handling
16
+ - Checking for race conditions in message processing
17
+ - Reviewing deserialization of job arguments / event payloads
18
+ - Auditing the dashboard web UI
19
+ - Reviewing worker process management
20
+
21
+ ## Key Security Concerns for This Gem
22
+
23
+ ### SQL Injection via Queue Names
24
+
25
+ ```ruby
26
+ # BAD: Unsanitized queue name in SQL
27
+ connection.execute("SELECT * FROM pgmq.q_#{queue_name}")
28
+
29
+ # GOOD: Sanitize queue names
30
+ def sanitize_name(name)
31
+ name.gsub(/[^a-zA-Z0-9_]/, "")
32
+ end
33
+ ```
34
+
35
+ PGMQ queue names are interpolated into SQL identifiers. The `Client` validates names, but any new code touching queue names must sanitize.
36
+
37
+ ### Connection Pool Safety
38
+
39
+ - pgmq-ruby uses `connection_pool` gem for thread-safe pooling
40
+ - Never hold a connection across async boundaries
41
+ - `TransactionalClient` pins a single connection -- ensure it's not leaked
42
+ - When using `-> { ActiveRecord::Base.connection.raw_connection }`, connection lifecycle is Rails-managed
43
+
44
+ ### Message Deserialization
45
+
46
+ ```ruby
47
+ # BAD: Unsafe deserialization
48
+ Marshal.load(message.message)
49
+
50
+ # GOOD: JSON only
51
+ JSON.parse(message.message)
52
+ ```
53
+
54
+ - All payloads are JSONB -- stick to JSON.parse
55
+ - ActiveJob's `deserialize` handles GlobalID resolution -- trust it but validate
56
+ - Event payloads with `_global_id` call `GlobalID::Locator.locate` -- ensure objects exist
57
+
58
+ ### Dashboard Authentication
59
+
60
+ - `web_auth` block receives the raw request -- must be configured by the host app
61
+ - Default is `nil` (allow all) -- document this clearly
62
+ - Dashboard inherits from `ActionController::Base` (isolated from host app)
63
+ - Never expose raw PGMQ internals without sanitization
64
+ - Filter sensitive job arguments in the UI
65
+
66
+ ### Worker Process Security
67
+
68
+ - Supervisor forks child processes -- signal handling must be correct
69
+ - Worker recycling kills processes -- ensure graceful cleanup
70
+ - Heartbeat writes to DB -- validate process ownership
71
+ - Memory measurement uses `ps` / `/proc` -- no shell injection risk (pid is numeric)
72
+
73
+ ### Visibility Timeout / Message Safety
74
+
75
+ - Messages become visible again after VT expires -- idempotency is critical
76
+ - `read_ct` tracks redeliveries -- DLQ routing must be reliable
77
+ - `FOR UPDATE SKIP LOCKED` prevents double-processing -- verify all read paths use it
78
+ - Archive vs delete: archived messages are queryable, deleted are gone
79
+
80
+ ## Verification Checklist
81
+
82
+ - [ ] No SQL injection via queue names
83
+ - [ ] All PGMQ operations go through Client (sanitized)
84
+ - [ ] Connection pool properly managed
85
+ - [ ] No unsafe deserialization (JSON only)
86
+ - [ ] Dashboard auth is configurable and documented
87
+ - [ ] Worker signals handled correctly
88
+ - [ ] No secrets in logs or error messages
89
+ - [ ] Idempotency enforced for event handlers
90
+
91
+ ## Security Tools
92
+
93
+ ```bash
94
+ # Static analysis
95
+ bundle exec rubocop
96
+
97
+ # Check for known vulnerabilities in dependencies
98
+ bundle audit check --update
99
+
100
+ # Review queue name handling
101
+ grep -r "pgmq\.q_\|pgmq\.a_" lib/
102
+ ```
103
+
104
+ ## Common Mistakes to Avoid
105
+
106
+ | Wrong | Right |
107
+ |-------|-------|
108
+ | String interpolation in SQL | Parameterized queries or sanitized identifiers |
109
+ | Marshal.load on payloads | JSON.parse only |
110
+ | Global PGMQ client without pool | Connection pool with thread safety |
111
+ | Default-open dashboard | Require explicit auth configuration |
112
+ | Swallowing worker errors | Log and track via failed_events table |
113
+ | Infinite visibility timeout | Always set reasonable VT with DLQ fallback |
114
+
115
+ ## Handoff
116
+
117
+ When complete, summarize:
118
+ - Vulnerabilities found (with severity)
119
+ - Remediation steps
120
+ - Tests to add
121
+
122
+ Now, focus on security review for the current task.
@@ -0,0 +1,148 @@
1
+ ---
2
+ description: "Use when implementing any feature or fixing any bug -- enforces RED-GREEN-REFACTOR: write failing test first, implement minimum code to pass, then refactor."
3
+ ---
4
+
5
+ # TDD Command
6
+
7
+ Enforce test-driven development methodology with RED -> GREEN -> REFACTOR cycle.
8
+
9
+ ## The TDD Cycle
10
+
11
+ ```text
12
+ RED -> GREEN -> REFACTOR -> REPEAT
13
+
14
+ RED: Write a failing test (test MUST fail first)
15
+ GREEN: Write MINIMAL code to pass (nothing more)
16
+ REFACTOR: Improve code while keeping tests green
17
+ REPEAT: Next feature/scenario
18
+ ```
19
+
20
+ ## When to Use
21
+
22
+ - Implementing new features
23
+ - Adding new queue types or event handlers
24
+ - Fixing bugs (write test that reproduces bug FIRST)
25
+ - Refactoring existing code
26
+ - Modifying the ActiveJob adapter
27
+ - Changing worker/process behavior
28
+ - Adding dashboard endpoints
29
+
30
+ ## Workflow
31
+
32
+ ### Step 1: Write Failing Tests (RED)
33
+
34
+ ```ruby
35
+ # spec/pgbus/example_spec.rb
36
+ RSpec.describe Pgbus::NewFeature do
37
+ describe "#process" do
38
+ context "when message is valid" do
39
+ it "processes successfully" do
40
+ expect(subject.process(message)).to eq(:success)
41
+ end
42
+ end
43
+
44
+ context "when max retries exceeded" do
45
+ it "routes to dead letter queue" do
46
+ expect(subject.process(stale_message)).to eq(:dead_lettered)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ ```
52
+
53
+ ### Step 2: Run Tests - Verify FAIL
54
+
55
+ ```bash
56
+ bundle exec rspec spec/pgbus/example_spec.rb
57
+
58
+ FAIL - NotImplementedError / Expected behavior not met
59
+ ```
60
+
61
+ **Tests MUST fail before implementing.** This confirms:
62
+ - Tests are actually running
63
+ - Tests are testing the right thing
64
+ - Implementation doesn't already exist
65
+
66
+ ### Step 3: Implement Minimal Code (GREEN)
67
+
68
+ Write the minimum code to make the test pass.
69
+
70
+ ### Step 4: Run Tests - Verify PASS
71
+
72
+ ```bash
73
+ bundle exec rspec spec/pgbus/example_spec.rb
74
+
75
+ N examples, 0 failures
76
+ ```
77
+
78
+ ### Step 5: Refactor (IMPROVE)
79
+
80
+ Improve code while keeping tests green:
81
+ - Extract methods to reduce complexity
82
+ - Improve naming
83
+ - Reduce duplication
84
+ - Ensure thread safety
85
+
86
+ ### Step 6: Run Full Suite
87
+
88
+ ```bash
89
+ bundle exec rspec
90
+ ```
91
+
92
+ ## Coverage Requirements
93
+
94
+ | Code Type | Minimum Coverage |
95
+ |-----------|------------------|
96
+ | All code | 80% |
97
+ | ActiveJob adapter | 100% |
98
+ | Event bus (handler, registry) | 100% |
99
+ | Process model (worker, supervisor) | 100% |
100
+ | Client (PGMQ wrapper) | 100% |
101
+ | Web::DataSource | 100% |
102
+ | Configuration | 100% |
103
+
104
+ ## Test Types to Include
105
+
106
+ ### Unit Tests (Configuration, Event, Serializer)
107
+ - Happy path scenarios
108
+ - Edge cases (nil values, empty queues, expired VT)
109
+ - Error conditions
110
+
111
+ ### Integration Tests (Client, Adapter, Worker)
112
+ - ActiveJob enqueue/execute lifecycle
113
+ - Worker claim-process-archive flow
114
+ - Dead letter queue routing
115
+ - Event publish/subscribe round-trip
116
+
117
+ ### Web Tests (DataSource, Authentication)
118
+ - DataSource with mocked PGMQ client
119
+ - Authentication block allow/deny
120
+ - Helper formatting
121
+
122
+ ## Best Practices
123
+
124
+ **DO:**
125
+ - Write the test FIRST, before any implementation
126
+ - Run tests and verify they FAIL before implementing
127
+ - Write MINIMAL code to make tests pass
128
+ - Refactor only after tests are green
129
+ - Mock PGMQ client in unit tests (avoid DB dependency)
130
+ - Test worker recycling thresholds explicitly
131
+
132
+ **DON'T:**
133
+ - Write implementation before tests
134
+ - Skip running tests after each change
135
+ - Write too much code at once
136
+ - Ignore failing tests
137
+ - Test implementation details (test behavior)
138
+ - Skip testing error paths
139
+
140
+ ## Checklist
141
+
142
+ - [ ] Tests written BEFORE implementation
143
+ - [ ] Tests fail initially (RED phase verified)
144
+ - [ ] Minimal code written to pass (GREEN)
145
+ - [ ] Code refactored with tests still passing
146
+ - [ ] Coverage meets requirements (80%+)
147
+ - [ ] All edge cases covered
148
+ - [ ] Backwards compatibility maintained
@@ -0,0 +1,49 @@
1
+ # Agent Orchestration Rules
2
+
3
+ ## Available Agents
4
+
5
+ | Agent | Purpose | When to Use |
6
+ |-------|---------|-------------|
7
+ | Explore | Codebase exploration | Finding files, understanding patterns |
8
+ | Plan | Implementation planning | Complex features, architectural decisions |
9
+ | general-purpose | Multi-step tasks | Research, complex searches |
10
+
11
+ ## Immediate Agent Usage
12
+
13
+ Use agents PROACTIVELY without waiting for user prompt:
14
+
15
+ 1. **Complex feature requests** -> Use Plan agent first
16
+ 2. **Codebase exploration** -> Use Explore agent
17
+ 3. **Multi-file searches** -> Use Explore agent (not direct Glob/Grep)
18
+ 4. **Architectural decisions** -> Use Plan agent
19
+
20
+ ## Parallel Execution
21
+
22
+ **ALWAYS** use parallel Task execution for independent operations:
23
+
24
+ ```markdown
25
+ # GOOD: Parallel execution
26
+ Launch multiple agents simultaneously:
27
+ 1. Agent 1: Explore client/adapter patterns
28
+ 2. Agent 2: Check event bus patterns
29
+ 3. Agent 3: Review test coverage
30
+
31
+ # BAD: Sequential when unnecessary
32
+ First explore, wait, then check patterns, wait, then review...
33
+ ```
34
+
35
+ ## When to Use Explore Agent
36
+
37
+ Use the Explore agent (subagent_type=Explore) instead of direct Glob/Grep when:
38
+ - Open-ended codebase exploration
39
+ - Searching for patterns across client, adapter, event bus, and process layers
40
+ - Answering questions about codebase structure
41
+ - Finding related implementations across modules
42
+
43
+ ## When NOT to Use Agents
44
+
45
+ Use direct tools when:
46
+ - Reading a specific known file path
47
+ - Simple pattern match in known location
48
+ - Single-file edits
49
+ - Running specific commands
@@ -0,0 +1,91 @@
1
+ # Coding Style Rules
2
+
3
+ ## File Organization
4
+
5
+ **MANY SMALL FILES > FEW LARGE FILES**
6
+
7
+ - High cohesion, low coupling
8
+ - 200-400 lines typical
9
+ - 800 lines maximum per file
10
+ - Extract complex logic to dedicated classes
11
+ - Organize by concern (client, adapter, event_bus, process, web)
12
+
13
+ ## Ruby Style
14
+
15
+ ### Classes & Methods
16
+
17
+ ```ruby
18
+ # Good: Small, focused methods
19
+ def execute(message, queue_name)
20
+ check_dead_letter(message, queue_name)
21
+ deserialize_and_run(message)
22
+ archive(message, queue_name)
23
+ end
24
+
25
+ # Bad: Giant methods doing everything
26
+ def process_everything
27
+ # 200 lines of code...
28
+ end
29
+ ```
30
+
31
+ ### Error Handling
32
+
33
+ ```ruby
34
+ # Good: Specific error handling
35
+ def execute(message, queue_name)
36
+ job = deserialize(message)
37
+ job.perform_now
38
+ client.archive_message(queue_name, message.msg_id.to_i)
39
+ :success
40
+ rescue ActiveJob::DeserializationError => e
41
+ Pgbus.logger.error { "[Pgbus] Deserialization failed: #{e.message}" }
42
+ :failed
43
+ rescue StandardError => e
44
+ Pgbus.logger.error { "[Pgbus] Job failed: #{e.message}" }
45
+ :failed
46
+ end
47
+
48
+ # Bad: Swallowing errors
49
+ def execute(message, queue_name)
50
+ deserialize(message).perform_now
51
+ rescue StandardError
52
+ nil
53
+ end
54
+ ```
55
+
56
+ ### PGMQ Operations
57
+
58
+ ```ruby
59
+ # Good: All PGMQ access through Client
60
+ Pgbus.client.send_message(queue, payload)
61
+ Pgbus.client.read_batch(queue, qty: 5)
62
+
63
+ # Bad: Direct PGMQ calls
64
+ pgmq = PGMQ::Client.new(...)
65
+ pgmq.produce("raw_queue_name", data)
66
+ ```
67
+
68
+ ### Thread Safety
69
+
70
+ ```ruby
71
+ # Good: Mutex for shared state
72
+ @mutex.synchronize { @queues_created[name] = true }
73
+
74
+ # Good: Concurrent primitives
75
+ @pool = Concurrent::FixedThreadPool.new(threads)
76
+
77
+ # Bad: Unsynchronized shared state
78
+ @queues_created[name] = true # race condition
79
+ ```
80
+
81
+ ## Code Quality Checklist
82
+
83
+ Before marking work complete:
84
+ - [ ] Code is readable and well-named
85
+ - [ ] Methods are small (<30 lines ideal, <50 max)
86
+ - [ ] Files are focused (<800 lines)
87
+ - [ ] No deep nesting (>4 levels)
88
+ - [ ] Proper error handling with logging
89
+ - [ ] All PGMQ operations through Client
90
+ - [ ] Thread safety verified for shared state
91
+ - [ ] Rubocop passes
@@ -0,0 +1,56 @@
1
+ # Git Workflow Rules
2
+
3
+ ## Commit Messages
4
+
5
+ Use conventional commits:
6
+ - `feat:` - New feature
7
+ - `fix:` - Bug fix
8
+ - `refactor:` - Code refactoring
9
+ - `perf:` - Performance improvement
10
+ - `docs:` - Documentation only
11
+ - `test:` - Adding/updating tests
12
+ - `chore:` - Maintenance tasks
13
+ - `ci:` - CI/CD changes
14
+
15
+ Format:
16
+ ```
17
+ feat(scope): brief description
18
+
19
+ Longer explanation if needed. Focus on WHY, not WHAT.
20
+
21
+ Refs #123
22
+ ```
23
+
24
+ ## Branch Naming
25
+
26
+ - `feature/description` - New features
27
+ - `fix/description` - Bug fixes
28
+ - `refactor/description` - Refactoring
29
+ - `ci/description` - CI changes
30
+ - `chore/description` - Maintenance
31
+
32
+ ## PR Workflow
33
+
34
+ 1. Create branch from `main`
35
+ 2. Make focused, atomic commits
36
+ 3. Run all validators before pushing
37
+ 4. Create PR with description and test plan
38
+ 5. Request review
39
+ 6. Squash merge when approved
40
+
41
+ ## Pre-Commit Checklist
42
+
43
+ Run before EVERY commit:
44
+ ```bash
45
+ bundle exec rubocop # Style
46
+ bundle exec rspec <relevant_specs> # Tests
47
+ ```
48
+
49
+ ## Rules
50
+
51
+ - **NEVER** commit directly to `main`
52
+ - **NEVER** force push to shared branches
53
+ - **ALWAYS** run validators before committing
54
+ - **ALWAYS** write meaningful commit messages
55
+ - Keep commits small and focused
56
+ - One logical change per commit
@@ -0,0 +1,73 @@
1
+ # Performance Rules
2
+
3
+ ## Context Window Management
4
+
5
+ **Critical**: Your context window can shrink significantly with many tools enabled.
6
+
7
+ Guidelines:
8
+ - Keep under 10 MCPs enabled per project
9
+ - Avoid loading large files unnecessarily
10
+ - Use targeted searches over broad exploration
11
+
12
+ ## PGMQ Performance
13
+
14
+ ### Polling vs LISTEN/NOTIFY
15
+
16
+ ```ruby
17
+ # Good: Use LISTEN/NOTIFY for instant wake-up
18
+ pgmq.enable_notify_insert(queue_name, throttle_interval_ms: 250)
19
+
20
+ # Polling as fallback only -- not primary mechanism
21
+ sleep(config.polling_interval) # only when LISTEN/NOTIFY unavailable
22
+ ```
23
+
24
+ ### Batch Operations
25
+
26
+ ```ruby
27
+ # Good: Batch reads for throughput
28
+ client.read_batch(queue, qty: idle_threads)
29
+
30
+ # Bad: Single reads in a loop
31
+ idle_threads.times { client.read_message(queue) }
32
+ ```
33
+
34
+ ### Connection Pool Sizing
35
+
36
+ - Pool size should match worker thread count
37
+ - Don't over-provision -- each connection holds PostgreSQL resources
38
+ - Use `-> { ActiveRecord::Base.connection.raw_connection }` in Rails to share the pool
39
+
40
+ ### Queue Table Performance
41
+
42
+ - PGMQ's `q_` tables use `vt ASC` index for fast reads
43
+ - `FOR UPDATE SKIP LOCKED` prevents contention between workers
44
+ - Archive tables grow unbounded -- consider partitioning for high-volume queues
45
+ - Purge processed events table periodically (idempotency dedup)
46
+
47
+ ## Worker Performance
48
+
49
+ ### Memory Management
50
+
51
+ ```ruby
52
+ # Good: Recycling prevents memory bloat
53
+ max_jobs_per_worker: 10_000
54
+ max_memory_mb: 512
55
+ max_worker_lifetime: 3600
56
+
57
+ # Bad: Workers running forever without limits
58
+ # (this is solid_queue's problem)
59
+ ```
60
+
61
+ ### Thread Pool Sizing
62
+
63
+ - Match threads to expected concurrency
64
+ - More threads = more PGMQ connections needed
65
+ - Monitor with `pool.queue_length` vs `pool.max_length`
66
+
67
+ ## Performance Checklist
68
+
69
+ - [ ] LISTEN/NOTIFY enabled for active queues
70
+ - [ ] Batch reads used where possible
71
+ - [ ] Connection pool sized appropriately
72
+ - [ ] Worker recycling configured
73
+ - [ ] No N+1 queries in dashboard DataSource
@@ -0,0 +1,67 @@
1
+ # Testing Rules
2
+
3
+ ## TDD Workflow
4
+
5
+ Follow RED -> GREEN -> REFACTOR:
6
+
7
+ 1. **RED**: Write a failing test first
8
+ 2. **GREEN**: Write minimal code to pass
9
+ 3. **REFACTOR**: Improve code while keeping tests green
10
+
11
+ ## Coverage Requirements
12
+
13
+ - **80% minimum** for all code
14
+ - **100% required** for:
15
+ - ActiveJob adapter (enqueue, execute, DLQ routing)
16
+ - Event bus (handler, registry, publisher)
17
+ - Client (PGMQ wrapper)
18
+ - Configuration
19
+ - Web::DataSource
20
+ - Web::Authentication
21
+
22
+ ## Test Type Preference
23
+
24
+ | Feature involves | Use |
25
+ |-----------------|-----|
26
+ | Configuration / Event / Serializer | Unit spec |
27
+ | Client / PGMQ interaction | Unit spec with mocked PGMQ |
28
+ | ActiveJob adapter | Integration spec |
29
+ | Worker / Supervisor | Unit spec (process behavior) |
30
+ | Dashboard DataSource | Unit spec with mocked client |
31
+ | Dashboard Authentication | Unit spec |
32
+ | Helper formatting | Unit spec |
33
+
34
+ ## RSpec Conventions
35
+
36
+ ```ruby
37
+ # Use let for setup
38
+ let(:config) { Pgbus::Configuration.new }
39
+
40
+ # Use subject for the thing being tested
41
+ subject(:data_source) { described_class.new(client: mock_client) }
42
+
43
+ # Use contexts for scenarios
44
+ context "when queue exists" do
45
+ before { allow(mock_client).to receive(:metrics).and_return(metrics) }
46
+ it { expect(result).not_to be_nil }
47
+ end
48
+
49
+ # Use doubles for PGMQ (avoid DB dependency in unit tests)
50
+ let(:mock_client) { double("Pgbus::Client", pgmq: double("pgmq")) }
51
+ ```
52
+
53
+ ## PGMQ in Tests
54
+
55
+ - Mock `Pgbus::Client` in unit tests -- PGMQ requires a real PostgreSQL connection
56
+ - Use doubles for PGMQ message objects (they're `Data.define` value objects)
57
+ - Test configuration without PGMQ connection
58
+ - Integration tests (when added) will need a real PostgreSQL with PGMQ extension
59
+
60
+ ## Test Checklist
61
+
62
+ - [ ] Tests written BEFORE implementation
63
+ - [ ] All tests pass: `bundle exec rspec`
64
+ - [ ] Coverage meets requirements
65
+ - [ ] No skipped tests without reason
66
+ - [ ] Edge cases covered (nil messages, expired VT, max retries exceeded)
67
+ - [ ] Error paths tested (connection failures, deserialization errors)
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-03-30
4
+
5
+ - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,80 @@
1
+ # Pgbus
2
+
3
+ PostgreSQL-native job processing and event bus for Rails, built on PGMQ.
4
+
5
+ ## Tech Stack
6
+
7
+ - **Ruby**: >= 3.3 | **Rails**: >= 7.1
8
+ - **Transport**: pgmq-ruby (PGMQ PostgreSQL extension)
9
+ - **Concurrency**: concurrent-ruby
10
+ - **Autoloading**: zeitwerk
11
+ - **Testing**: RSpec
12
+ - **Linting**: RuboCop
13
+
14
+ ## Critical Rules
15
+
16
+ ### Never Do
17
+ 1. **NO direct PGMQ calls** — always go through `Pgbus::Client`
18
+ 2. **NO hardcoded queue names** — use `config.queue_name()`
19
+ 3. **NO raw SQL in dashboard** — use `Web::DataSource`
20
+ 4. **NO `Marshal.load`** — JSON serialization only
21
+ 5. **NO unsynchronized shared state** — use Mutex or Concurrent primitives
22
+ 6. **NO swallowing errors** — log via `Pgbus.logger`, track in `pgbus_failed_events`
23
+
24
+ ### Always Do
25
+ 1. **TDD**: Write tests BEFORE implementation
26
+ 2. **Worker recycling**: Configure `max_jobs`, `max_memory_mb`, `max_lifetime`
27
+ 3. **Dead letter routing**: Check `read_ct` > `max_retries`
28
+ 4. **LISTEN/NOTIFY**: Use `enable_notify_insert` for instant wake-up
29
+ 5. **Queue prefix**: All queues through `config.queue_name()`
30
+ 6. **Visibility timeout**: Always pass `vt:` parameter on reads
31
+
32
+ ## Commands
33
+
34
+ ```bash
35
+ bundle exec rspec # Run tests
36
+ bundle exec rubocop # Lint
37
+ bundle exec rake # Both
38
+ ```
39
+
40
+ ## Slash Commands
41
+
42
+ | Command | Purpose |
43
+ |---------|---------|
44
+ | `/lfg` | Full autonomous workflow: branch → understand → explore → plan → TDD → verify → PR |
45
+ | `/github-review-comments` | Process unresolved PR review comments |
46
+ | `/review-pr` | Review a PR for pattern compliance |
47
+ | `/tdd` | Enforce RED → GREEN → REFACTOR cycle |
48
+ | `/security` | Security audit (PGMQ ops, connections, auth, deserialization) |
49
+ | `/architect` | Coordinate multi-layer development |
50
+
51
+ ## Architecture
52
+
53
+ ```
54
+ Layer 6: Dashboard app/controllers/pgbus/, app/views/pgbus/
55
+ Layer 5: CLI lib/pgbus/cli.rb
56
+ Layer 4: Process Model lib/pgbus/process/ (supervisor, worker, dispatcher, consumer)
57
+ Layer 3: Event Bus lib/pgbus/event_bus/ (publisher, subscriber, registry, handler)
58
+ Layer 2: ActiveJob lib/pgbus/active_job/ (adapter, executor)
59
+ Layer 1: Client lib/pgbus/client.rb (PGMQ wrapper)
60
+ Layer 0: Config lib/pgbus/configuration.rb, config_loader.rb
61
+ ```
62
+
63
+ ## Key Design Decisions
64
+
65
+ - Worker recycling via `max_jobs_per_worker`, `max_memory_mb`, `max_worker_lifetime` — fixes solid_queue's memory leak problem
66
+ - LISTEN/NOTIFY via PGMQ's `enable_notify_insert` for instant wake-up (polling as fallback only)
67
+ - Dead letter queues: after `max_retries` failed reads (tracked by PGMQ's `read_ct`), move to `_dlq` queue
68
+ - Idempotent events: `pgbus_processed_events` table with (event_id, handler_class) unique index
69
+ - Dashboard via Tailwind CDN + Turbo CDN — zero npm dependency
70
+
71
+ ## Queue Naming
72
+
73
+ All PGMQ queues are prefixed: `{queue_prefix}_{name}` (default: `pgbus_default`).
74
+ DLQ queues append `_dlq` suffix.
75
+
76
+ ## More Documentation
77
+
78
+ See `.claude/` directory:
79
+ - `commands/` — Slash command definitions
80
+ - `rules/` — Coding style, git workflow, testing, agents, performance, security
@@ -0,0 +1,10 @@
1
+ # Code of Conduct
2
+
3
+ "pgbus" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
4
+
5
+ * Participants will be tolerant of opposing views.
6
+ * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
+ * When interpreting the words and actions of others, participants should always assume good intentions.
8
+ * Behaviour which can be reasonably considered harassment will not be tolerated.
9
+
10
+ If you have any concerns about behaviour within this project, please contact us at ["mikael@mhenrixon.com"](mailto:"mikael@mhenrixon.com").