Summary
routers/schedules.py and routers/webhooks.py call four methods on the db facade (DatabaseManager in src/backend/database.py) that are not delegated to the underlying ScheduleOperations instance. The methods live orphaned in src/backend/db/schedules.py:518-598; there is no __getattr__ proxy on DatabaseManager.
Effect: every POST /api/agents/{name}/schedules/{id}/webhook (and the GET / DELETE variants, plus POST /api/webhooks/{token}) returns 500 on a live stack with AttributeError: 'DatabaseManager' object has no attribute 'generate_webhook_token'. The webhook trigger feature is non-functional in production.
This was introduced in commit c630931d (#291, ~Nov 2025) and went undetected because integration tests don't run in CI.
Reproduction (against a live stack)
TOKEN=$(curl -s -X POST http://localhost:8000/api/token -d 'username=admin&password=...' | jq -r .access_token)
AGENT="test-webhook-$(uuidgen | head -c 8)"
# Create agent and schedule (works)
curl -s -X POST http://localhost:8000/api/agents -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "{\"name\":\"$AGENT\"}"
SID=$(curl -s -X POST "http://localhost:8000/api/agents/$AGENT/schedules" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"x","cron_expression":"0 0 1 1 *","message":"noop","enabled":true,"timezone":"UTC"}' | jq -r .id)
# Generate webhook token — FAILS with 500
curl -s -X POST "http://localhost:8000/api/agents/$AGENT/schedules/$SID/webhook" -H "Authorization: Bearer $TOKEN"
# => {"detail":"Internal Server Error"}
# Backend logs:
# AttributeError: 'DatabaseManager' object has no attribute 'generate_webhook_token'
Affected callers
| File:Line |
Call |
Method on ScheduleOperations |
routers/schedules.py:493 |
db.generate_webhook_token(schedule_id) |
db/schedules.py:518 |
routers/schedules.py:522 |
db.get_webhook_status(schedule_id) |
db/schedules.py:578 |
routers/schedules.py:551 |
db.revoke_webhook_token(schedule_id) |
db/schedules.py:566 |
routers/webhooks.py:259 |
db.get_schedule_by_webhook_token(token) |
db/schedules.py:541 |
All four method bodies exist and are presumably correct (their unit-level callers work in the SQLite layer); only the facade delegation is missing.
Recommended fix
Add four pass-through methods to DatabaseManager in src/backend/database.py, in the existing "Schedule Management" section near line 705:
# Webhook token management (WEBHOOK-001, #291)
def generate_webhook_token(self, schedule_id: str):
return self._schedule_ops.generate_webhook_token(schedule_id)
def get_schedule_by_webhook_token(self, token: str):
return self._schedule_ops.get_schedule_by_webhook_token(token)
def revoke_webhook_token(self, schedule_id: str):
return self._schedule_ops.revoke_webhook_token(schedule_id)
def get_webhook_status(self, schedule_id: str):
return self._schedule_ops.get_webhook_status(schedule_id)
12 lines total, no logic changes.
Acceptance criteria
Severity
HIGH — functional regression, not security. Rate limiter / ACL / network isolation are all sound; the feature is just unreachable.
Related
Summary
routers/schedules.pyandrouters/webhooks.pycall four methods on thedbfacade (DatabaseManagerinsrc/backend/database.py) that are not delegated to the underlyingScheduleOperationsinstance. The methods live orphaned insrc/backend/db/schedules.py:518-598; there is no__getattr__proxy onDatabaseManager.Effect: every
POST /api/agents/{name}/schedules/{id}/webhook(and theGET/DELETEvariants, plusPOST /api/webhooks/{token}) returns 500 on a live stack withAttributeError: 'DatabaseManager' object has no attribute 'generate_webhook_token'. The webhook trigger feature is non-functional in production.This was introduced in commit
c630931d(#291, ~Nov 2025) and went undetected because integration tests don't run in CI.Reproduction (against a live stack)
Affected callers
ScheduleOperationsrouters/schedules.py:493db.generate_webhook_token(schedule_id)db/schedules.py:518routers/schedules.py:522db.get_webhook_status(schedule_id)db/schedules.py:578routers/schedules.py:551db.revoke_webhook_token(schedule_id)db/schedules.py:566routers/webhooks.py:259db.get_schedule_by_webhook_token(token)db/schedules.py:541All four method bodies exist and are presumably correct (their unit-level callers work in the SQLite layer); only the facade delegation is missing.
Recommended fix
Add four pass-through methods to
DatabaseManagerinsrc/backend/database.py, in the existing "Schedule Management" section near line 705:12 lines total, no logic changes.
Acceptance criteria
DatabaseManagertests/integration/test_webhook_rate_limit.py::test_webhook_rate_limit_returns_429_after_threshold(added in PR fix(security): lock down Redis — auth + ACL + network split (#589) #643) passes against a live stackwebhook_urlfielddb.<method>(...)call site inrouters/andservices/resolves to a real method onDatabaseManager(lint-style guard against regression — would have caught this in CI)Severity
HIGH — functional regression, not security. Rate limiter / ACL / network isolation are all sound; the feature is just unreachable.
Related
c630931dtests/integration/test_webhook_rate_limit.pyagainst a live stack