1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//! The kernel's perception-consumer seam.
//!
//! The media + AI-task kernel is domain-agnostic: it samples frames, accepts detections from workers,
//! and persists them. Anything that *interprets* those detections into domain events — the zone engine
//! (spatial rules), the ANPR/access-control engine (plate authorization), and other domain
//! verticals — plug in here as a [`DetectionConsumer`] rather than being wired
//! into the ingest handler or [`crate::state::AppState`] directly.
//!
//! This inverts the dependency: ingest fans a committed batch out to a registry of consumers; a
//! consumer self-declares which `task_type`s it cares about. New apps are *added* to the registry (and,
//! after the crate split, linked in by the composing binary or fed over the event stream) — the kernel
//! never gains an `if task_type == "..."` branch.
use Arc;
use ;
use SqlitePool;
use crateDetectionIngest;
/// One committed batch of detections handed to consumers after it is persisted. Carries the
/// site/camera/task context so a consumer needs no extra lookups (and so the seam is tenant-aware for
/// distributed deployments).
/// A pluggable interpreter of detection batches. Implementors live in their own module/crate (zones is
/// kernel-open; ANPR/entry is a proprietary app) and are registered into [`crate::state::AppState`].
/// Drive every interested consumer for one committed batch, AT MOST ONCE per
/// `(consumer, camera_id, frame_id)`.
///
/// This is the single fan-out path used by both the ingest handler (inline, low-latency) and the
/// durable drainer ([`crate::services::fanout`], which replays batches whose fan-out didn't complete
/// because the process crashed between commit and fan-out). The per-consumer claim row in
/// `consumer_fanout` is what makes replay safe: a consumer that already saw this frame is skipped, so
/// a replayed batch never double-drives it (no double-counted ANPR votes / zone state).
///
/// A batch without a `frame_id` cannot be deduped, so its consumers are driven directly and it is not
/// eligible for durable replay (the worker opted out of the idempotency key).
///
/// Returns `true` when every interested consumer was driven or was already done — i.e. the batch is
/// fully fanned out and may be marked complete. Returns `false` if a dedup-claim write failed for some
/// consumer, so the caller leaves the batch un-fanned for the drainer to retry (the consumers that
/// already succeeded stay claimed and will not re-run).
pub async