@camstack/types 1.2.279 → 1.2.281

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.
@@ -201,6 +201,59 @@ export interface NcRuleSectionSubject {
201
201
  readonly delivery: string;
202
202
  readonly conditions: NcRuleConditionsSubject;
203
203
  }
204
+ /**
205
+ * Which capability a device must carry to be OFFERED on a rule of this kind's
206
+ * device-scope picker (operator ruling, 2026-10-01). `null` means "no
207
+ * capability filter for this kind" — either every device applies (the system
208
+ * families, each already a separate concern; see
209
+ * `docs/architecture/notifications.md`'s D637 `deviceIds` table) or `mixed`,
210
+ * which authors nothing.
211
+ *
212
+ * DERIVED, never a hand-picked list:
213
+ * - `sensor` reads `EVENTFUL_CAP_NAMES` — the SAME cap table the engine
214
+ * itself turns a linked device's bound caps into sensor event kinds with
215
+ * (`EVENT_KIND_BY_CAP`). A device this picker offers for a sensor rule is
216
+ * exactly a device the rule's `sensorKinds` condition could ever name.
217
+ * - `detection` (which a package-event rule is — there is no separate
218
+ * `package` kind, D637) / `occupancy` read the one pipeline cap a camera
219
+ * needs to produce the detections / counts / parcels those kinds match on.
220
+ * - `sound` reads the one cap a device needs to produce the audio metrics a
221
+ * sound rule's sampling window is built from.
222
+ * - `device-events` / `system` / `alarm` / `mixed` are `null`: a device
223
+ * event rule's `deviceIds` legitimately spans every device type (liveness,
224
+ * battery, consumable, `detection-blind`), a system rule has no device
225
+ * scope at all (dropped from the editor's tabs), and `alarm`'s `deviceIds`
226
+ * is a reference to whichever camera's rule set it off — not a scope this
227
+ * picker authors.
228
+ *
229
+ * Callers resolve the ids via `deviceManager.listDeviceIdsByCaps` (max 16
230
+ * names — `EVENTFUL_CAP_NAMES` is exactly 16) and must treat an unanswered
231
+ * read as "unknown", never as "no devices" (D315): this function only names
232
+ * WHICH caps to ask for, not what to show while the answer is pending.
233
+ */
234
+ export declare function ruleDeviceCompatCapNames(kind: NcRuleKind): readonly string[] | null;
235
+ /**
236
+ * The capability every CAMERA carries: `camera-streams` is a `defaultActive`
237
+ * wrapper bound to every `Camera`-type device (`camera-streams.cap.ts`).
238
+ * Named once, because a sensor rule's compatible set needs it (below).
239
+ */
240
+ export declare const NC_CAMERA_SCOPE_CAP_NAME = "camera-streams";
241
+ /**
242
+ * Every capability QUERY whose union is the set of devices a rule of this kind
243
+ * may be SCOPED to (`conditions.devices`) — what the save-time guard and the
244
+ * admin's Devices tab ask `deviceManager.listDeviceIdsByCaps`, one call per
245
+ * entry. `null` exactly where {@link ruleDeviceCompatCapNames} is `null`.
246
+ *
247
+ * Several queries rather than one list because `listDeviceIdsByCaps` takes at
248
+ * most 16 names and `EVENTFUL_CAP_NAMES` is already exactly 16.
249
+ *
250
+ * A `sensor` rule's set is the eventful devices PLUS every camera (fix round
251
+ * 1, 2026-10-01): the engine matches a sensor rule scoped to a CAMERA — a
252
+ * per-camera row carries the camera's id, and a linked set matches on
253
+ * `rowCameraIds` (D630 §6) — so a camera-scoped sensor rule is legitimate and
254
+ * a guard that refused it would make every such stored rule un-editable.
255
+ */
256
+ export declare function ruleDeviceCompatCapQueries(kind: NcRuleKind): readonly (readonly string[])[] | null;
204
257
  /**
205
258
  * Which kind of rule is this?
206
259
  *
@@ -279,7 +332,7 @@ export declare function droppedConditionsForKind(conditions: NcAuthoredCondition
279
332
  * the stored system rules that span families. `mixed` is a real, owning section
280
333
  * (a rule must be listed somewhere) that nothing can CREATE into.
281
334
  */
282
- export type NcRuleSectionKey = 'all' | 'detection' | 'audio' | 'occupancy' | 'device-events' | 'system' | 'alarm' | 'mixed';
335
+ export type NcRuleSectionKey = 'all' | 'detection' | 'sensor' | 'audio' | 'occupancy' | 'device-events' | 'system' | 'alarm' | 'mixed';
283
336
  export interface NcRuleSection {
284
337
  readonly key: NcRuleSectionKey;
285
338
  /** English fallback; the viewer maps `key` onto an i18n key. */
@@ -308,15 +361,20 @@ export declare function isOccupancyRule(subject: NcRuleSectionSubject): boolean;
308
361
  * `all` always matches — a section that hides a rule is how an operator
309
362
  * concludes it was deleted. The others PARTITION the list: sound and
310
363
  * occupancy are claimed by their condition, a system-event rule by its trigger
311
- * and then by the family of its event kinds (or `mixed`, D637), and
312
- * everything left over — including a trigger this build has never heard of —
313
- * counts as a detection rule so it stays visible somewhere.
314
- *
315
- * Keyed on the CONDITION for two of them, because the trigger alone cannot
316
- * answer: a sound rule is `immediate` and an occupancy rule is `device-event`,
317
- * and in each case the condition is the discriminator the engine itself uses.
318
- * A section keyed on delivery would put both back in the pile they are
319
- * invisible in.
364
+ * and then by the family of its event kinds (or `mixed`, D637), `sensor`
365
+ * claims the `device-event` trigger that occupancy did not (operator ruling,
366
+ * 2026-10-01 — a doorbell press used to fall into `detection` for want of its
367
+ * own section, which is the "everything mixed together" the viewer's
368
+ * `ruleKindOf`-based list had already fixed and the admin's section strip had
369
+ * not), and everything left over — including a trigger this build has never
370
+ * heard of — counts as a detection rule so it stays visible somewhere.
371
+ *
372
+ * Keyed on the CONDITION for sound and occupancy, because the trigger alone
373
+ * cannot answer: a sound rule is `immediate` and an occupancy rule is
374
+ * `device-event`, and in each case the condition is the discriminator the
375
+ * engine itself uses. `sensor` IS keyed on the trigger, same as `ruleKindOf`'s
376
+ * `'sensor'` kind — a device-event subject with no occupancy condition is a
377
+ * sensor rule, full stop.
320
378
  */
321
379
  export declare function ruleMatchesSection(subject: NcRuleSectionSubject, section: NcRuleSectionKey): boolean;
322
380
  /** The ONE real section a rule lives in — `all` holds everything and is not it. */
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A model-free appearance descriptor of one detection box (D683 §8).
3
+ *
4
+ * Computed on the runner from the analysis raster the frame already leased
5
+ * (`pipeline-runner/appearance-descriptor.ts`), carried on `ObjectDetection`
6
+ * and mirrored onto the tracker's `SpatialDetection`. A vector about a box,
7
+ * never pixels (D9/D18). No learned weights, so no licence question (O6).
8
+ *
9
+ * Layout: the box shrunk 10 % per side, split into an upper and a lower half;
10
+ * per half a hue–saturation histogram (8 hue × 4 saturation), a value histogram
11
+ * (8) and a magnitude-weighted gradient-orientation histogram (8). Each block
12
+ * is L1-normalised and quantised to integers 0–255. About 300 bytes of JSON.
13
+ */
14
+ export declare const APPEARANCE_DESCRIPTOR_VERSION = 1;
15
+ export declare const APPEARANCE_BIN_COUNT = 96;
16
+ export declare const APPEARANCE_BLOCKS: {
17
+ readonly upperHs: readonly [0, 32];
18
+ readonly upperV: readonly [32, 40];
19
+ readonly upperGrad: readonly [40, 48];
20
+ readonly lowerHs: readonly [48, 80];
21
+ readonly lowerV: readonly [80, 88];
22
+ readonly lowerGrad: readonly [88, 96];
23
+ };
24
+ export type AppearanceBlockName = keyof typeof APPEARANCE_BLOCKS;
25
+ export interface AppearanceDescriptor {
26
+ readonly version: 1;
27
+ /** {@link APPEARANCE_BIN_COUNT} integers, laid out by {@link APPEARANCE_BLOCKS}. */
28
+ readonly bins: readonly number[];
29
+ /** The view carries no colour (IR / greyscale / near-black): hue–saturation says nothing. */
30
+ readonly gray: boolean;
31
+ }
32
+ /**
33
+ * Whether appearance may influence anything at all (D683 §3.2, fix round 1
34
+ * ruling 2026-09-30 on the Task 10 cost-bench STOP): OFF until the
35
+ * descriptor is calibrated on tapes that carry it (Task 23, review M9).
36
+ *
37
+ * This is the ONE authority both addons read — never two settings that
38
+ * could disagree (CLAUDE.md D62). The tracker's `TrackerV2Config.appearanceResolvesAmbiguity`
39
+ * default reads it (`addon-post-analysis/pipeline/tracker-v2/types.ts`); the
40
+ * runner's cost gate reads it too (`addon-pipeline/pipeline-runner/appearance-attach.ts`),
41
+ * short-circuiting BEFORE any crop, resample or descriptor is computed, so a
42
+ * disabled camera costs nothing extra per frame.
43
+ */
44
+ export declare const APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT = false;
@@ -1,6 +1,7 @@
1
1
  import type { FirstLevelMacroClass } from './labels.js';
2
2
  import type { AudioClassifyOutcome } from '../capabilities/schemas/detection-shared.js';
3
3
  import type { TrackState } from '../capabilities/pipeline-analytics.cap.js';
4
+ import type { AppearanceDescriptor } from './appearance.js';
4
5
  import type { BoundingBox as EngineBoundingBox, FrameInput, Landmark } from './io.js';
5
6
  export interface SpatialDetection {
6
7
  readonly class: string;
@@ -24,6 +25,12 @@ export interface SpatialDetection {
24
25
  * produced it all the way to the confirmation gate (D379).
25
26
  */
26
27
  readonly birthEvidence?: BirthEvidence;
28
+ /**
29
+ * Mirrored from {@link ObjectDetection.appearance} when this detection enters
30
+ * the tracker (D683). Absent = no descriptor was computed (too small, no
31
+ * raster, a runner that predates it): never read as "different".
32
+ */
33
+ readonly appearance?: AppearanceDescriptor;
27
34
  }
28
35
  export interface CropInput {
29
36
  readonly frame: FrameInput;
@@ -422,6 +429,11 @@ export interface ObjectDetection extends DetectionBase {
422
429
  readonly x: number;
423
430
  readonly y: number;
424
431
  }[];
432
+ /**
433
+ * The box's model-free appearance descriptor, computed by the runner on the
434
+ * frame this detection was measured on (D683). First-level boxes only.
435
+ */
436
+ readonly appearance?: AppearanceDescriptor;
425
437
  }
426
438
  /** Audio detection — flat, time-localised within the window. */
427
439
  export interface AudioDetection extends DetectionBase {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.279",
3
+ "version": "1.2.281",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",