@camstack/types 1.2.169 → 1.2.171

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.
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Birth-decision ledger — one durable row per track-birth VERDICT (D409).
3
+ *
4
+ * ── Why this exists ────────────────────────────────────────────────────────
5
+ * On 2026-09-08 the operator marked eleven tracks for review and five of them
6
+ * said "traccia partita in ritardo" — the track appears to begin long after the
7
+ * subject entered the scene. Two causes were proposed and NEITHER was measured:
8
+ *
9
+ * - the confirmation gate's deferral (`maxDeferralMs`, 2 s). This is now
10
+ * DISPROVEN as a cause of a late TIMELINE: a track's `firstSeen` and its
11
+ * first position are the CANDIDATE's first frame, not the confirmation's.
12
+ * Verified on `d035f251` (device 3829): first position == `firstSeen`, with
13
+ * confirmation 2 163 ms later.
14
+ * - the detector simply not firing until the subject is well into the scene.
15
+ * Unmeasured, and the reason this table exists.
16
+ *
17
+ * A second, real defect is also unquantified. The deferred-birth re-offer in
18
+ * `index.ts` takes the track from `result.tracked` with NO `matchedThisFrame`
19
+ * check, so a birth can be CONFIRMED on a frame the subject was never observed
20
+ * in. D379's carried birth evidence means some of those confirmations rest on a
21
+ * real observation of the birth instant anyway — and how often is exactly what
22
+ * nobody knows. {@link BirthDecisionRecord.decidedOnCoastedFrame} and
23
+ * {@link BirthDecisionRecord.decidedByBirthEvidence} are the two columns that
24
+ * answer it together.
25
+ *
26
+ * ── Why a TABLE and not a log line ─────────────────────────────────────────
27
+ * `logs.query` is an in-memory ring. Measured on the live hub on 2026-09-08 it
28
+ * held 40 000 entries covering 38 minutes — the whole ring is under an hour of
29
+ * fleet traffic. Every question this data is for ("how often, on camera 617,
30
+ * over the week the operator complained about") outlives that ring by two
31
+ * orders of magnitude, and a previous investigation in this repo was blocked
32
+ * for precisely this reason. The gate ALREADY writes a log line per decision
33
+ * (`confirmation gate: birth confirmed/suppressed/undecided`); adding a
34
+ * fourteenth field to it would answer nothing a day later.
35
+ *
36
+ * This is a MEASUREMENT table. It changes no behaviour, gates nothing, and
37
+ * nothing reads it on the frame path.
38
+ */
39
+ import { z } from 'zod';
40
+ /**
41
+ * How many birth decisions the cluster keeps, across every camera.
42
+ *
43
+ * Sized off a MEASURED rate, not a guess. Fleet-wide on the live hub,
44
+ * 2026-09-08 20:16 local, over the 38 minutes the log ring covered: 17 `track
45
+ * started` + 14 `birth suppressed — track record retracted` = 31 decided
46
+ * births, i.e. ~49/hour, ~1 200/day. Call a busy daytime hour five times that
47
+ * and the fleet writes ~6 000 rows/day.
48
+ *
49
+ * 30 000 rows is therefore ~25 days at the measured rate and ~5 days at five
50
+ * times it — comfortably past "a few days of traffic", which is the bar the
51
+ * operator's week-long complaint sets. At ~200 bytes a row the ceiling is
52
+ * ~6 MB, against a `tracks` table measured at 4.6 MB for a single 501-row page.
53
+ *
54
+ * A COUNT and not an age, for the same reason as the debug-note archive: the
55
+ * denominator of a miss rate is only meaningful over whatever window the table
56
+ * actually holds, and a quiet fleet should keep more of it, not less. Trimmed
57
+ * oldest-first on append.
58
+ */
59
+ export declare const MAX_BIRTH_DECISION_RECORDS = 30000;
60
+ /** Default page size for `listBirthDecisions` when the caller omits one. */
61
+ export declare const BIRTH_DECISION_DEFAULT_LIMIT = 500;
62
+ /**
63
+ * How stale a motion onset may be and still be offered as the birth-latency
64
+ * proxy. Beyond this the row records `null`, never a large number.
65
+ *
66
+ * 60 s is one order of magnitude above the latency being measured (the
67
+ * complaint is "seconds late") and one below the duration of a busy scene's
68
+ * continuous motion burst, where the onset is minutes old and says nothing
69
+ * about THIS subject. See {@link BirthDecisionRecord.birthLatencyMs} for the
70
+ * full error bars.
71
+ */
72
+ export declare const MAX_BIRTH_LATENCY_PROXY_MS = 60000;
73
+ /**
74
+ * What the gate decided about a birth, from the CALLER's point of view.
75
+ *
76
+ * Deliberately not `ConfirmationVerdict`: `undecided` is not a birth decision
77
+ * at all — it is a deferral, and the row is written when the deferral ENDS.
78
+ * The third member is the one the gate's own type cannot express, because
79
+ * exhaustion is a property of how long the caller waited.
80
+ */
81
+ export declare const BirthDecisionVerdictSchema: z.ZodEnum<{
82
+ confirmed: "confirmed";
83
+ suppressed: "suppressed";
84
+ "exhausted-fallback": "exhausted-fallback";
85
+ }>;
86
+ export type BirthDecisionVerdict = z.infer<typeof BirthDecisionVerdictSchema>;
87
+ /** One birth decision, as it is kept. */
88
+ export declare const BirthDecisionRecordSchema: z.ZodObject<{
89
+ id: z.ZodString;
90
+ at: z.ZodNumber;
91
+ deviceId: z.ZodNumber;
92
+ sourceTrackId: z.ZodString;
93
+ className: z.ZodString;
94
+ verdict: z.ZodEnum<{
95
+ confirmed: "confirmed";
96
+ suppressed: "suppressed";
97
+ "exhausted-fallback": "exhausted-fallback";
98
+ }>;
99
+ reason: z.ZodNullable<z.ZodString>;
100
+ attempts: z.ZodNumber;
101
+ deferredForMs: z.ZodNumber;
102
+ decidedOnCoastedFrame: z.ZodBoolean;
103
+ birthEvidenceAvailable: z.ZodBoolean;
104
+ decidedByBirthEvidence: z.ZodBoolean;
105
+ bestScore: z.ZodNullable<z.ZodNumber>;
106
+ appliedMinConfidence: z.ZodNullable<z.ZodNumber>;
107
+ firstSeen: z.ZodNullable<z.ZodNumber>;
108
+ motionOnsetAt: z.ZodNullable<z.ZodNumber>;
109
+ birthLatencyMs: z.ZodNullable<z.ZodNumber>;
110
+ birthIndexInBurst: z.ZodNullable<z.ZodNumber>;
111
+ }, z.core.$strip>;
112
+ export type BirthDecisionRecord = z.infer<typeof BirthDecisionRecordSchema>;
113
+ /** Query input for `listBirthDecisions` — newest first, one camera or all. */
114
+ export declare const BirthDecisionQueryInputSchema: z.ZodObject<{
115
+ deviceId: z.ZodOptional<z.ZodNumber>;
116
+ since: z.ZodOptional<z.ZodNumber>;
117
+ verdict: z.ZodOptional<z.ZodEnum<{
118
+ confirmed: "confirmed";
119
+ suppressed: "suppressed";
120
+ "exhausted-fallback": "exhausted-fallback";
121
+ }>>;
122
+ limit: z.ZodOptional<z.ZodNumber>;
123
+ }, z.core.$strip>;
124
+ export type BirthDecisionQueryInput = z.infer<typeof BirthDecisionQueryInputSchema>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Debug-note archive — the durable corpus of what operators asked to be
3
+ * checked, and the ONE thing about a debug note that outlives its track.
4
+ *
5
+ * D353 bound `debugNote` to the `debug` flag: turning the flag off clears the
6
+ * note in the same statement, so a track can never present a question without
7
+ * the flag that explains it. That invariant is right and stays. Its side
8
+ * effect was not: the act of REVIEWING a debug set destroyed every note in it,
9
+ * and the notes that survived a sweep aged out with their tracks anyway. On
10
+ * 2026-09-08 the notes from one morning's review — 25 to 27 of them — were
11
+ * already unreadable by the afternoon, leaving 11.
12
+ *
13
+ * So the clear now ARCHIVES first (D405). A row here is:
14
+ *
15
+ * - written ONLY by the hub, from `applyTrackFlags`, immediately before the
16
+ * note is cleared off the track row;
17
+ * - keyed by ITS OWN id, derived from `(sourceTrackId, note)` so reviewing
18
+ * the same track twice cannot double the corpus;
19
+ * - NOT a track row, NOT subject to track retention, and NOT a durability pin
20
+ * on anything — the track it names is expected to be gone.
21
+ *
22
+ * `sourceTrackId` is deliberately not called `trackId`. The analytics
23
+ * retention model derives track OWNERSHIP from a declared `trackId` column
24
+ * (`collection-classification.ts`), and a row that the cascade would delete
25
+ * with its track is exactly the row this table exists to keep. The name says
26
+ * PROVENANCE — the same distinction the stationary registry draws for the same
27
+ * reason.
28
+ */
29
+ import { z } from 'zod';
30
+ /**
31
+ * How many archived notes the cluster keeps, across every camera.
32
+ *
33
+ * GLOBAL, not per-device, and a COUNT, not an age. Both halves are deliberate:
34
+ *
35
+ * - A per-device bound multiplies by the fleet — 30 cameras at 500 each is
36
+ * 15 000 rows to protect a corpus that only ever gets read fleet-wide (the
37
+ * thing being mined is "what keeps going wrong", not "what goes wrong on
38
+ * 617"). One global ceiling is one number to reason about.
39
+ * - An AGE bound would delete the corpus for being old, which is the failure
40
+ * being fixed: a note from six months ago is the most valuable row in the
41
+ * table precisely because the pattern it describes has had time to repeat.
42
+ *
43
+ * The number is sized off the measured rate. The 2026-09-08 sweep produced
44
+ * 25–27 notes; call a heavy day 100. 5 000 rows is roughly fifty such days,
45
+ * and at the observed rate (a few sweeps a week) several years. The cost is
46
+ * bounded by construction: a note is capped at `MAX_TRACK_DEBUG_NOTE_LEN`
47
+ * (500 chars) and the metadata beside it is under 100 bytes, so the ceiling is
48
+ * ~3 MB of text and the realistic occupancy is well under 1 MB — against a
49
+ * `tracks` table measured at 4.6 MB for a single 501-row page.
50
+ */
51
+ export declare const MAX_ARCHIVED_DEBUG_NOTES = 5000;
52
+ /** Default page size for `listArchivedDebugNotes` when the caller omits one. */
53
+ export declare const ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT = 200;
54
+ /** One archived note — the operator's words plus enough context to find what
55
+ * they were looking at, after the track itself is gone. */
56
+ export declare const ArchivedDebugNoteSchema: z.ZodObject<{
57
+ id: z.ZodString;
58
+ note: z.ZodString;
59
+ deviceId: z.ZodNumber;
60
+ sourceTrackId: z.ZodString;
61
+ archivedAt: z.ZodNumber;
62
+ trackStartedAt: z.ZodNullable<z.ZodNumber>;
63
+ trackClass: z.ZodNullable<z.ZodString>;
64
+ trackLabel: z.ZodNullable<z.ZodString>;
65
+ }, z.core.$strip>;
66
+ export type ArchivedDebugNote = z.infer<typeof ArchivedDebugNoteSchema>;
67
+ /** Query input for `listArchivedDebugNotes` — newest first, one camera or all. */
68
+ export declare const ArchivedDebugNoteQueryInputSchema: z.ZodObject<{
69
+ deviceId: z.ZodOptional<z.ZodNumber>;
70
+ limit: z.ZodOptional<z.ZodNumber>;
71
+ }, z.core.$strip>;
72
+ export type ArchivedDebugNoteQueryInput = z.infer<typeof ArchivedDebugNoteQueryInputSchema>;
@@ -297,6 +297,14 @@ export interface PipelineAnalyticsPackagePickedUpPayload {
297
297
  /** The `package-events` delivery row this pick-up pairs to. */
298
298
  readonly deliveredEventId: string;
299
299
  readonly className: string;
300
+ /**
301
+ * The track of the PERSON who collected the parcel, when one was resolved
302
+ * (D404). It is also the pick-up row's media owner, which is what puts a
303
+ * frame of the collector — rather than of the empty doormat — on the
304
+ * notification. Absent means nobody qualified near the parcel's last
305
+ * sighting; the pick-up still fires, carrying the parcel's own owner.
306
+ */
307
+ readonly collectorTrackId?: string;
300
308
  readonly timestamp: number;
301
309
  readonly [key: string]: unknown;
302
310
  }
@@ -3518,6 +3518,8 @@ function createDeviceProxy(api, binding, opts) {
3518
3518
  reconcileFromDisk: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "reconcileFromDisk", "mutation", input),
3519
3519
  deleteTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "deleteTracks", "mutation", input),
3520
3520
  setTrackFlags: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "setTrackFlags", "mutation", input),
3521
+ listArchivedDebugNotes: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listArchivedDebugNotes", "query", input),
3522
+ listBirthDecisions: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listBirthDecisions", "query", input),
3521
3523
  getEventStoreFootprint: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventStoreFootprint", "query", input),
3522
3524
  getEventMediaFootprintByKind: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventMediaFootprintByKind", "query", input),
3523
3525
  pruneEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEvents", "mutation", input),
@@ -3518,6 +3518,8 @@ function createDeviceProxy(api, binding, opts) {
3518
3518
  reconcileFromDisk: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "reconcileFromDisk", "mutation", input),
3519
3519
  deleteTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "deleteTracks", "mutation", input),
3520
3520
  setTrackFlags: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "setTrackFlags", "mutation", input),
3521
+ listArchivedDebugNotes: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listArchivedDebugNotes", "query", input),
3522
+ listBirthDecisions: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listBirthDecisions", "query", input),
3521
3523
  getEventStoreFootprint: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventStoreFootprint", "query", input),
3522
3524
  getEventMediaFootprintByKind: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventMediaFootprintByKind", "query", input),
3523
3525
  pruneEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEvents", "mutation", input),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.169",
3
+ "version": "1.2.171",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",