M4: implement n8n automation
Outbox dispatcher (background thread, FOR UPDATE SKIP LOCKED claim, exponential backoff, no transaction held during HTTP I/O). n8n callback endpoint with shared-secret auth and idempotency by event ID. Automation nav + UI with manual retry. 49 backend tests passing, ruff clean. Fixed a crash-on-redelivery bug in seeded outbox payloads and made the dispatcher defensive against malformed payloads. Verified the full live round trip against a real n8n instance: return -> outbox -> dispatcher -> n8n workflow -> callback -> succeeded, including the S5 failed-retry demo scenario.
This commit is contained in:
@@ -235,16 +235,37 @@ def load_seed(db: Session) -> SeedResult:
|
||||
db.execute(insert(DataQualityIssue), dq_rows)
|
||||
counts["data_quality_issues"] = len(dq_rows)
|
||||
|
||||
vehicle_ref_by_booking_ref = {
|
||||
row["public_ref"]: row["vehicle_ref"] for row in _read_csv("bookings.csv")
|
||||
}
|
||||
|
||||
outbox_rows = []
|
||||
for row in _read_csv("workflow_runs.csv"):
|
||||
booking_id = booking_id_by_ref.get(row["aggregate_ref"])
|
||||
# Build the same schema-complete envelope the live return workflow (M2) produces,
|
||||
# so a seeded/historical event is redeliverable (e.g. via manual retry) without the
|
||||
# dispatcher crashing on a missing key. See PROJECT_STATE.md M4 notes.
|
||||
outbox_rows.append(
|
||||
{
|
||||
"event_id": uuid.UUID(row["event_id"]),
|
||||
"event_type": row["event_type"],
|
||||
"aggregate_type": "booking",
|
||||
"aggregate_id": booking_id or uuid.uuid4(),
|
||||
"payload_json": {"aggregate_ref": row["aggregate_ref"]},
|
||||
"payload_json": {
|
||||
"correlation_id": str(uuid.uuid4()),
|
||||
"aggregate": {
|
||||
"type": "booking",
|
||||
"id": str(booking_id or uuid.uuid4()),
|
||||
"public_ref": row["aggregate_ref"],
|
||||
},
|
||||
"data": {
|
||||
"vehicle_ref": vehicle_ref_by_booking_ref.get(row["aggregate_ref"], ""),
|
||||
"inspection_ref": "",
|
||||
"resulting_vehicle_status": "cleaning",
|
||||
"attention_reasons": [],
|
||||
},
|
||||
"aggregate_ref": row["aggregate_ref"],
|
||||
},
|
||||
"occurred_at": _parse_dt(row["occurred_at"]),
|
||||
"delivery_status": row["status"],
|
||||
"attempts": int(row["attempts"]),
|
||||
|
||||
Reference in New Issue
Block a user