@camstack/addon-post-analysis 1.2.307 → 1.2.308

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.
@@ -1,4 +1,4 @@
1
- import { $ as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, $n as ruleDeviceCompatCapQueries, $t as TimelapseRuleInputSchema, A as EMBEDDING_REQUIREMENTS_FAILED, An as isDetectionMacroClass, Ar as ZodObject, At as NcTemplateVarDescriptorSchema, B as ListTrackRelationsInputSchema, Bn as memoizePinnedFfmpeg, Br as record, Bt as SCENE_DEFAULT_ANCHOR_THRESHOLD, C as DEFAULT_EVENT_COLOR, Cn as encodeVectorBase64, Cr as customAction, Ct as NcSnoozeInputSchema, D as DeclaredDevices, Dn as gifDeriveSpeed, Dr as nodePin, Dt as NcTaxonomySchema, E as DETECTION_PIPELINE_CAP_NAME, En as failureContributionCapability, Er as isDeviceScopedCap, Et as NcSystemEventKindSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as isSystemDelivery, Fr as literal, Ft as RECORDING_SOURCE_CAMSTACK_ADDON, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as plateGalleryCapability, Gr as unknown, Gt as SCENE_DIVERGED, H as LockStateSchema, Hn as occupancyScope, Hr as string, Ht as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, I as FIRST_LEVEL_MACRO_CLASSES, In as isSystemEventSummaryRule, Ir as looseObject, It as RebuildObjectEmbeddingsRefusalSchema, J as MOTION_CLOSE_AFTER_MS, Jn as readTimelapseGeneratedAt, Jt as SetTrackPurityInputSchema, K as MAX_TRACK_PURITIES, Kn as readDeviceStateFrom, Kr as EventCategory, Kt as SCENE_RESET_RECAPTURES, L as FULL_IMAGE_BBOX, Ln as isVectorIndexDimConflict, Lr as number, Lt as RetrainStatusSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as isKnownTemplateVar, Mr as array, Mt as OpsLogEntrySchema, N as EVENT_KIND_BY_CAP, Nn as isMediaOwnerType, Nr as boolean, O as DetectionSourceSchema, On as gifFromMp4Args, Ot as NcTemplatePreviewInputSchema, P as EVENT_OWNER_TYPES, Pn as isScheduleActive, Pr as discriminatedUnion, Pt as RECORDING_EXPORT_MAX_READ_BYTES, Q as NC_AUDIO_CONFIRM_HITS_DEFAULT, Qn as ruleBelongsToAlarm$1, Qt as TRACK_RELATIONS_DEFAULT_LIMIT, R as FailureCounters, Rn as kebabToCamel, Rr as object, Rt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, S as CoverStateSchema, Sr as createEvent, St as NcScheduleSchema, T as DETECTION_MACRO_CLASSES, Tn as faceGalleryCapability, Tr as hydrateSchema, Tt as NcSnoozeSuppressedSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as tuple, Ut as SCENE_DEFAULT_QUIET_SECONDS, V as ListTrackRelationsResultSchema, Vn as notificationRulesCapability, Vr as strictObject, Vt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, W as MAX_BIRTH_DECISION_RECORDS, Wn as pipelineAnalyticsCapability, Wr as union, Wt as SCENE_DEFAULT_UNCOVERED_POLICY, X as MotionTriggeredBySchema, Xn as resolveLocationMode, Xt as SetTrackRelationInputSchema, Y as MediaFileKindEnum, Yn as resolveClipSearchMinScore, Yt as SetTrackPurityResultSchema, Z as NC_ALARM_SYSTEM_EVENT_KINDS, Zt as TIMELAPSE_DENSE_FLOOR_SEC, _ as BaseDevice, _n as describeSystemEventFamilyProblem, _r as isSourceCap, _t as NcRuleInputSchema, a as CLIP_EMBEDDING_STEP_ID, an as TrackSideSchema, ar as subKindsOf, at as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, b as ClearTrackRelationInputSchema, bn as embeddingRefusalOf, br as BaseAddon, bt as NcRuleTargetSchema, c as resolveClusterModelPin, cn as alarmPanelCapability, cr as systemEventFilterApplies, ct as NC_SNOOZE_MAX_MINUTES, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as audioModeOf, dr as templateVarsFor, dt as NC_TAXONOMY, en as TimelapseRulePatchSchema, er as ruleKindOf, et as NC_CLIP_POST_ROLL_DEFAULT_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as buildEventKindDescriptor, fr as vectorDimFromBase64, ft as NC_TEMPLATE_VARS, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as describeSystemEventDeliveryProblem, gr as evaluateSensorEdge, gt as NcConditionsSchema, h as ArchivedDebugNoteSchema, hn as deriveRecordingMode, hr as canonicalHash, ht as NcConditionDescriptorSchema, in as TrackRelationSchema, ir as storageOccupancyCapability, it as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as isEventOwnerType, jr as _enum, jt as OPS_LOG_DEFAULT_LIMIT, k as EMBEDDING_MODEL_FAILED, kn as isAudioClassName, kr as sleep$1, kt as NcTemplatePreviewSchema, l as APPEARANCE_BIN_COUNT, ln as assertTimelapseCadences, lr as systemEventSummaryScheduleOf, lt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, m as AlarmArmModeSchema, mn as cosineSimilarity$1, mr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, mt as NcAlarmSettingsPatchSchema, nn as TrackPuritySchema, nr as sensorRaisedOf, nt as NC_CLIP_RETENTION_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, on as TrackSourceSchema, or as systemEventDeliveryProblems, ot as NC_DEFAULT_SNOOZE_MINUTES, p as AUDIO_MACRO_LABELS, pn as consumableLowPercentOf, pr as zoneAnalyticsCapability, pt as NcAlarmConfigSchema, q as MAX_TRACK_RELATIONS, qn as readMotionTriggeredBy, qt as SceneMonitorSchema, rn as TrackRelationKindSchema, rr as servedTemplateVars, rt as NC_CONDITION_CATALOG, s as readClusterModelPinStrict, sn as addonWidgetsSourceCapability, sr as systemEventFamilyProblems, st as NC_SENSOR_TRANSITION_KINDS, t as BUILTIN_CLIP_MODELS, tn as TimelapseRuleSchema, tr as sceneMonitorCapability, tt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, u as APPEARANCE_BLOCKS, un as audioMetricsCapability, ur as templateCarriesSummary, ut as NC_TANK_SYSTEM_EVENT_KINDS, v as BirthDecisionRecordSchema, vr as DeviceType, vt as NcRulePatchSchema, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as evictionPolicyOfLocation, wr as defineCustomActions, wt as NcSnoozeSchema, x as ClearTrackRelationResultSchema, xr as CamProfileSchema, xt as NcRuleUpdateInputSchema, y as COCO_TO_MACRO, yr as errMsg, yt as NcRuleSchema, z as LabelAttributionSchema, zn as mayWriteToLocation, zr as partialRecord, zt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS } from "../clip-model-registry-C9Aw4qkh.mjs";
1
+ import { $ as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, $n as ruleDeviceCompatCapQueries, $t as TimelapseRuleInputSchema, A as EMBEDDING_REQUIREMENTS_FAILED, An as isDetectionMacroClass, Ar as ZodObject, At as NcTemplateVarDescriptorSchema, B as ListTrackRelationsInputSchema, Bn as memoizePinnedFfmpeg, Br as record, Bt as SCENE_DEFAULT_ANCHOR_THRESHOLD, C as DEFAULT_EVENT_COLOR, Cn as encodeVectorBase64, Cr as customAction, Ct as NcSnoozeInputSchema, D as DeclaredDevices, Dn as gifDeriveSpeed, Dr as nodePin, Dt as NcTaxonomySchema, E as DETECTION_PIPELINE_CAP_NAME, En as failureContributionCapability, Er as isDeviceScopedCap, Et as NcSystemEventKindSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as isSystemDelivery, Fr as literal, Ft as RECORDING_SOURCE_CAMSTACK_ADDON, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as plateGalleryCapability, Gr as unknown, Gt as SCENE_DIVERGED, H as LockStateSchema, Hn as occupancyScope, Hr as string, Ht as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, I as FIRST_LEVEL_MACRO_CLASSES, In as isSystemEventSummaryRule, Ir as looseObject, It as RebuildObjectEmbeddingsRefusalSchema, J as MOTION_CLOSE_AFTER_MS, Jn as readTimelapseGeneratedAt, Jt as SetTrackPurityInputSchema, K as MAX_TRACK_PURITIES, Kn as readDeviceStateFrom, Kr as EventCategory, Kt as SCENE_RESET_RECAPTURES, L as FULL_IMAGE_BBOX, Ln as isVectorIndexDimConflict, Lr as number, Lt as RetrainStatusSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as isKnownTemplateVar, Mr as array, Mt as OpsLogEntrySchema, N as EVENT_KIND_BY_CAP, Nn as isMediaOwnerType, Nr as boolean, O as DetectionSourceSchema, On as gifFromMp4Args, Ot as NcTemplatePreviewInputSchema, P as EVENT_OWNER_TYPES, Pn as isScheduleActive, Pr as discriminatedUnion, Pt as RECORDING_EXPORT_MAX_READ_BYTES, Q as NC_AUDIO_CONFIRM_HITS_DEFAULT, Qn as ruleBelongsToAlarm$1, Qt as TRACK_RELATIONS_DEFAULT_LIMIT, R as FailureCounters, Rn as kebabToCamel, Rr as object, Rt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, S as CoverStateSchema, Sr as createEvent, St as NcScheduleSchema, T as DETECTION_MACRO_CLASSES, Tn as faceGalleryCapability, Tr as hydrateSchema, Tt as NcSnoozeSuppressedSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as tuple, Ut as SCENE_DEFAULT_QUIET_SECONDS, V as ListTrackRelationsResultSchema, Vn as notificationRulesCapability, Vr as strictObject, Vt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, W as MAX_BIRTH_DECISION_RECORDS, Wn as pipelineAnalyticsCapability, Wr as union, Wt as SCENE_DEFAULT_UNCOVERED_POLICY, X as MotionTriggeredBySchema, Xn as resolveLocationMode, Xt as SetTrackRelationInputSchema, Y as MediaFileKindEnum, Yn as resolveClipSearchMinScore, Yt as SetTrackPurityResultSchema, Z as NC_ALARM_SYSTEM_EVENT_KINDS, Zt as TIMELAPSE_DENSE_FLOOR_SEC, _ as BaseDevice, _n as describeSystemEventFamilyProblem, _r as isSourceCap, _t as NcRuleInputSchema, a as CLIP_EMBEDDING_STEP_ID, an as TrackSideSchema, ar as subKindsOf, at as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, b as ClearTrackRelationInputSchema, bn as embeddingRefusalOf, br as BaseAddon, bt as NcRuleTargetSchema, c as resolveClusterModelPin, cn as alarmPanelCapability, cr as systemEventFilterApplies, ct as NC_SNOOZE_MAX_MINUTES, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as audioModeOf, dr as templateVarsFor, dt as NC_TAXONOMY, en as TimelapseRulePatchSchema, er as ruleKindOf, et as NC_CLIP_POST_ROLL_DEFAULT_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as buildEventKindDescriptor, fr as vectorDimFromBase64, ft as NC_TEMPLATE_VARS, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as describeSystemEventDeliveryProblem, gr as evaluateSensorEdge, gt as NcConditionsSchema, h as ArchivedDebugNoteSchema, hn as deriveRecordingMode, hr as canonicalHash, ht as NcConditionDescriptorSchema, in as TrackRelationSchema, ir as storageOccupancyCapability, it as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as isEventOwnerType, jr as _enum, jt as OPS_LOG_DEFAULT_LIMIT, k as EMBEDDING_MODEL_FAILED, kn as isAudioClassName, kr as sleep$1, kt as NcTemplatePreviewSchema, l as APPEARANCE_BIN_COUNT, ln as assertTimelapseCadences, lr as systemEventSummaryScheduleOf, lt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, m as AlarmArmModeSchema, mn as cosineSimilarity$1, mr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, mt as NcAlarmSettingsPatchSchema, nn as TrackPuritySchema, nr as sensorRaisedOf, nt as NC_CLIP_RETENTION_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, on as TrackSourceSchema, or as systemEventDeliveryProblems, ot as NC_DEFAULT_SNOOZE_MINUTES, p as AUDIO_MACRO_LABELS, pn as consumableLowPercentOf, pr as zoneAnalyticsCapability, pt as NcAlarmConfigSchema, q as MAX_TRACK_RELATIONS, qn as readMotionTriggeredBy, qt as SceneMonitorSchema, rn as TrackRelationKindSchema, rr as servedTemplateVars, rt as NC_CONDITION_CATALOG, s as readClusterModelPinStrict, sn as addonWidgetsSourceCapability, sr as systemEventFamilyProblems, st as NC_SENSOR_TRANSITION_KINDS, t as BUILTIN_CLIP_MODELS, tn as TimelapseRuleSchema, tr as sceneMonitorCapability, tt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, u as APPEARANCE_BLOCKS, un as audioMetricsCapability, ur as templateCarriesSummary, ut as NC_TANK_SYSTEM_EVENT_KINDS, v as BirthDecisionRecordSchema, vr as DeviceType, vt as NcRulePatchSchema, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as evictionPolicyOfLocation, wr as defineCustomActions, wt as NcSnoozeSchema, x as ClearTrackRelationResultSchema, xr as CamProfileSchema, xt as NcRuleUpdateInputSchema, y as COCO_TO_MACRO, yr as errMsg, yt as NcRuleSchema, z as LabelAttributionSchema, zn as mayWriteToLocation, zr as partialRecord, zt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS } from "../clip-model-registry-FGOIz3PZ.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs$1 from "node:fs";
4
4
  import { createWriteStream, promises } from "node:fs";
@@ -42578,6 +42578,280 @@ function parseDebugDeviceIds(url) {
42578
42578
  return out;
42579
42579
  }
42580
42580
  //#endregion
42581
+ //#region src/pipeline-analytics/store/tracking-tape-index-store.ts
42582
+ /**
42583
+ * @durable class=ledger owner=pipeline-analytics
42584
+ * write="one row per PROMOTED tracking-tape window (`debug.tape.promote`);
42585
+ * updated when the operator marks it reviewed or releases its pins;
42586
+ * deleted together with its file by `debug.tape.delete`."
42587
+ * retention="NOT an age clock and NOT track retention — a promoted window is
42588
+ * history being labelled (D681). Bounded by the promotion budget
42589
+ * (MAX_PROMOTED_TAPES = 60, 1 GiB): a promotion beyond it is REFUSED,
42590
+ * never satisfied by evicting another row."
42591
+ */
42592
+ var TRACKING_TAPES_COLLECTION = "pipeline-analytics:tracking-tapes";
42593
+ var TRACKING_TAPES_COLUMNS = [
42594
+ {
42595
+ name: "id",
42596
+ type: "TEXT",
42597
+ primaryKey: true,
42598
+ notNull: true
42599
+ },
42600
+ {
42601
+ name: "deviceId",
42602
+ type: "INTEGER",
42603
+ notNull: true
42604
+ },
42605
+ {
42606
+ name: "scenario",
42607
+ type: "TEXT",
42608
+ notNull: true
42609
+ },
42610
+ {
42611
+ name: "note",
42612
+ type: "TEXT"
42613
+ },
42614
+ {
42615
+ name: "lighting",
42616
+ type: "TEXT",
42617
+ notNull: true
42618
+ },
42619
+ {
42620
+ name: "fromMs",
42621
+ type: "INTEGER",
42622
+ notNull: true
42623
+ },
42624
+ {
42625
+ name: "toMs",
42626
+ type: "INTEGER",
42627
+ notNull: true
42628
+ },
42629
+ {
42630
+ name: "state",
42631
+ type: "TEXT",
42632
+ notNull: true
42633
+ },
42634
+ {
42635
+ name: "frames",
42636
+ type: "INTEGER",
42637
+ notNull: true
42638
+ },
42639
+ {
42640
+ name: "skips",
42641
+ type: "INTEGER",
42642
+ notNull: true
42643
+ },
42644
+ {
42645
+ name: "gaps",
42646
+ type: "INTEGER",
42647
+ notNull: true
42648
+ },
42649
+ {
42650
+ name: "bytes",
42651
+ type: "INTEGER",
42652
+ notNull: true
42653
+ },
42654
+ {
42655
+ name: "reviewed",
42656
+ type: "BOOLEAN",
42657
+ notNull: true
42658
+ },
42659
+ {
42660
+ name: "trackIds",
42661
+ type: "TEXT",
42662
+ notNull: true
42663
+ },
42664
+ {
42665
+ name: "createdAt",
42666
+ type: "INTEGER",
42667
+ notNull: true
42668
+ },
42669
+ {
42670
+ name: "releasedAt",
42671
+ type: "INTEGER"
42672
+ },
42673
+ {
42674
+ name: "pinsSettled",
42675
+ type: "BOOLEAN",
42676
+ notNull: true
42677
+ }
42678
+ ];
42679
+ var TrackingTapeRowSchema = object({
42680
+ id: string(),
42681
+ deviceId: number().int(),
42682
+ scenario: string(),
42683
+ note: string().nullable(),
42684
+ lighting: TapeLightingSchema,
42685
+ fromMs: number().int(),
42686
+ toMs: number().int(),
42687
+ state: _enum(["complete", "incomplete"]),
42688
+ frames: number().int(),
42689
+ skips: number().int(),
42690
+ gaps: number().int(),
42691
+ bytes: number().int(),
42692
+ reviewed: boolean(),
42693
+ trackIds: array(string()),
42694
+ createdAt: number().int(),
42695
+ releasedAt: number().int().nullable(),
42696
+ pinsSettled: boolean()
42697
+ });
42698
+ var TrackIdsSchema = array(string());
42699
+ /**
42700
+ * True when the SQLite settings backend would hand `text` back DECODED: it
42701
+ * parses any TEXT that starts with `[` or `{` and is valid JSON
42702
+ * (`decodeColumnValue` decodes by content, not by declared type). Free text
42703
+ * like that cannot round-trip through a TEXT column (follow-up 617, review M2).
42704
+ */
42705
+ function decodesAsJson(text) {
42706
+ if (!(text.startsWith("[") || text.startsWith("{"))) return false;
42707
+ try {
42708
+ JSON.parse(text);
42709
+ return true;
42710
+ } catch {
42711
+ return false;
42712
+ }
42713
+ }
42714
+ /**
42715
+ * Free text (`scenario`, `note`) as the column hands it back: a value the
42716
+ * backend decoded is re-encoded, so a row written before `debug.tape.promote`
42717
+ * refused such text never vanishes (its whitespace may differ). Anything else
42718
+ * goes to the schema as it came.
42719
+ */
42720
+ function freeText(raw) {
42721
+ return raw !== null && typeof raw === "object" ? JSON.stringify(raw) : raw;
42722
+ }
42723
+ /**
42724
+ * `trackIds` as the column hands it back. The SQLite backend decodes a TEXT
42725
+ * that starts with `[` on read (`decodeColumnValue`), so the store sees the
42726
+ * ARRAY it wrote; a string is parsed here only for a backend (or an old row)
42727
+ * that hands the text back as written. `JSON.parse(String(array))` threw on the
42728
+ * decoded array and every promoted row was skipped as malformed (follow-up 617).
42729
+ * `null` = unreadable, absent included (the column is NOT NULL, so absence is
42730
+ * corruption) — the row is reported, never repaired.
42731
+ */
42732
+ function decodeTrackIds(raw) {
42733
+ if (raw === void 0 || raw === null) return null;
42734
+ if (typeof raw !== "string") return raw;
42735
+ try {
42736
+ return JSON.parse(raw);
42737
+ } catch {
42738
+ return null;
42739
+ }
42740
+ }
42741
+ var TrackingTapeIndexStore = class {
42742
+ store;
42743
+ logger;
42744
+ constructor(deps) {
42745
+ this.store = deps.store;
42746
+ this.logger = deps.logger;
42747
+ }
42748
+ static async declare(store) {
42749
+ await store.declareCollection.mutate({
42750
+ collection: TRACKING_TAPES_COLLECTION,
42751
+ columns: [...TRACKING_TAPES_COLUMNS],
42752
+ indexes: [{
42753
+ name: "idx_trackingtapes_device_created",
42754
+ columns: ["deviceId", "createdAt"]
42755
+ }]
42756
+ });
42757
+ }
42758
+ async insert(row) {
42759
+ const { id, trackIds, ...rest } = TrackingTapeRowSchema.parse(row);
42760
+ await this.store.insert.mutate({
42761
+ collection: TRACKING_TAPES_COLLECTION,
42762
+ record: {
42763
+ id,
42764
+ data: {
42765
+ ...rest,
42766
+ trackIds: JSON.stringify(trackIds)
42767
+ }
42768
+ }
42769
+ });
42770
+ }
42771
+ async get(id) {
42772
+ const first = (await this.store.query.query({
42773
+ collection: TRACKING_TAPES_COLLECTION,
42774
+ filter: {
42775
+ where: { id },
42776
+ limit: 1
42777
+ }
42778
+ }))[0];
42779
+ return first === void 0 ? null : this.parse(first.id, first.data);
42780
+ }
42781
+ async list(deviceId) {
42782
+ const rows = await this.store.query.query({
42783
+ collection: TRACKING_TAPES_COLLECTION,
42784
+ filter: {
42785
+ ...deviceId !== void 0 ? { where: { deviceId } } : {},
42786
+ orderBy: {
42787
+ field: "createdAt",
42788
+ direction: "desc"
42789
+ },
42790
+ limit: 1e3
42791
+ }
42792
+ });
42793
+ const out = [];
42794
+ for (const r of rows) {
42795
+ const parsed = this.parse(r.id, r.data);
42796
+ if (parsed !== null) out.push(parsed);
42797
+ }
42798
+ return out;
42799
+ }
42800
+ async setReviewed(id, reviewed) {
42801
+ await this.store.update.mutate({
42802
+ collection: TRACKING_TAPES_COLLECTION,
42803
+ id,
42804
+ data: { reviewed }
42805
+ });
42806
+ }
42807
+ /** Release AND unsettle in one row write: the unpins the release implies
42808
+ * are owed from this moment, whether or not the caller lives to make them. */
42809
+ async setReleased(id, atMs) {
42810
+ await this.store.update.mutate({
42811
+ collection: TRACKING_TAPES_COLLECTION,
42812
+ id,
42813
+ data: {
42814
+ releasedAt: atMs,
42815
+ pinsSettled: false
42816
+ }
42817
+ });
42818
+ }
42819
+ async setPinsSettled(id, settled) {
42820
+ await this.store.update.mutate({
42821
+ collection: TRACKING_TAPES_COLLECTION,
42822
+ id,
42823
+ data: { pinsSettled: settled }
42824
+ });
42825
+ }
42826
+ async remove(id) {
42827
+ await this.store.delete.mutate({
42828
+ collection: TRACKING_TAPES_COLLECTION,
42829
+ key: id
42830
+ });
42831
+ }
42832
+ parse(id, data) {
42833
+ const ids = TrackIdsSchema.safeParse(decodeTrackIds(data["trackIds"]));
42834
+ const parsed = TrackingTapeRowSchema.safeParse({
42835
+ id,
42836
+ ...data,
42837
+ scenario: freeText(data["scenario"]),
42838
+ note: freeText(data["note"] ?? null),
42839
+ releasedAt: data["releasedAt"] ?? null,
42840
+ trackIds: ids.success ? ids.data : null
42841
+ });
42842
+ if (parsed.success) return parsed.data;
42843
+ this.logger.warn("tracking tape index: skipped a malformed row", {
42844
+ meta: {
42845
+ id,
42846
+ issues: parsed.error.issues.map((i) => i.path.join(".")),
42847
+ trackIdsType: typeof data["trackIds"]
42848
+ },
42849
+ ...typeof data["deviceId"] === "number" ? { tags: { deviceId: data["deviceId"] } } : {}
42850
+ });
42851
+ return null;
42852
+ }
42853
+ };
42854
+ //#endregion
42581
42855
  //#region src/pipeline-analytics/debug/tape-actions.ts
42582
42856
  /**
42583
42857
  * `debug.tape.*` — the tracking-tape surface (D681, spec §3.3). Admin,
@@ -42608,12 +42882,19 @@ var TapeStatusRowSchema = object({
42608
42882
  var TapeStatusInputSchema = object({ deviceIds: array(number().int()).optional() });
42609
42883
  var TapeStatusOutputSchema = object({ cameras: array(TapeStatusRowSchema).readonly() });
42610
42884
  var TapeLightingInputSchema = _enum(["day", "ir"]);
42885
+ /**
42886
+ * Free text the settings store would hand back decoded is refused by name
42887
+ * (follow-up 617, review M2): whole JSON array/object text in a TEXT column
42888
+ * comes back as an array or object, and the index could not read the row.
42889
+ */
42890
+ var NOT_JSON_TEXT = "must not be JSON array/object text — the settings store would hand it back decoded";
42891
+ var notJsonText = (s) => !decodesAsJson(s);
42611
42892
  var PromoteInputSchema = object({
42612
42893
  deviceId: number().int().positive(),
42613
42894
  fromMs: number().int(),
42614
42895
  toMs: number().int(),
42615
- scenario: string(),
42616
- note: string().max(500).optional(),
42896
+ scenario: string().refine(notJsonText, { message: NOT_JSON_TEXT }),
42897
+ note: string().max(500).refine(notJsonText, { message: NOT_JSON_TEXT }).optional(),
42617
42898
  lighting: TapeLightingInputSchema.optional()
42618
42899
  });
42619
42900
  var PromoteOutputSchema = discriminatedUnion("ok", [object({
@@ -51074,6 +51355,17 @@ function pairRiders(detections) {
51074
51355
  * (D438) no frame did, and a newcomer on the stale prediction took the id.
51075
51356
  */
51076
51357
  var MAX_PREDICTION_MS = 1e3;
51358
+ /**
51359
+ * A frame more than `maxMissedMs` behind the latest is a CLOCK STEP only as
51360
+ * the K-th such frame in a row, each in order with the one before (follow-up
51361
+ * 617, fix round 3; D49: a read that would destroy work needs another to
51362
+ * agree). A step ends every track on the camera; one stray timestamp — a
51363
+ * backlog spike 4.5 s late — or two runner clocks delivering interleaved
51364
+ * (a camera migrating between runners) must not. Three is the least that a
51365
+ * single late frame plus an interleaved partner cannot reach by chance; the
51366
+ * cost of a real step is K−1 dropped frames (~260 ms at 7.7 fps).
51367
+ */
51368
+ var CLOCK_STEP_CONFIRM_FRAMES = 3;
51077
51369
  /** Confident boxes kept on a tracklet's path (the old tracker's bound). */
51078
51370
  var MAX_PATH_LENGTH = 300;
51079
51371
  /** Swap test: crossed pairs cheaper than this over the chosen pairs are doubt. */
@@ -51879,6 +52171,35 @@ function linkReach(from, gapMs) {
51879
52171
  return diag * Math.min(LINK_REACH_MAX_DIAGONALS, Math.max(LINK_REACH_MIN_DIAGONALS, LINK_REACH_DIAGONALS_PER_SEC * gapMs / 1e3));
51880
52172
  }
51881
52173
  var arcKey = (from, to) => `${from}\u0000${to}`;
52174
+ /**
52175
+ * The rejections that mean "geometry or appearance said no to a plausible
52176
+ * continuation" of a confirmed predecessor — a track the operator sees break
52177
+ * (follow-up 617, F5). Every one of them is past `hardRejection`, so the
52178
+ * predecessor is confirmed. NOT here (fix round 1): `unconfirmed`;
52179
+ * `has-successor` (continued elsewhere); `concurrent` and `identity-conflict`
52180
+ * (two subjects — the normal case, and the most frequent); `dropped` (an
52181
+ * operator delete or a refused chain).
52182
+ */
52183
+ var BREAK_REJECTIONS = new Set([
52184
+ "reach",
52185
+ "area",
52186
+ "appearance",
52187
+ "contested",
52188
+ "cost"
52189
+ ]);
52190
+ /** The confirmed predecessors whose link a decision rejected as a break (F5). */
52191
+ function confirmedPredecessorRejections(candidates) {
52192
+ const out = [];
52193
+ for (const c of candidates ?? []) {
52194
+ if (c.rejected === void 0 || !BREAK_REJECTIONS.has(c.rejected)) continue;
52195
+ out.push({
52196
+ from: c.from,
52197
+ rejected: c.rejected,
52198
+ gapMs: c.gapMs
52199
+ });
52200
+ }
52201
+ return out;
52202
+ }
51882
52203
  function arcFacts(a) {
51883
52204
  return {
51884
52205
  from: a.from.id,
@@ -51956,6 +52277,26 @@ var DeferredLinker = class {
51956
52277
  if (seen === void 0) this.names.set(p.id, new Set([p.identity]));
51957
52278
  else seen.add(p.identity);
51958
52279
  }
52280
+ /**
52281
+ * A clock step (follow-up 617, fix round 2): every piece still undecided is
52282
+ * decided `new`, so nothing that existed before the step can be absorbed
52283
+ * across it — whatever was ready was committed just before. The facade
52284
+ * rebases its clock past `LINK_MAX_GAP_MS`, so no later piece can continue
52285
+ * one of these either. An extra id, never a mix-up.
52286
+ */
52287
+ closeUndecided(now, pieces) {
52288
+ for (const p of pieces) this.accept(p, now);
52289
+ for (const p of this.pieces.values()) {
52290
+ if (this.outcomes.has(p.id)) continue;
52291
+ this.decide(p.id, "new");
52292
+ this.observer?.({
52293
+ t: now,
52294
+ kind: "new",
52295
+ to: p.id,
52296
+ reason: "clock-step"
52297
+ });
52298
+ }
52299
+ }
51959
52300
  isTailFinal(id, now, isLive) {
51960
52301
  const root = this.rootOf(id);
51961
52302
  if (this.pieces.has(root) && !this.outcomes.has(root)) return false;
@@ -52706,7 +53047,15 @@ var Tracklet = class {
52706
53047
  h: this.assocBox.h
52707
53048
  };
52708
53049
  }
53050
+ /**
53051
+ * A confident sighting. One OLDER than the last (`t < lastSeenAt` — ~1 % of
53052
+ * frames reach the tracker out of capture order, up to 1.2 s back) is
53053
+ * `stale`: it changes nothing. A rewound tracklet would be measured from a
53054
+ * box its subject left a second ago, and L2 refused a link by reach on
53055
+ * exactly that (follow-up 617, F1). The caller reports it.
53056
+ */
52709
53057
  observeConfident(det, t, faceBearing) {
53058
+ if (t < this.lastSeenAt) return "stale";
52710
53059
  const dt = t - this.lastSeenAt;
52711
53060
  if (dt > 0) {
52712
53061
  const from = centreOf(this.lastBox);
@@ -52718,6 +53067,7 @@ var Tracklet = class {
52718
53067
  this.emitIntervalMs = dt;
52719
53068
  }
52720
53069
  this.recordConfident(det, t, faceBearing);
53070
+ return "recorded";
52721
53071
  }
52722
53072
  observeLowScore(box, t) {
52723
53073
  this.assocBox = box;
@@ -52819,6 +53169,15 @@ var TrackletTracker = class {
52819
53169
  demoteSilent(t) {
52820
53170
  for (const tr of this.live.values()) if (tr.mode === "active" && t - tr.lastSeenAt > MAX_PREDICTION_MS) tr.mode = "coasting";
52821
53171
  }
53172
+ /** Ends every live tracklet at its own last sighting (a clock step); the ids it ended. */
53173
+ endAll(reason) {
53174
+ const ended = [];
53175
+ for (const tr of [...this.live.values()]) {
53176
+ ended.push(tr.id);
53177
+ this.finish(tr, tr.lastSeenAt, reason);
53178
+ }
53179
+ return ended;
53180
+ }
52822
53181
  /** Silence: end what the coast budget no longer covers. */
52823
53182
  expire(now) {
52824
53183
  for (const tr of [...this.live.values()]) if (now - tr.lastSeenAt > this.config.maxMissedMs) this.finish(tr, tr.lastSeenAt, "missed");
@@ -52909,12 +53268,12 @@ var TrackletTracker = class {
52909
53268
  const d = frame.detections[pair.detection];
52910
53269
  if (tr === void 0 || d === void 0) return;
52911
53270
  frame.used.add(pair.detection);
53271
+ if (tr.observeConfident(d, frame.t, frame.ctx.faceBearing?.has(d.bbox) === true) === "stale") return;
52912
53272
  tr.mode = "active";
52913
53273
  if (d.class !== tr.klass) this.refuse({
52914
53274
  reason: "cross-class-absorbed",
52915
53275
  against: tr
52916
53276
  }, d);
52917
- tr.observeConfident(d, frame.t, frame.ctx.faceBearing?.has(d.bbox) === true);
52918
53277
  if (pair.contested) {
52919
53278
  tr.contestedHits += 1;
52920
53279
  tr.contestedSince ??= frame.t;
@@ -53103,27 +53462,151 @@ var TrackerV2 = class {
53103
53462
  l1;
53104
53463
  l2;
53105
53464
  pending = [];
53465
+ /**
53466
+ * Internal time = the caller's frame time + `offset`. Zero until a clock
53467
+ * step; each step moves it so the internal clock never goes back and
53468
+ * resumes past `LINK_MAX_GAP_MS` of silence (fix round 2, C1). L1, L2, their
53469
+ * facts and prunes run on internal time; fusions and `frameClock()` are
53470
+ * handed back in the caller's clock.
53471
+ */
53472
+ offset = 0;
53106
53473
  lastT = 0;
53474
+ /** The latest FRAME time taken (internal) — never the sweep's extrapolated `now`. */
53475
+ lastFrameT = Number.NEGATIVE_INFINITY;
53476
+ observers;
53477
+ /** Caller times of the consecutive frames past the stale bound so far — the
53478
+ * run a clock step needs `CLOCK_STEP_CONFIRM_FRAMES` of (fix round 3). */
53479
+ stepRun = [];
53107
53480
  constructor(config, deps) {
53108
53481
  this.config = config;
53109
- this.l1 = new TrackletTracker(config, deps.mintTrackId, deps.observers ?? {});
53482
+ this.observers = deps.observers ?? {};
53483
+ const out = this.observers;
53484
+ const internal = {
53485
+ ...out.refusal !== void 0 ? { refusal: out.refusal } : {},
53486
+ ended: (f) => out.ended?.({
53487
+ ...f,
53488
+ at: f.at - this.offset
53489
+ }),
53490
+ split: (f) => out.split?.({
53491
+ ...f,
53492
+ t: f.t - this.offset
53493
+ })
53494
+ };
53495
+ this.l1 = new TrackletTracker(config, deps.mintTrackId, internal);
53110
53496
  this.l2 = new DeferredLinker({
53111
53497
  maxMissedMs: config.maxMissedMs,
53112
53498
  appearanceResolvesAmbiguity: config.appearanceResolvesAmbiguity
53113
- }, deps.observers?.link);
53499
+ }, (f) => out.link?.({
53500
+ ...f,
53501
+ t: f.t - this.offset
53502
+ }));
53114
53503
  }
53115
- update(detections, t, ctx) {
53504
+ update(detections, frameT, ctx) {
53505
+ const order = this.classifyFrame(frameT);
53506
+ if (order === "out-of-order") return this.drop(frameT, detections.length);
53507
+ let t = frameT + this.offset;
53508
+ if (order === "clock-step") t = this.clockStep(frameT, this.lastFrameT - t);
53509
+ this.stepRun = [];
53510
+ this.lastFrameT = t;
53116
53511
  this.lastT = Math.max(this.lastT, t);
53117
53512
  const step = this.l1.step(detections, t, ctx);
53118
53513
  this.link(this.lastT);
53119
53514
  return step.emissions.map((e) => this.emit(e, t));
53120
53515
  }
53121
53516
  /**
53517
+ * A frame stamped before the latest one already taken (follow-up 617, F1;
53518
+ * ~1 % of frames, up to 1.2 s back). It is decided ONCE, for the whole
53519
+ * frame (fix round 1, H1): matched against the tracklets' NEWEST boxes, a
53520
+ * stale box can land on another subject — a walker's old box on the walker
53521
+ * behind — and be emitted under that id. So nothing binds, spawns, splits or
53522
+ * is demoted on it; the confirmed tracklets are emitted as on a frame that
53523
+ * matched nothing (no sighting), and one fact says the frame was dropped.
53524
+ */
53525
+ /**
53526
+ * How `update` will take a frame at `frameT` — without taking it. The
53527
+ * processor asks first (fix round 3, MEDIUM-2), so a dropped frame never
53528
+ * reaches the stationary gate, the zones or the event path.
53529
+ * - `in-order`: not behind the latest frame taken;
53530
+ * - `out-of-order`: behind it — within `maxMissedMs` (disorder, ≤ 1.2 s
53531
+ * measured), or further back but not yet the `CLOCK_STEP_CONFIRM_FRAMES`-th
53532
+ * such frame in a row;
53533
+ * - `clock-step`: the K-th consecutive frame past the bound, each in order
53534
+ * with the one before (within `maxMissedMs`).
53535
+ */
53536
+ classifyFrame(frameT) {
53537
+ const behindMs = this.lastFrameT - (frameT + this.offset);
53538
+ if (!(behindMs > 0)) return "in-order";
53539
+ if (behindMs <= this.config.maxMissedMs) return "out-of-order";
53540
+ return this.runLength(frameT) >= CLOCK_STEP_CONFIRM_FRAMES ? "clock-step" : "out-of-order";
53541
+ }
53542
+ /** The past-bound run this frame would make, itself included. */
53543
+ runLength(frameT) {
53544
+ const prev = this.stepRun.at(-1);
53545
+ return (prev !== void 0 && frameT >= prev && frameT - prev <= this.config.maxMissedMs ? this.stepRun.length : 0) + 1;
53546
+ }
53547
+ /**
53548
+ * Drops a frame `classifyFrame` called `out-of-order`. A frame past the
53549
+ * bound extends (or restarts) the run a clock step needs; any other breaks
53550
+ * it. One fact per frame.
53551
+ */
53552
+ drop(frameT, detections) {
53553
+ const behindMs = this.lastFrameT - (frameT + this.offset);
53554
+ let pendingClockStep = 0;
53555
+ if (behindMs > this.config.maxMissedMs) {
53556
+ pendingClockStep = this.runLength(frameT);
53557
+ this.stepRun = pendingClockStep === 1 ? [frameT] : [...this.stepRun, frameT];
53558
+ } else this.stepRun = [];
53559
+ this.observers.outOfOrder?.({
53560
+ t: frameT,
53561
+ lastFrameT: this.frameClock(),
53562
+ behindMs,
53563
+ detections,
53564
+ pendingClockStep
53565
+ });
53566
+ return this.l1.liveTracklets().filter((tr) => tr.confirmed).map((tracklet) => this.emit({
53567
+ tracklet,
53568
+ detection: void 0,
53569
+ matched: false
53570
+ }, this.lastFrameT));
53571
+ }
53572
+ /**
53573
+ * The frame clock stepped back further than `maxMissedMs` (fix round 2, C1):
53574
+ * not disorder — an NTP correction, or a camera moved to a runner whose clock
53575
+ * is behind. An unbounded gate dropped every frame until the new clock passed
53576
+ * the old high mark (60 s back: 461 frames, a new subject never tracked, the
53577
+ * departed one coasting on). Instead: commit what L2 has decided, end every
53578
+ * live tracklet at its own last sighting, decide every piece still undecided
53579
+ * `new` (no fusion crosses the step), and resume the internal clock past
53580
+ * `LINK_MAX_GAP_MS` of silence, so nothing later can continue anything
53581
+ * earlier. Returns the frame's internal time.
53582
+ */
53583
+ clockStep(frameT, behindMs) {
53584
+ const lastFrameT = this.frameClock();
53585
+ this.link(this.lastT);
53586
+ const ended = this.l1.endAll("clock-step");
53587
+ this.l2.closeUndecided(this.lastT, this.pieces());
53588
+ const resumeAt = Math.max(this.lastT, this.lastFrameT) + LINK_MAX_GAP_MS + 1;
53589
+ this.offset = resumeAt - frameT;
53590
+ this.lastT = resumeAt;
53591
+ this.observers.clockStep?.({
53592
+ t: frameT,
53593
+ lastFrameT,
53594
+ behindMs,
53595
+ ended
53596
+ });
53597
+ return resumeAt;
53598
+ }
53599
+ /** The latest frame time taken, in the caller's clock (rebased by a clock step). */
53600
+ frameClock() {
53601
+ return this.lastFrameT - this.offset;
53602
+ }
53603
+ /**
53122
53604
  * The close sweep's entry on a camera whose frames stopped (D438): end the
53123
53605
  * tracklets the coast budget no longer covers, THEN run L2 (review C2).
53124
53606
  * `now` is FRAME time (the processor converts wall clock, Task 11).
53125
53607
  */
53126
- advance(now) {
53608
+ advance(frameNow) {
53609
+ const now = frameNow + this.offset;
53127
53610
  this.lastT = Math.max(this.lastT, now);
53128
53611
  this.l1.expire(now);
53129
53612
  this.link(now);
@@ -53139,6 +53622,7 @@ var TrackerV2 = class {
53139
53622
  markSuppressed(id) {
53140
53623
  this.l1.markSuppressed(id);
53141
53624
  }
53625
+ /** In the caller's clock (translated when each was committed, LOW-5). */
53142
53626
  drainFusions() {
53143
53627
  const out = this.pending;
53144
53628
  this.pending = [];
@@ -53151,8 +53635,8 @@ var TrackerV2 = class {
53151
53635
  isLive(id) {
53152
53636
  return this.l2.membersOf(this.l2.rootOf(id)).some((m) => this.isLiveTracklet(m));
53153
53637
  }
53154
- isTailFinal(id, now) {
53155
- return this.l2.isTailFinal(id, now, (m) => this.isLiveTracklet(m));
53638
+ isTailFinal(id, frameNow) {
53639
+ return this.l2.isTailFinal(id, frameNow + this.offset, (m) => this.isLiveTracklet(m));
53156
53640
  }
53157
53641
  /**
53158
53642
  * Ends every live member of the chain. `stationary` (round 4): the
@@ -53168,9 +53652,15 @@ var TrackerV2 = class {
53168
53652
  const tr = this.l1.get(id);
53169
53653
  return tr !== void 0 && tr.endedAt === null;
53170
53654
  }
53655
+ pieces() {
53656
+ return [...this.l1.liveTracklets(), ...this.l1.endedTracklets()].map(toPiece);
53657
+ }
53171
53658
  link(now) {
53172
- const pieces = [...this.l1.liveTracklets(), ...this.l1.endedTracklets()].map(toPiece);
53173
- this.pending.push(...this.l2.step(now, pieces));
53659
+ const offset = this.offset;
53660
+ for (const f of this.l2.step(now, this.pieces())) this.pending.push({
53661
+ ...f,
53662
+ t: f.t - offset
53663
+ });
53174
53664
  this.l1.prune(now);
53175
53665
  this.l2.prune(now);
53176
53666
  }
@@ -53233,7 +53723,8 @@ var NO_ENDS = {
53233
53723
  missed: 0,
53234
53724
  ambiguous: 0,
53235
53725
  dropped: 0,
53236
- stationary: 0
53726
+ stationary: 0,
53727
+ "clock-step": 0
53237
53728
  };
53238
53729
  function emptyStats() {
53239
53730
  return {
@@ -53246,16 +53737,28 @@ function emptyStats() {
53246
53737
  linksRefused: 0,
53247
53738
  linksNew: 0,
53248
53739
  linksDisagreed: 0,
53249
- lowScoreHolds: 0
53740
+ lowScoreHolds: 0,
53741
+ outOfOrderFrames: 0,
53742
+ clockSteps: 0
53250
53743
  };
53251
53744
  }
53252
53745
  /** Nothing happened in the window: no tracklet ended, no split, no L2
53253
53746
  * decision, no low-score hold (review LOW-N1 — what the sweep stays quiet about). */
53254
53747
  function isIdleTrackerStats(stats) {
53255
- return [stats.tracklets.confirmed, stats.tracklets.unconfirmed].flatMap((c) => Object.values(c)).every((n) => n === 0) && stats.splits === 0 && stats.linksCommitted === 0 && stats.linksRefused === 0 && stats.linksNew === 0 && stats.linksDisagreed === 0 && stats.lowScoreHolds === 0;
53748
+ return [stats.tracklets.confirmed, stats.tracklets.unconfirmed].flatMap((c) => Object.values(c)).every((n) => n === 0) && stats.splits === 0 && stats.linksCommitted === 0 && stats.linksRefused === 0 && stats.linksNew === 0 && stats.linksDisagreed === 0 && stats.lowScoreHolds === 0 && stats.outOfOrderFrames === 0 && stats.clockSteps === 0;
53256
53749
  }
53257
53750
  /** Shared empty — a fold is rare and every other frame would allocate. */
53258
53751
  var EMPTY_RIDER_FOLDS = [];
53752
+ /** The tape observation of a dropped frame: no gate verdict, nothing bound. */
53753
+ var DROPPED_FRAME_OBSERVATION = {
53754
+ gate: {
53755
+ strong: [],
53756
+ weak: [],
53757
+ confirmed: [],
53758
+ woken: []
53759
+ },
53760
+ live: []
53761
+ };
53259
53762
  /** Mapping from StateAnalyzer's `ObjectState.state` values to the
53260
53763
  * canonical TrackState enum used on tracks + events. */
53261
53764
  function mapObjectStateToTrackState(s) {
@@ -53434,6 +53937,14 @@ var FrameProcessor = class {
53434
53937
  else if (f.kind === "new") this.stats.linksNew += 1;
53435
53938
  else if (f.kind === "disagreed") this.stats.linksDisagreed += 1;
53436
53939
  this.trackerObservers.link?.(f);
53940
+ },
53941
+ outOfOrder: (f) => {
53942
+ this.stats.outOfOrderFrames += 1;
53943
+ this.trackerObservers.outOfOrder?.(f);
53944
+ },
53945
+ clockStep: (f) => {
53946
+ this.stats.clockSteps += 1;
53947
+ this.trackerObservers.clockStep?.(f);
53437
53948
  }
53438
53949
  }
53439
53950
  });
@@ -53717,6 +54228,37 @@ var FrameProcessor = class {
53717
54228
  * Zero is the expected reading; a rising one means two people stand close
53718
54229
  * together on this camera, which is worth knowing per-camera rather than
53719
54230
  * per-fleet. */
54231
+ /**
54232
+ * A frame `classifyFrame` called out of order: the tracker records the drop
54233
+ * (and any clock-step run it extends) and nothing else reads the frame. The
54234
+ * per-frame side tables a reader asks for afterwards are emptied, so it never
54235
+ * re-reads the previous frame's.
54236
+ */
54237
+ droppedFrame(input) {
54238
+ this.tracker.drop(input.timestamp, input.frame.detections.length);
54239
+ this.lastZoneOverlaps = /* @__PURE__ */ new Map();
54240
+ this.lastZoneRejections = /* @__PURE__ */ new Map();
54241
+ this.lastLabelWriteRefusals = [];
54242
+ const fusions = this.tracker.drainFusions();
54243
+ this.applyFusions(fusions);
54244
+ return {
54245
+ deviceId: this.deviceId,
54246
+ timestamp: input.timestamp,
54247
+ frameWidth: input.frame.width,
54248
+ frameHeight: input.frame.height,
54249
+ tracked: [],
54250
+ objectEvents: [],
54251
+ appearanceEvents: [],
54252
+ rawTrackedDetections: [],
54253
+ stationaryConfirmed: [],
54254
+ stationaryWoken: [],
54255
+ stationaryContinued: 0,
54256
+ riderFolds: [],
54257
+ ...input.observe === true ? { observation: DROPPED_FRAME_OBSERVATION } : {},
54258
+ fusions,
54259
+ droppedOutOfOrder: true
54260
+ };
54261
+ }
53720
54262
  facesRefusedOutsideParent() {
53721
54263
  return this.faceOutsideParentCount;
53722
54264
  }
@@ -53739,6 +54281,7 @@ var FrameProcessor = class {
53739
54281
  }
53740
54282
  process(input) {
53741
54283
  const { timestamp, frame } = input;
54284
+ if (this.tracker.classifyFrame(timestamp) === "out-of-order") return this.droppedFrame(input);
53742
54285
  this.lastFrameT = timestamp;
53743
54286
  this.lastFrameWallAt = this.wallClock();
53744
54287
  const frameWidth = frame.width;
@@ -56490,239 +57033,6 @@ async function promoteTapeWindow(deps, input, signal) {
56490
57033
  };
56491
57034
  }
56492
57035
  //#endregion
56493
- //#region src/pipeline-analytics/store/tracking-tape-index-store.ts
56494
- /**
56495
- * @durable class=ledger owner=pipeline-analytics
56496
- * write="one row per PROMOTED tracking-tape window (`debug.tape.promote`);
56497
- * updated when the operator marks it reviewed or releases its pins;
56498
- * deleted together with its file by `debug.tape.delete`."
56499
- * retention="NOT an age clock and NOT track retention — a promoted window is
56500
- * history being labelled (D681). Bounded by the promotion budget
56501
- * (MAX_PROMOTED_TAPES = 60, 1 GiB): a promotion beyond it is REFUSED,
56502
- * never satisfied by evicting another row."
56503
- */
56504
- var TRACKING_TAPES_COLLECTION = "pipeline-analytics:tracking-tapes";
56505
- var TRACKING_TAPES_COLUMNS = [
56506
- {
56507
- name: "id",
56508
- type: "TEXT",
56509
- primaryKey: true,
56510
- notNull: true
56511
- },
56512
- {
56513
- name: "deviceId",
56514
- type: "INTEGER",
56515
- notNull: true
56516
- },
56517
- {
56518
- name: "scenario",
56519
- type: "TEXT",
56520
- notNull: true
56521
- },
56522
- {
56523
- name: "note",
56524
- type: "TEXT"
56525
- },
56526
- {
56527
- name: "lighting",
56528
- type: "TEXT",
56529
- notNull: true
56530
- },
56531
- {
56532
- name: "fromMs",
56533
- type: "INTEGER",
56534
- notNull: true
56535
- },
56536
- {
56537
- name: "toMs",
56538
- type: "INTEGER",
56539
- notNull: true
56540
- },
56541
- {
56542
- name: "state",
56543
- type: "TEXT",
56544
- notNull: true
56545
- },
56546
- {
56547
- name: "frames",
56548
- type: "INTEGER",
56549
- notNull: true
56550
- },
56551
- {
56552
- name: "skips",
56553
- type: "INTEGER",
56554
- notNull: true
56555
- },
56556
- {
56557
- name: "gaps",
56558
- type: "INTEGER",
56559
- notNull: true
56560
- },
56561
- {
56562
- name: "bytes",
56563
- type: "INTEGER",
56564
- notNull: true
56565
- },
56566
- {
56567
- name: "reviewed",
56568
- type: "BOOLEAN",
56569
- notNull: true
56570
- },
56571
- {
56572
- name: "trackIds",
56573
- type: "TEXT",
56574
- notNull: true
56575
- },
56576
- {
56577
- name: "createdAt",
56578
- type: "INTEGER",
56579
- notNull: true
56580
- },
56581
- {
56582
- name: "releasedAt",
56583
- type: "INTEGER"
56584
- },
56585
- {
56586
- name: "pinsSettled",
56587
- type: "BOOLEAN",
56588
- notNull: true
56589
- }
56590
- ];
56591
- var TrackingTapeRowSchema = object({
56592
- id: string(),
56593
- deviceId: number().int(),
56594
- scenario: string(),
56595
- note: string().nullable(),
56596
- lighting: TapeLightingSchema,
56597
- fromMs: number().int(),
56598
- toMs: number().int(),
56599
- state: _enum(["complete", "incomplete"]),
56600
- frames: number().int(),
56601
- skips: number().int(),
56602
- gaps: number().int(),
56603
- bytes: number().int(),
56604
- reviewed: boolean(),
56605
- trackIds: array(string()),
56606
- createdAt: number().int(),
56607
- releasedAt: number().int().nullable(),
56608
- pinsSettled: boolean()
56609
- });
56610
- var TrackIdsSchema = array(string());
56611
- var TrackingTapeIndexStore = class {
56612
- store;
56613
- logger;
56614
- constructor(deps) {
56615
- this.store = deps.store;
56616
- this.logger = deps.logger;
56617
- }
56618
- static async declare(store) {
56619
- await store.declareCollection.mutate({
56620
- collection: TRACKING_TAPES_COLLECTION,
56621
- columns: [...TRACKING_TAPES_COLUMNS],
56622
- indexes: [{
56623
- name: "idx_trackingtapes_device_created",
56624
- columns: ["deviceId", "createdAt"]
56625
- }]
56626
- });
56627
- }
56628
- async insert(row) {
56629
- const { id, trackIds, ...rest } = TrackingTapeRowSchema.parse(row);
56630
- await this.store.insert.mutate({
56631
- collection: TRACKING_TAPES_COLLECTION,
56632
- record: {
56633
- id,
56634
- data: {
56635
- ...rest,
56636
- trackIds: JSON.stringify(trackIds)
56637
- }
56638
- }
56639
- });
56640
- }
56641
- async get(id) {
56642
- const first = (await this.store.query.query({
56643
- collection: TRACKING_TAPES_COLLECTION,
56644
- filter: {
56645
- where: { id },
56646
- limit: 1
56647
- }
56648
- }))[0];
56649
- return first === void 0 ? null : this.parse(first.id, first.data);
56650
- }
56651
- async list(deviceId) {
56652
- const rows = await this.store.query.query({
56653
- collection: TRACKING_TAPES_COLLECTION,
56654
- filter: {
56655
- ...deviceId !== void 0 ? { where: { deviceId } } : {},
56656
- orderBy: {
56657
- field: "createdAt",
56658
- direction: "desc"
56659
- },
56660
- limit: 1e3
56661
- }
56662
- });
56663
- const out = [];
56664
- for (const r of rows) {
56665
- const parsed = this.parse(r.id, r.data);
56666
- if (parsed !== null) out.push(parsed);
56667
- }
56668
- return out;
56669
- }
56670
- async setReviewed(id, reviewed) {
56671
- await this.store.update.mutate({
56672
- collection: TRACKING_TAPES_COLLECTION,
56673
- id,
56674
- data: { reviewed }
56675
- });
56676
- }
56677
- /** Release AND unsettle in one row write: the unpins the release implies
56678
- * are owed from this moment, whether or not the caller lives to make them. */
56679
- async setReleased(id, atMs) {
56680
- await this.store.update.mutate({
56681
- collection: TRACKING_TAPES_COLLECTION,
56682
- id,
56683
- data: {
56684
- releasedAt: atMs,
56685
- pinsSettled: false
56686
- }
56687
- });
56688
- }
56689
- async setPinsSettled(id, settled) {
56690
- await this.store.update.mutate({
56691
- collection: TRACKING_TAPES_COLLECTION,
56692
- id,
56693
- data: { pinsSettled: settled }
56694
- });
56695
- }
56696
- async remove(id) {
56697
- await this.store.delete.mutate({
56698
- collection: TRACKING_TAPES_COLLECTION,
56699
- key: id
56700
- });
56701
- }
56702
- parse(id, data) {
56703
- let trackIds = [];
56704
- try {
56705
- trackIds = JSON.parse(String(data["trackIds"] ?? "[]"));
56706
- } catch {
56707
- trackIds = null;
56708
- }
56709
- const ids = TrackIdsSchema.safeParse(trackIds);
56710
- const parsed = TrackingTapeRowSchema.safeParse({
56711
- id,
56712
- ...data,
56713
- note: data["note"] ?? null,
56714
- releasedAt: data["releasedAt"] ?? null,
56715
- trackIds: ids.success ? ids.data : null
56716
- });
56717
- if (parsed.success) return parsed.data;
56718
- this.logger.warn("tracking tape index: skipped a malformed row", {
56719
- meta: { id },
56720
- ...typeof data["deviceId"] === "number" ? { tags: { deviceId: data["deviceId"] } } : {}
56721
- });
56722
- return null;
56723
- }
56724
- };
56725
- //#endregion
56726
57036
  //#region src/pipeline-analytics/tracking-bench/recorder/tape-status.ts
56727
57037
  /** The one node that may hold tapes: sqlite and bulk state are hub-only, and
56728
57038
  * an agent persists nothing (spec §3.1). */
@@ -97602,6 +97912,12 @@ var TRACKER_FACT_LOG_SAMPLE_EVERY = 10;
97602
97912
  * positive spawns (review M1: ~1 750 `new` decisions per camera per hour) —
97603
97913
  * are sampled harder; the per-minute summary carries their exact counts. */
97604
97914
  var TRACKER_PIECE_LOG_SAMPLE_EVERY = 50;
97915
+ /** Out-of-order frames (follow-up 617, F1: ~1 % of frames) log the first N,
97916
+ * then one in this many — every line carries the count; the per-minute
97917
+ * summary carries the exact `outOfOrderFrames`. */
97918
+ var TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY = 50;
97919
+ /** Clock-step WARNs past the first ten (fix round 3, MEDIUM-1). */
97920
+ var TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY = 50;
97605
97921
  /** First N, then one in `every` — the refusal stream's cadence (D391). */
97606
97922
  function sampledTrackerFact(count, every = TRACKER_FACT_LOG_SAMPLE_EVERY) {
97607
97923
  return count <= TRACKER_FACT_LOG_ALWAYS_FIRST || count % every === 0;
@@ -102249,9 +102565,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102249
102565
  let splitCount = 0;
102250
102566
  let ignoredCount = 0;
102251
102567
  let newCount = 0;
102568
+ let outOfOrderCount = 0;
102569
+ let clockStepCount = 0;
102252
102570
  const endedCounts = /* @__PURE__ */ new Map();
102253
102571
  const logLink = (f) => {
102254
- const meta = {
102572
+ const decision = {
102255
102573
  source,
102256
102574
  t: f.t,
102257
102575
  kind: f.kind,
@@ -102260,7 +102578,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102260
102578
  cost: f.cost,
102261
102579
  gapMs: f.gapMs,
102262
102580
  reason: f.reason,
102263
- recognition: f.recognition,
102581
+ recognition: f.recognition
102582
+ };
102583
+ const meta = {
102584
+ ...decision,
102264
102585
  candidates: f.candidates
102265
102586
  };
102266
102587
  switch (f.kind) {
@@ -102298,7 +102619,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102298
102619
  meta
102299
102620
  });
102300
102621
  return;
102301
- case "new":
102622
+ case "new": {
102623
+ if (f.reason === "clock-step") {
102624
+ trackerLog.debug("tracker: piece decided new — closed by a frame clock step", {
102625
+ tags: { deviceId },
102626
+ meta
102627
+ });
102628
+ return;
102629
+ }
102302
102630
  if (f.reason === "unconfirmed") {
102303
102631
  trackerLog.debug("tracker: piece decided new — never confirmed", {
102304
102632
  tags: { deviceId },
@@ -102306,6 +102634,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102306
102634
  });
102307
102635
  return;
102308
102636
  }
102637
+ const rejectedPredecessors = confirmedPredecessorRejections(f.candidates);
102638
+ if (rejectedPredecessors.length > 0) {
102639
+ trackerLog.info("tracker: piece decided new — a confirmed predecessor was rejected", {
102640
+ tags: { deviceId },
102641
+ meta: {
102642
+ ...decision,
102643
+ rejectedPredecessors
102644
+ }
102645
+ });
102646
+ return;
102647
+ }
102309
102648
  newCount += 1;
102310
102649
  if (!sampledTrackerFact(newCount, TRACKER_PIECE_LOG_SAMPLE_EVERY)) return;
102311
102650
  trackerLog.info("tracker: piece decided new — no predecessor", {
@@ -102316,6 +102655,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102316
102655
  }
102317
102656
  });
102318
102657
  return;
102658
+ }
102319
102659
  }
102320
102660
  };
102321
102661
  p.setTrackerObservers({
@@ -102336,6 +102676,39 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102336
102676
  });
102337
102677
  },
102338
102678
  link: logLink,
102679
+ clockStep: (f) => {
102680
+ clockStepCount += 1;
102681
+ if (!sampledTrackerFact(clockStepCount, TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY)) return;
102682
+ trackerLog.warn("tracker: frame clock stepped back — tracklets ended, clock rebased", {
102683
+ tags: { deviceId },
102684
+ meta: {
102685
+ source,
102686
+ t: f.t,
102687
+ lastFrameT: f.lastFrameT,
102688
+ behindMs: f.behindMs,
102689
+ ended: f.ended,
102690
+ clockStepCount,
102691
+ sampledEvery: TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY
102692
+ }
102693
+ });
102694
+ },
102695
+ outOfOrder: (f) => {
102696
+ outOfOrderCount += 1;
102697
+ if (!sampledTrackerFact(outOfOrderCount, TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY)) return;
102698
+ trackerLog.info("tracker: out-of-order frame dropped — older than the latest frame", {
102699
+ tags: { deviceId },
102700
+ meta: {
102701
+ source,
102702
+ t: f.t,
102703
+ lastFrameT: f.lastFrameT,
102704
+ behindMs: f.behindMs,
102705
+ detections: f.detections,
102706
+ pendingClockStep: f.pendingClockStep,
102707
+ outOfOrderCount,
102708
+ sampledEvery: TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY
102709
+ }
102710
+ });
102711
+ },
102339
102712
  ended: (f) => {
102340
102713
  const k = `${f.reason}:${String(f.confirmed)}`;
102341
102714
  const n = (endedCounts.get(k) ?? 0) + 1;
@@ -102477,6 +102850,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
102477
102850
  });
102478
102851
  });
102479
102852
  this.reportTrackerSummary(deviceId, key, processor, "frame");
102853
+ if (result.droppedOutOfOrder === true) return;
102480
102854
  for (const r of processor.getLastLabelWriteRefusals()) {
102481
102855
  const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
102482
102856
  this.ctx.logger.info(message, {