Skip to content

fix(webhooks): missing DatabaseManager delegation breaks all schedule webhook endpoints (WEBHOOK-001 facade gap) #647

Description

@AndriiPasternak31

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

  • All four delegation methods added on DatabaseManager
  • tests/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 stack
  • Manual reproduction above no longer 500s — returns 200 with a webhook_url field
  • Add a unit test that asserts every db.<method>(...) call site in routers/ and services/ resolves to a real method on DatabaseManager (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

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions