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.
- checksums.yaml +7 -0
- data/.bun-version +1 -0
- data/.claude/commands/architect.md +100 -0
- data/.claude/commands/github-review-comments.md +237 -0
- data/.claude/commands/lfg.md +271 -0
- data/.claude/commands/review-pr.md +69 -0
- data/.claude/commands/security.md +122 -0
- data/.claude/commands/tdd.md +148 -0
- data/.claude/rules/agents.md +49 -0
- data/.claude/rules/coding-style.md +91 -0
- data/.claude/rules/git-workflow.md +56 -0
- data/.claude/rules/performance.md +73 -0
- data/.claude/rules/testing.md +67 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +80 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +417 -0
- data/Rakefile +14 -0
- data/app/controllers/pgbus/api/stats_controller.rb +11 -0
- data/app/controllers/pgbus/application_controller.rb +35 -0
- data/app/controllers/pgbus/dashboard_controller.rb +27 -0
- data/app/controllers/pgbus/dead_letter_controller.rb +50 -0
- data/app/controllers/pgbus/events_controller.rb +23 -0
- data/app/controllers/pgbus/jobs_controller.rb +48 -0
- data/app/controllers/pgbus/processes_controller.rb +10 -0
- data/app/controllers/pgbus/queues_controller.rb +21 -0
- data/app/helpers/pgbus/application_helper.rb +69 -0
- data/app/views/layouts/pgbus/application.html.erb +76 -0
- data/app/views/pgbus/dashboard/_processes_table.html.erb +30 -0
- data/app/views/pgbus/dashboard/_queues_table.html.erb +39 -0
- data/app/views/pgbus/dashboard/_recent_failures.html.erb +33 -0
- data/app/views/pgbus/dashboard/_stats_cards.html.erb +28 -0
- data/app/views/pgbus/dashboard/show.html.erb +10 -0
- data/app/views/pgbus/dead_letter/_messages_table.html.erb +40 -0
- data/app/views/pgbus/dead_letter/index.html.erb +15 -0
- data/app/views/pgbus/dead_letter/show.html.erb +52 -0
- data/app/views/pgbus/events/index.html.erb +57 -0
- data/app/views/pgbus/events/show.html.erb +28 -0
- data/app/views/pgbus/jobs/_enqueued_table.html.erb +34 -0
- data/app/views/pgbus/jobs/_failed_table.html.erb +45 -0
- data/app/views/pgbus/jobs/index.html.erb +16 -0
- data/app/views/pgbus/jobs/show.html.erb +57 -0
- data/app/views/pgbus/processes/_processes_table.html.erb +37 -0
- data/app/views/pgbus/processes/index.html.erb +3 -0
- data/app/views/pgbus/queues/_queues_list.html.erb +41 -0
- data/app/views/pgbus/queues/index.html.erb +3 -0
- data/app/views/pgbus/queues/show.html.erb +49 -0
- data/bun.lock +18 -0
- data/config/routes.rb +45 -0
- data/docs/README.md +28 -0
- data/docs/switch_from_good_job.md +279 -0
- data/docs/switch_from_sidekiq.md +226 -0
- data/docs/switch_from_solid_queue.md +247 -0
- data/exe/pgbus +7 -0
- data/lib/generators/pgbus/install_generator.rb +56 -0
- data/lib/generators/pgbus/templates/migration.rb.erb +114 -0
- data/lib/generators/pgbus/templates/pgbus.yml.erb +74 -0
- data/lib/generators/pgbus/templates/pgbus_binstub.erb +7 -0
- data/lib/pgbus/active_job/adapter.rb +109 -0
- data/lib/pgbus/active_job/executor.rb +107 -0
- data/lib/pgbus/batch.rb +153 -0
- data/lib/pgbus/cli.rb +84 -0
- data/lib/pgbus/client.rb +162 -0
- data/lib/pgbus/concurrency/blocked_execution.rb +74 -0
- data/lib/pgbus/concurrency/semaphore.rb +66 -0
- data/lib/pgbus/concurrency.rb +65 -0
- data/lib/pgbus/config_loader.rb +27 -0
- data/lib/pgbus/configuration.rb +99 -0
- data/lib/pgbus/engine.rb +31 -0
- data/lib/pgbus/event.rb +31 -0
- data/lib/pgbus/event_bus/handler.rb +76 -0
- data/lib/pgbus/event_bus/publisher.rb +42 -0
- data/lib/pgbus/event_bus/registry.rb +54 -0
- data/lib/pgbus/event_bus/subscriber.rb +30 -0
- data/lib/pgbus/process/consumer.rb +113 -0
- data/lib/pgbus/process/dispatcher.rb +154 -0
- data/lib/pgbus/process/heartbeat.rb +71 -0
- data/lib/pgbus/process/signal_handler.rb +49 -0
- data/lib/pgbus/process/supervisor.rb +198 -0
- data/lib/pgbus/process/worker.rb +153 -0
- data/lib/pgbus/serializer.rb +43 -0
- data/lib/pgbus/version.rb +5 -0
- data/lib/pgbus/web/authentication.rb +24 -0
- data/lib/pgbus/web/data_source.rb +406 -0
- data/lib/pgbus.rb +49 -0
- data/package.json +9 -0
- data/sig/pgbus.rbs +4 -0
- 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
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
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -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").
|