@camstack/addon-export-google 0.1.1 → 0.1.2

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.
@@ -13447,6 +13447,114 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13447
13447
  limit: number().optional(),
13448
13448
  tags: record(string(), string()).optional()
13449
13449
  }), array(LogEntrySchema).readonly());
13450
+ /**
13451
+ * `failure-contribution` — the capability an addon reports its OWN losses
13452
+ * through, per camera, with the denominator attached. It stores nothing.
13453
+ *
13454
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13455
+ *
13456
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13457
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13458
+ * copied: the contributor reports what it already knows, hub-main adds only
13459
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13460
+ * somebody to forget to edit.
13461
+ *
13462
+ * They are not merged, because their invariants are opposites:
13463
+ *
13464
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13465
+ * claim a camera cost nothing, which is a measurement nobody made;
13466
+ * - a `failure-contribution` zero is the **most valuable value on the
13467
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13468
+ * and it is exactly what an absent entry cannot say.
13469
+ *
13470
+ * Putting a loss counter on a cost entry would also break the reconciliation
13471
+ * that gives `load-contribution` its point: contributions are subtracted from
13472
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13473
+ * has no process.
13474
+ *
13475
+ * ## Why not a log line, since the counters already exist
13476
+ *
13477
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13478
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13479
+ * ends in a log line, and a log line is the thing the operator asked to stop
13480
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13481
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13482
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13483
+ * media blackout were both diagnosed. The counters stay; this is where they can
13484
+ * be READ.
13485
+ *
13486
+ * ## The rate is served with its denominator or not at all
13487
+ *
13488
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13489
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13490
+ * than yesterday" and was **flat across twelve hours** once divided by the
13491
+ * successes on the same path. A surface that publishes only the numerator
13492
+ * reproduces that mistake on every read.
13493
+ *
13494
+ * ## Shape
13495
+ *
13496
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13497
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13498
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13499
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13500
+ * a forked runner's entries reach hub-main over transport that already exists.
13501
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13502
+ * result through `system.getFailureContributions`.
13503
+ */
13504
+ var FailureReasonCountSchema = object({
13505
+ /**
13506
+ * Why the attempt did not land, in the contributor's own vocabulary —
13507
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13508
+ * strings that already appear in this repo's logs and, where one exists, the
13509
+ * same string the per-track `previewMissReason` records (D276): a second
13510
+ * vocabulary for the same loss would make the row and the counter
13511
+ * un-joinable.
13512
+ */
13513
+ reason: string(),
13514
+ count: number().int().nonnegative()
13515
+ });
13516
+ var FailureContributionSchema = object({
13517
+ /**
13518
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13519
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13520
+ * `unit` free: the families are owned by different addons and a shared enum
13521
+ * is a central list that rots invisibly.
13522
+ */
13523
+ family: string(),
13524
+ /**
13525
+ * The NUMERIC device id — the same value every log line carries as
13526
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13527
+ * cannot name the camera must not emit the entry, because a fleet total
13528
+ * cannot answer the only question anybody asks of this surface.
13529
+ */
13530
+ deviceId: number().int().positive(),
13531
+ /**
13532
+ * A second dimension inside the family: the model / step id for an inference
13533
+ * timeout, so "which camera AND which model" is one read. Absent when the
13534
+ * family has a single variant.
13535
+ */
13536
+ variant: string().optional(),
13537
+ /**
13538
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13539
+ * differencing two reads must drop the interval when it changes, because the
13540
+ * counter restarted from zero in a respawned runner. Same discipline as
13541
+ * `LoadContribution.startedAtMs`.
13542
+ */
13543
+ sinceMs: number(),
13544
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13545
+ atMs: number(),
13546
+ /**
13547
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13548
+ * window. A failure count published without it is the mistake this schema
13549
+ * exists to make impossible.
13550
+ */
13551
+ attempts: number().int().nonnegative(),
13552
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13553
+ succeeded: number().int().nonnegative(),
13554
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13555
+ reasons: array(FailureReasonCountSchema).readonly()
13556
+ });
13557
+ method(_void(), array(FailureContributionSchema).readonly());
13450
13558
  var LoadContributionSchema = object({
13451
13559
  role: _enum([
13452
13560
  "decode",
@@ -17975,6 +18083,20 @@ var TrackSchema = object({
17975
18083
  * `=== true` and render nothing otherwise — never infer "no rider".
17976
18084
  */
17977
18085
  hasRider: boolean().optional(),
18086
+ /**
18087
+ * WHY this track ended without a NATIVE best-shot tile
18088
+ * ([D276](../decisions/adr-0276-a-stand-in-tile-is-provisional-and-a-close-says-why.md)) —
18089
+ * a composed token line (`no-key-frame capture=keyframe:native-missx4`,
18090
+ * `derive-returned-null tile=standin`, …) written at close and CLEARED by
18091
+ * the late-keyFrame upgrade when a native tile lands after all. The
18092
+ * operator-facing answer to "perché manca l'immagine?" on a track whose
18093
+ * tile is a face/plate stand-in, a raster crop, or an icon.
18094
+ *
18095
+ * **Absent ≠ "missed silently"**: a row written before the column, a hub
18096
+ * that predates the field, and every track whose tile landed native all
18097
+ * omit it. Render nothing when absent.
18098
+ */
18099
+ previewMissReason: string().optional(),
17978
18100
  ...TrackFlagFields,
17979
18101
  ...TrackRetrainFields
17980
18102
  });
@@ -27220,6 +27342,13 @@ var LoggingSettingsPatchSchema = object({
27220
27342
  * anyone but its owner.
27221
27343
  */
27222
27344
  var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: string() });
27345
+ /**
27346
+ * One per-camera failure counter, plus WHO reported it.
27347
+ *
27348
+ * Same rule as {@link ReportedLoadContributionSchema}: `addonId` is stamped by
27349
+ * the hub as it enumerates providers, never by the contributor.
27350
+ */
27351
+ var ReportedFailureContributionSchema = FailureContributionSchema.extend({ addonId: string() });
27223
27352
  var GetLoggingSettingsInputSchema = object({
27224
27353
  scopeNodeId: string().optional(),
27225
27354
  /**
@@ -27278,7 +27407,7 @@ method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), met
27278
27407
  }), method(_void(), SiteLocationStatusSchema, {
27279
27408
  kind: "mutation",
27280
27409
  auth: "admin"
27281
- }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
27410
+ }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(_void(), array(ReportedFailureContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
27282
27411
  kind: "mutation",
27283
27412
  auth: "admin"
27284
27413
  });
@@ -29860,6 +29989,12 @@ Object.freeze({
29860
29989
  addonId: null,
29861
29990
  access: "create"
29862
29991
  },
29992
+ "failureContribution.list": {
29993
+ capName: "failure-contribution",
29994
+ capScope: "system",
29995
+ addonId: null,
29996
+ access: "view"
29997
+ },
29863
29998
  "fanControl.setDirection": {
29864
29999
  capName: "fan-control",
29865
30000
  capScope: "device",
@@ -33166,6 +33301,12 @@ Object.freeze({
33166
33301
  addonId: null,
33167
33302
  access: "create"
33168
33303
  },
33304
+ "system.getFailureContributions": {
33305
+ capName: "system",
33306
+ capScope: "system",
33307
+ addonId: null,
33308
+ access: "view"
33309
+ },
33169
33310
  "system.getLoadContributions": {
33170
33311
  capName: "system",
33171
33312
  capScope: "system",
@@ -13443,6 +13443,114 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13443
13443
  limit: number().optional(),
13444
13444
  tags: record(string(), string()).optional()
13445
13445
  }), array(LogEntrySchema).readonly());
13446
+ /**
13447
+ * `failure-contribution` — the capability an addon reports its OWN losses
13448
+ * through, per camera, with the denominator attached. It stores nothing.
13449
+ *
13450
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
13451
+ *
13452
+ * `load-contribution` answers *what did this camera COST*. This answers *what
13453
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
13454
+ * copied: the contributor reports what it already knows, hub-main adds only
13455
+ * `addonId`, nothing needs global knowledge, and there is no central list for
13456
+ * somebody to forget to edit.
13457
+ *
13458
+ * They are not merged, because their invariants are opposites:
13459
+ *
13460
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
13461
+ * claim a camera cost nothing, which is a measurement nobody made;
13462
+ * - a `failure-contribution` zero is the **most valuable value on the
13463
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
13464
+ * and it is exactly what an absent entry cannot say.
13465
+ *
13466
+ * Putting a loss counter on a cost entry would also break the reconciliation
13467
+ * that gives `load-contribution` its point: contributions are subtracted from
13468
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
13469
+ * has no process.
13470
+ *
13471
+ * ## Why not a log line, since the counters already exist
13472
+ *
13473
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
13474
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
13475
+ * ends in a log line, and a log line is the thing the operator asked to stop
13476
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
13477
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
13478
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
13479
+ * media blackout were both diagnosed. The counters stay; this is where they can
13480
+ * be READ.
13481
+ *
13482
+ * ## The rate is served with its denominator or not at all
13483
+ *
13484
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
13485
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
13486
+ * than yesterday" and was **flat across twelve hours** once divided by the
13487
+ * successes on the same path. A surface that publishes only the numerator
13488
+ * reproduces that mistake on every read.
13489
+ *
13490
+ * ## Shape
13491
+ *
13492
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
13493
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
13494
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
13495
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
13496
+ * a forked runner's entries reach hub-main over transport that already exists.
13497
+ * No new UDS message, no second registry (D3). The operator reads the assembled
13498
+ * result through `system.getFailureContributions`.
13499
+ */
13500
+ var FailureReasonCountSchema = object({
13501
+ /**
13502
+ * Why the attempt did not land, in the contributor's own vocabulary —
13503
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
13504
+ * strings that already appear in this repo's logs and, where one exists, the
13505
+ * same string the per-track `previewMissReason` records (D276): a second
13506
+ * vocabulary for the same loss would make the row and the counter
13507
+ * un-joinable.
13508
+ */
13509
+ reason: string(),
13510
+ count: number().int().nonnegative()
13511
+ });
13512
+ var FailureContributionSchema = object({
13513
+ /**
13514
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
13515
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
13516
+ * `unit` free: the families are owned by different addons and a shared enum
13517
+ * is a central list that rots invisibly.
13518
+ */
13519
+ family: string(),
13520
+ /**
13521
+ * The NUMERIC device id — the same value every log line carries as
13522
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
13523
+ * cannot name the camera must not emit the entry, because a fleet total
13524
+ * cannot answer the only question anybody asks of this surface.
13525
+ */
13526
+ deviceId: number().int().positive(),
13527
+ /**
13528
+ * A second dimension inside the family: the model / step id for an inference
13529
+ * timeout, so "which camera AND which model" is one read. Absent when the
13530
+ * family has a single variant.
13531
+ */
13532
+ variant: string().optional(),
13533
+ /**
13534
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
13535
+ * differencing two reads must drop the interval when it changes, because the
13536
+ * counter restarted from zero in a respawned runner. Same discipline as
13537
+ * `LoadContribution.startedAtMs`.
13538
+ */
13539
+ sinceMs: number(),
13540
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
13541
+ atMs: number(),
13542
+ /**
13543
+ * THE DENOMINATOR — every attempt on this path for this camera in the
13544
+ * window. A failure count published without it is the mistake this schema
13545
+ * exists to make impossible.
13546
+ */
13547
+ attempts: number().int().nonnegative(),
13548
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
13549
+ succeeded: number().int().nonnegative(),
13550
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
13551
+ reasons: array(FailureReasonCountSchema).readonly()
13552
+ });
13553
+ method(_void(), array(FailureContributionSchema).readonly());
13446
13554
  var LoadContributionSchema = object({
13447
13555
  role: _enum([
13448
13556
  "decode",
@@ -17971,6 +18079,20 @@ var TrackSchema = object({
17971
18079
  * `=== true` and render nothing otherwise — never infer "no rider".
17972
18080
  */
17973
18081
  hasRider: boolean().optional(),
18082
+ /**
18083
+ * WHY this track ended without a NATIVE best-shot tile
18084
+ * ([D276](../decisions/adr-0276-a-stand-in-tile-is-provisional-and-a-close-says-why.md)) —
18085
+ * a composed token line (`no-key-frame capture=keyframe:native-missx4`,
18086
+ * `derive-returned-null tile=standin`, …) written at close and CLEARED by
18087
+ * the late-keyFrame upgrade when a native tile lands after all. The
18088
+ * operator-facing answer to "perché manca l'immagine?" on a track whose
18089
+ * tile is a face/plate stand-in, a raster crop, or an icon.
18090
+ *
18091
+ * **Absent ≠ "missed silently"**: a row written before the column, a hub
18092
+ * that predates the field, and every track whose tile landed native all
18093
+ * omit it. Render nothing when absent.
18094
+ */
18095
+ previewMissReason: string().optional(),
17974
18096
  ...TrackFlagFields,
17975
18097
  ...TrackRetrainFields
17976
18098
  });
@@ -27216,6 +27338,13 @@ var LoggingSettingsPatchSchema = object({
27216
27338
  * anyone but its owner.
27217
27339
  */
27218
27340
  var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: string() });
27341
+ /**
27342
+ * One per-camera failure counter, plus WHO reported it.
27343
+ *
27344
+ * Same rule as {@link ReportedLoadContributionSchema}: `addonId` is stamped by
27345
+ * the hub as it enumerates providers, never by the contributor.
27346
+ */
27347
+ var ReportedFailureContributionSchema = FailureContributionSchema.extend({ addonId: string() });
27219
27348
  var GetLoggingSettingsInputSchema = object({
27220
27349
  scopeNodeId: string().optional(),
27221
27350
  /**
@@ -27274,7 +27403,7 @@ method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), met
27274
27403
  }), method(_void(), SiteLocationStatusSchema, {
27275
27404
  kind: "mutation",
27276
27405
  auth: "admin"
27277
- }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
27406
+ }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(_void(), array(ReportedFailureContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
27278
27407
  kind: "mutation",
27279
27408
  auth: "admin"
27280
27409
  });
@@ -29856,6 +29985,12 @@ Object.freeze({
29856
29985
  addonId: null,
29857
29986
  access: "create"
29858
29987
  },
29988
+ "failureContribution.list": {
29989
+ capName: "failure-contribution",
29990
+ capScope: "system",
29991
+ addonId: null,
29992
+ access: "view"
29993
+ },
29859
29994
  "fanControl.setDirection": {
29860
29995
  capName: "fan-control",
29861
29996
  capScope: "device",
@@ -33162,6 +33297,12 @@ Object.freeze({
33162
33297
  addonId: null,
33163
33298
  access: "create"
33164
33299
  },
33300
+ "system.getFailureContributions": {
33301
+ capName: "system",
33302
+ capScope: "system",
33303
+ addonId: null,
33304
+ access: "view"
33305
+ },
33165
33306
  "system.getLoadContributions": {
33166
33307
  capName: "system",
33167
33308
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-google",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Google Home export — hub-side smart-home fulfillment (SYNC / QUERY / EXECUTE / DISCONNECT) for the non-camera fleet, served over the hub's own OAuth account link. No Google credential is stored, sent or required.",
5
5
  "keywords": [
6
6
  "camstack",