@camstack/addon-pipeline 1.2.345 → 1.2.346

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.
Files changed (35) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-CrEsRBe_.js → custom-model-catalog-B0QdF7dJ.js} +2 -2
  4. package/dist/{custom-model-catalog-C7zLb7ze.mjs → custom-model-catalog-C4LhTrew.mjs} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +8 -8
  7. package/dist/{dist-cT7hhXh9.mjs → dist-Dj26w1od.mjs} +2044 -60
  8. package/dist/{dist-_I6JkuJ8.js → dist-qBCVRsyf.js} +2073 -59
  9. package/dist/{model-catalogs-BrKLOMBe.js → model-catalogs-C6tbm7vF.js} +1 -1
  10. package/dist/{model-catalogs-CPAmiLRa.mjs → model-catalogs-CsxkEiiz.mjs} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-Dmga8QNL.mjs → node-Cc9bQ8ZM.mjs} +1 -1
  14. package/dist/{node-6zL5ddry.js → node-DmCpPH58.js} +1 -1
  15. package/dist/pipeline-runner/index.js +733 -6
  16. package/dist/pipeline-runner/index.mjs +739 -12
  17. package/dist/{process-memory-DZcnBnb1.mjs → process-memory-BrI4evjl.mjs} +1 -1
  18. package/dist/{process-memory-CstCtC3A.js → process-memory-BxIflT01.js} +1 -1
  19. package/dist/recorder/index.js +25 -3
  20. package/dist/recorder/index.mjs +25 -3
  21. package/dist/{segment-demux-js-qzo-pQK7.mjs → segment-demux-js-CmuAuckE.mjs} +1 -1
  22. package/dist/{segment-demux-js-DPtDBph6.js → segment-demux-js-DCLzkQTf.js} +1 -1
  23. package/dist/stream-broker/_stub.js +2 -2
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Drg8iwTA.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BX73joE9.mjs} +2 -2
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B38GLERV.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-pieGo_tC.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-Bz5eB0VL.mjs → hostInit-DKWabjpO.mjs} +2 -2
  30. package/dist/stream-broker/index.js +3 -3
  31. package/dist/stream-broker/index.mjs +5 -5
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/package.json +1 -1
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C2VoXjqc.mjs +0 -26
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DATpA6mb.mjs +0 -26
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- //#region ../types/dist/event-category-B_0XEcLF.mjs
2
+ //#region ../types/dist/event-category-DaIZi00R.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -78,6 +78,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
78
78
  */
79
79
  EventCategory["AddonLoadFailed"] = "addon.load-failed";
80
80
  /**
81
+ * A keychain value was stored or removed (D787). Carries the ref, its
82
+ * owner and the new version — NEVER the value. A process holding the ref
83
+ * drops its cached copy and the owning `BaseAddon` re-resolves its config.
84
+ * Telemetry (D8): a process that misses it still revalidates by version.
85
+ */
86
+ EventCategory["KeychainChanged"] = "keychain.changed";
87
+ /**
81
88
  * Addon recovered from a previous failure. Emitted when an addon
82
89
  * transitions from `failed` back to `healthy` (typically via the
83
90
  * monitor's auto-retry loop, or after manual `addons.retryLoad`).
@@ -5370,7 +5377,7 @@ var ZodIssueCode = {
5370
5377
  var ZodFirstPartyTypeKind;
5371
5378
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5372
5379
  //#endregion
5373
- //#region ../types/dist/sleep-BNeXenvr.mjs
5380
+ //#region ../types/dist/sleep-Cr5YOk3m.mjs
5374
5381
  /**
5375
5382
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5376
5383
  * window to float samples (D455).
@@ -5481,6 +5488,97 @@ function buildAlawTable() {
5481
5488
  }
5482
5489
  var ULAW_TABLE = buildUlawTable();
5483
5490
  var ALAW_TABLE = buildAlawTable();
5491
+ /** Is this field's stored value a credential? */
5492
+ function isSecretConfigField(field) {
5493
+ if (field.type === "password") return true;
5494
+ return "secret" in field && field.secret === true;
5495
+ }
5496
+ /**
5497
+ * Is this field's value stored in the KEYCHAIN (D787)? Every secret field is,
5498
+ * by its TYPE, unless it opts out with `storage: 'inline'` — never by its name.
5499
+ */
5500
+ function isKeychainStoredField(field) {
5501
+ if (!isSecretConfigField(field)) return false;
5502
+ if (field.type === "textarea" && field.isJson === true) return false;
5503
+ return !("storage" in field && field.storage === "inline");
5504
+ }
5505
+ /**
5506
+ * What a settings blob holds where a secret used to be.
5507
+ *
5508
+ * A keychain-stored field never holds its value. It holds a self-describing
5509
+ * MARKER, `{"$secret":"secret://…","v":1}` — an object, never a string, so no
5510
+ * code can mistake it for a credential and no form can send it to a camera as
5511
+ * a password. The empty state is `null` / absent, never `''`.
5512
+ *
5513
+ * The same shapes travel to and from the admin UI:
5514
+ *
5515
+ * - read: `hydrateSchema` hands a secret field a {@link SecretFieldView}
5516
+ * (`{ $secret, v, set: true }`) or `null` — the browser learns whether a
5517
+ * value is set, never the value.
5518
+ * - write: an untouched field submits the view back unchanged (KEEP); a typed
5519
+ * value is a plain non-empty string (CHANGE); the Clear button submits
5520
+ * {@link SECRET_CLEAR} (CLEAR). `''`, `null` and the legacy redaction tokens
5521
+ * are KEEP — an empty string is what a defaults-fallback pushes back, and
5522
+ * treating it as "clear" is how a save destroyed a stored credential before.
5523
+ *
5524
+ * Pure and browser-safe.
5525
+ */
5526
+ var SECRET_CLEAR = { $secretClear: true };
5527
+ /** The `'***'` token D634 redaction writes for non-admins. */
5528
+ var LEGACY_DEVICE_REDACTION = "***";
5529
+ function isRecord$1$1(value) {
5530
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5531
+ }
5532
+ function makeSecretMarker(ref) {
5533
+ return {
5534
+ $secret: ref,
5535
+ v: 1
5536
+ };
5537
+ }
5538
+ /** A stored marker: an object whose `$secret` is a non-empty string. */
5539
+ function isSecretMarker(value) {
5540
+ if (!isRecord$1$1(value)) return false;
5541
+ const ref = value["$secret"];
5542
+ return typeof ref === "string" && ref.length > 0;
5543
+ }
5544
+ function isSecretClear(value) {
5545
+ return isRecord$1$1(value) && value["$secretClear"] === true;
5546
+ }
5547
+ /**
5548
+ * The UI view of a stored secret field value: a marker becomes "set", inline
5549
+ * plaintext becomes "set (inline)", nothing becomes `null`. A value never
5550
+ * survives this function.
5551
+ */
5552
+ function toSecretFieldView(stored) {
5553
+ if (isSecretMarker(stored)) return {
5554
+ $secret: stored.$secret,
5555
+ v: 1,
5556
+ set: true
5557
+ };
5558
+ if (typeof stored === "string" && stored.length > 0) return {
5559
+ $secret: null,
5560
+ v: 1,
5561
+ set: true,
5562
+ inline: true
5563
+ };
5564
+ return null;
5565
+ }
5566
+ /**
5567
+ * Classify an incoming value for a secret path. Only a non-empty string that is
5568
+ * not a redaction token sets a value; only {@link SECRET_CLEAR} clears.
5569
+ * Everything else — a marker (ours or anyone's), a UI view, `''`, `null`,
5570
+ * `undefined`, a redaction token, a non-string — keeps what is stored.
5571
+ */
5572
+ function classifySecretWrite(incoming) {
5573
+ if (isSecretClear(incoming)) return { kind: "clear" };
5574
+ if (typeof incoming !== "string") return { kind: "keep" };
5575
+ if (incoming.length === 0) return { kind: "keep" };
5576
+ if (incoming === "__camstack_redacted__" || incoming === LEGACY_DEVICE_REDACTION) return { kind: "keep" };
5577
+ return {
5578
+ kind: "set",
5579
+ value: incoming
5580
+ };
5581
+ }
5484
5582
  Object.fromEntries([
5485
5583
  {
5486
5584
  id: "overview",
@@ -5652,33 +5750,19 @@ Object.fromEntries([
5652
5750
  function isValuelessField(field) {
5653
5751
  return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button";
5654
5752
  }
5655
- /**
5656
- * Merge a `ConfigUISchema` with a raw `values` record into a
5657
- * `ConfigUISchemaWithValues`. Used by every `get*Settings` backend endpoint
5658
- * before returning to the admin UI. Groups are walked recursively.
5659
- *
5660
- * For each leaf field:
5661
- * - structural fields (`separator`, `info`, `button`) pass through unchanged
5662
- * - other fields get `value = values[key] ?? field.default ?? null`
5663
- *
5664
- * Unknown keys in `values` (keys not declared in the schema) are silently
5665
- * ignored — they don't contribute to the output. Missing keys fall back to
5666
- * the schema default or `null`. This mirrors the old backend behaviour where
5667
- * `FormBuilder` rendered `values[key] ?? field.default`.
5668
- */
5669
- function hydrateSchema(schema, values) {
5753
+ function hydrateSchema(schema, values, options = {}) {
5670
5754
  return {
5671
5755
  ...schema.tabs ? { tabs: [...schema.tabs] } : {},
5672
5756
  sections: schema.sections.map((section) => ({
5673
5757
  ...section,
5674
- fields: section.fields.map((field) => hydrateField(field, values))
5758
+ fields: section.fields.map((field) => hydrateField(field, values, options))
5675
5759
  }))
5676
5760
  };
5677
5761
  }
5678
- function hydrateField(field, values) {
5762
+ function hydrateField(field, values, options) {
5679
5763
  if (isValuelessField(field)) return field;
5680
5764
  if (field.type === "group") {
5681
- const hydratedChildren = field.fields.map((child) => hydrateField(child, values));
5765
+ const hydratedChildren = field.fields.map((child) => hydrateField(child, values, options));
5682
5766
  return {
5683
5767
  ...field,
5684
5768
  fields: hydratedChildren
@@ -5687,7 +5771,7 @@ function hydrateField(field, values) {
5687
5771
  if (field.type === "sub-tabs") {
5688
5772
  const hydratedTabs = field.tabs.map((tab) => ({
5689
5773
  ...tab,
5690
- fields: tab.fields.map((child) => hydrateField(child, values))
5774
+ fields: tab.fields.map((child) => hydrateField(child, values, options))
5691
5775
  }));
5692
5776
  return {
5693
5777
  ...field,
@@ -5697,6 +5781,33 @@ function hydrateField(field, values) {
5697
5781
  const key = field.key;
5698
5782
  const storedValue = Object.prototype.hasOwnProperty.call(values, key) ? values[key] : void 0;
5699
5783
  const defaultValue = field.default;
5784
+ const asView = (v) => isSecretMarker(v) || options.inlineSecretsAsViews === true && v !== void 0;
5785
+ if (isKeychainStoredField(field) && asView(storedValue)) return {
5786
+ ...field,
5787
+ value: toSecretFieldView(storedValue)
5788
+ };
5789
+ if (isKeychainStoredField(field) && options.inlineSecretsAsViews === true) return {
5790
+ ...field,
5791
+ value: null
5792
+ };
5793
+ if (field.type === "editable-array" && Array.isArray(storedValue)) {
5794
+ const secretItemKeys = field.itemFields.filter((item) => "key" in item && isKeychainStoredField(item)).map((item) => "key" in item ? item.key : "");
5795
+ if (secretItemKeys.length > 0) {
5796
+ const rows = storedValue.map((row) => {
5797
+ if (row === null || typeof row !== "object" || Array.isArray(row)) return row;
5798
+ const out = { ...row };
5799
+ for (const k of secretItemKeys) {
5800
+ const v = Reflect.get(row, k);
5801
+ if (asView(v)) out[k] = toSecretFieldView(v);
5802
+ }
5803
+ return out;
5804
+ });
5805
+ return {
5806
+ ...field,
5807
+ value: rows
5808
+ };
5809
+ }
5810
+ }
5700
5811
  if (field.multiple) {
5701
5812
  const stored = Array.isArray(storedValue) ? storedValue : storedValue !== void 0 && storedValue !== null ? [storedValue] : [];
5702
5813
  const itemFallback = field.multiple.itemDefault !== void 0 ? field.multiple.itemDefault : defaultValue !== void 0 ? defaultValue : typeOf(field) === "string" ? "" : null;
@@ -5955,6 +6066,431 @@ function projectStore(store, perNodeKeys, nodeId) {
5955
6066
  }
5956
6067
  return out;
5957
6068
  }
6069
+ var GLOBAL_SCOPE = { kind: "global" };
6070
+ /** The scope every addon-wide (not per-node, not per-device) setting lives in. */
6071
+ function globalSecretScope() {
6072
+ return GLOBAL_SCOPE;
6073
+ }
6074
+ function encodeSegment(value) {
6075
+ return encodeURIComponent(value);
6076
+ }
6077
+ /**
6078
+ * The field path of a value inside an array item, addressed by the item's id:
6079
+ * `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
6080
+ * `providers[authentik].clientSecret`.
6081
+ */
6082
+ function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
6083
+ return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
6084
+ }
6085
+ /** A write to a secret path could not be stored in the keychain, so it was refused. */
6086
+ var KeychainWriteRefusedError = class extends Error {
6087
+ fieldPath;
6088
+ constructor(fieldPath, cause) {
6089
+ super(`secret "${fieldPath}" was not saved: the keychain could not store it (${cause instanceof Error ? cause.message : String(cause)}). Nothing was written.`);
6090
+ this.fieldPath = fieldPath;
6091
+ this.name = "KeychainWriteRefusedError";
6092
+ }
6093
+ };
6094
+ var KEEP = { kind: "keep" };
6095
+ function isRecord(value) {
6096
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6097
+ }
6098
+ function defaultMintId() {
6099
+ return globalThis.crypto.randomUUID();
6100
+ }
6101
+ /** Every marker ref anywhere under `value`. */
6102
+ function collectSecretMarkerRefs(value, out = []) {
6103
+ if (isSecretMarker(value)) {
6104
+ out.push(value.$secret);
6105
+ return out;
6106
+ }
6107
+ if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
6108
+ else if (isRecord(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6109
+ return out;
6110
+ }
6111
+ async function storedValueEquals(secrets, marker, candidate) {
6112
+ const resolved = (await secrets.resolve([marker.$secret]))[marker.$secret];
6113
+ return resolved?.kind === "value" && resolved.value === candidate;
6114
+ }
6115
+ async function writeLeaf(ctx, incoming, stored, fieldPath) {
6116
+ const intent = classifySecretWrite(incoming);
6117
+ const ref = ctx.secrets.ref(ctx.scope, fieldPath);
6118
+ const storedIsOurs = isSecretMarker(stored) && stored.$secret === ref;
6119
+ if (intent.kind === "keep") return KEEP;
6120
+ if (intent.kind === "set" && !ctx.operatorWrite && typeof stored === "string" && stored === intent.value) return KEEP;
6121
+ if (intent.kind === "clear") {
6122
+ if (storedIsOurs) ctx.deletes.push(ref);
6123
+ return {
6124
+ kind: "value",
6125
+ value: null
6126
+ };
6127
+ }
6128
+ if (storedIsOurs && await storedValueEquals(ctx.secrets, stored, intent.value)) return {
6129
+ kind: "value",
6130
+ value: stored
6131
+ };
6132
+ try {
6133
+ await ctx.secrets.put(ref, intent.value);
6134
+ } catch (err) {
6135
+ throw new KeychainWriteRefusedError(fieldPath, err);
6136
+ }
6137
+ return {
6138
+ kind: "value",
6139
+ value: makeSecretMarker(ref)
6140
+ };
6141
+ }
6142
+ /** Apply a member result onto `out`: KEEP restores the stored member, or removes it. */
6143
+ function applyMember(out, key, result, storedParent) {
6144
+ if (result.kind === "value") {
6145
+ out[key] = result.value;
6146
+ return;
6147
+ }
6148
+ if (Object.prototype.hasOwnProperty.call(storedParent, key)) out[key] = storedParent[key];
6149
+ else delete out[key];
6150
+ }
6151
+ async function writeMembers(ctx, children, incoming, stored, pathOf) {
6152
+ const out = { ...incoming };
6153
+ for (const [member, node] of children) applyMember(out, member, await writeNode(ctx, node, incoming[member], stored[member], pathOf(member)), stored);
6154
+ return out;
6155
+ }
6156
+ function rowId(row, idKey) {
6157
+ const raw = row[idKey];
6158
+ if (typeof raw === "string" && raw.length > 0) return raw;
6159
+ if (typeof raw === "number" && Number.isFinite(raw)) return String(raw);
6160
+ return null;
6161
+ }
6162
+ async function writeArray(ctx, node, incoming, stored, fieldPath) {
6163
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord) : [];
6164
+ if (!Array.isArray(incoming)) {
6165
+ ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
6166
+ return {
6167
+ kind: "value",
6168
+ value: incoming
6169
+ };
6170
+ }
6171
+ const storedById = /* @__PURE__ */ new Map();
6172
+ for (const row of storedRows) {
6173
+ const id = rowId(row, node.idKey);
6174
+ if (id !== null) storedById.set(id, row);
6175
+ }
6176
+ const kept = /* @__PURE__ */ new Set();
6177
+ const rows = [];
6178
+ for (const row of incoming) {
6179
+ if (!isRecord(row)) {
6180
+ rows.push(row);
6181
+ continue;
6182
+ }
6183
+ const id = rowId(row, node.idKey) ?? ctx.mintId();
6184
+ kept.add(id);
6185
+ const withId = {
6186
+ ...row,
6187
+ [node.idKey]: row[node.idKey] ?? id
6188
+ };
6189
+ if (rowId(withId, node.idKey) === null) withId[node.idKey] = id;
6190
+ const storedRow = storedById.get(id) ?? {};
6191
+ rows.push(await writeMembers(ctx, node.item, withId, storedRow, (member) => arrayItemFieldPath(fieldPath, id, member)));
6192
+ }
6193
+ for (const [id, row] of storedById) if (!kept.has(id)) ctx.deletes.push(...collectSecretMarkerRefs(row));
6194
+ return {
6195
+ kind: "value",
6196
+ value: rows
6197
+ };
6198
+ }
6199
+ async function writeNode(ctx, node, incoming, stored, fieldPath) {
6200
+ switch (node.kind) {
6201
+ case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
6202
+ case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
6203
+ case "object": {
6204
+ if (!isRecord(incoming)) {
6205
+ if (incoming === void 0) return KEEP;
6206
+ ctx.deletes.push(...collectSecretMarkerRefs(stored));
6207
+ return {
6208
+ kind: "value",
6209
+ value: incoming
6210
+ };
6211
+ }
6212
+ const storedObj = isRecord(stored) ? stored : {};
6213
+ return {
6214
+ kind: "value",
6215
+ value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
6216
+ };
6217
+ }
6218
+ }
6219
+ }
6220
+ /** Split a perNode-scoped key (`key@nodeId`). */
6221
+ function splitNodeKey(key) {
6222
+ const at = key.indexOf("@");
6223
+ return at <= 0 ? {
6224
+ base: key,
6225
+ nodeId: null
6226
+ } : {
6227
+ base: key.slice(0, at),
6228
+ nodeId: key.slice(at + 1)
6229
+ };
6230
+ }
6231
+ /** Rewrite a settings patch so no secret value reaches the blob. See the file doc. */
6232
+ async function transformSecretWrite(input) {
6233
+ const deletes = [];
6234
+ const base = {
6235
+ secrets: input.secrets,
6236
+ mintId: input.mintId ?? defaultMintId,
6237
+ deletes,
6238
+ operatorWrite: input.operatorWrite === true
6239
+ };
6240
+ const out = {};
6241
+ for (const [key, incoming] of Object.entries(input.patch)) {
6242
+ const { base: baseKey, nodeId } = splitNodeKey(key);
6243
+ const node = input.tree.get(baseKey);
6244
+ if (node === void 0) {
6245
+ out[key] = incoming;
6246
+ continue;
6247
+ }
6248
+ const scope = nodeId !== null && nodeId.length > 0 ? {
6249
+ kind: "node",
6250
+ nodeId
6251
+ } : input.scope ?? globalSecretScope();
6252
+ const result = await writeNode({
6253
+ ...base,
6254
+ scope
6255
+ }, node, incoming, input.stored[key], baseKey);
6256
+ if (result.kind === "value") out[key] = result.value;
6257
+ }
6258
+ return {
6259
+ patch: out,
6260
+ afterWrite: async () => {
6261
+ if (deletes.length === 0) return { orphaned: [] };
6262
+ try {
6263
+ await input.secrets.delete([...new Set(deletes)]);
6264
+ return { orphaned: [] };
6265
+ } catch {
6266
+ return { orphaned: [...new Set(deletes)] };
6267
+ }
6268
+ }
6269
+ };
6270
+ }
6271
+ /**
6272
+ * Replace every marker in `value` with its keychain value. Returns the SAME
6273
+ * reference when there is no marker (no allocation, no keychain call).
6274
+ */
6275
+ async function resolveSecretMarkers(value, secrets) {
6276
+ const refs = collectSecretMarkerRefs(value);
6277
+ if (refs.length === 0) return {
6278
+ value,
6279
+ unresolved: []
6280
+ };
6281
+ const answers = await secrets.resolve(refs);
6282
+ const unresolved = [];
6283
+ for (const ref of new Set(refs)) {
6284
+ const answer = answers[ref];
6285
+ if (answer === void 0) unresolved.push({
6286
+ ref,
6287
+ reason: "not-initialized"
6288
+ });
6289
+ else if (answer.kind === "unavailable") unresolved.push({
6290
+ ref,
6291
+ reason: answer.reason
6292
+ });
6293
+ else if (answer.kind === "missing" || answer.kind === "forbidden") unresolved.push({
6294
+ ref,
6295
+ reason: answer.kind
6296
+ });
6297
+ }
6298
+ const substitute = (v) => {
6299
+ if (isSecretMarker(v)) {
6300
+ const answer = answers[v.$secret];
6301
+ return answer?.kind === "value" ? {
6302
+ keep: true,
6303
+ value: answer.value
6304
+ } : {
6305
+ keep: false,
6306
+ value: void 0
6307
+ };
6308
+ }
6309
+ if (Array.isArray(v)) {
6310
+ const items = [];
6311
+ for (const item of v) {
6312
+ const s = substitute(item);
6313
+ if (s.keep) items.push(s.value);
6314
+ }
6315
+ return {
6316
+ keep: true,
6317
+ value: items
6318
+ };
6319
+ }
6320
+ if (isRecord(v)) {
6321
+ const out = {};
6322
+ for (const [k, child] of Object.entries(v)) {
6323
+ const s = substitute(child);
6324
+ if (s.keep) out[k] = s.value;
6325
+ }
6326
+ return {
6327
+ keep: true,
6328
+ value: out
6329
+ };
6330
+ }
6331
+ return {
6332
+ keep: true,
6333
+ value: v
6334
+ };
6335
+ };
6336
+ return {
6337
+ value: substitute(value).value,
6338
+ unresolved
6339
+ };
6340
+ }
6341
+ function touchesTree(tree, patch) {
6342
+ if (tree.size === 0) return false;
6343
+ for (const key of Object.keys(patch)) {
6344
+ const at = key.indexOf("@");
6345
+ if (tree.has(at > 0 ? key.slice(0, at) : key)) return true;
6346
+ }
6347
+ return false;
6348
+ }
6349
+ /** The blob as stored now; a failed read REFUSES the write instead of guessing. */
6350
+ async function readStoredAddonBlob(raw) {
6351
+ const threeWay = raw.readAddonStoreResult;
6352
+ if (threeWay === void 0) return raw.readAddonStore();
6353
+ const result = await threeWay.call(raw);
6354
+ if (result.kind === "failed") throw new Error(`settings write refused: the stored blob could not be read (${result.error})`);
6355
+ return result.kind === "missing" ? {} : result.value;
6356
+ }
6357
+ function createSecretAwareSettings(raw, opts) {
6358
+ const chains = /* @__PURE__ */ new Map();
6359
+ const serialize = (key, fn) => {
6360
+ const next = (chains.get(key) ?? Promise.resolve()).then(fn, fn);
6361
+ const tail = next.catch(() => void 0);
6362
+ chains.set(key, tail);
6363
+ tail.then(() => {
6364
+ if (chains.get(key) === tail) chains.delete(key);
6365
+ });
6366
+ return next;
6367
+ };
6368
+ const write = async (chainKey, tree, scope, patch, readStored, rawWrite) => {
6369
+ if (!touchesTree(tree, patch)) return rawWrite(patch);
6370
+ return serialize(chainKey, async () => {
6371
+ const stored = await readStored();
6372
+ const result = await transformSecretWrite({
6373
+ tree,
6374
+ secrets: opts.secrets,
6375
+ patch,
6376
+ stored,
6377
+ ...scope !== void 0 ? { scope } : {},
6378
+ operatorWrite: opts.isOperatorWrite?.() === true
6379
+ });
6380
+ if (Object.keys(result.patch).length > 0) await rawWrite(result.patch);
6381
+ const { orphaned } = await result.afterWrite();
6382
+ if (orphaned.length > 0) opts.onOrphaned(orphaned);
6383
+ });
6384
+ };
6385
+ const wrapped = {
6386
+ writeAddonStore: (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p)),
6387
+ writeDeviceStore: (deviceId, patch) => write(`device:${deviceId}`, opts.deviceTree(), {
6388
+ kind: "device",
6389
+ deviceId
6390
+ }, patch, () => raw.readDeviceStore(deviceId), (p) => raw.writeDeviceStore(deviceId, p))
6391
+ };
6392
+ return new Proxy(raw, { get(target, prop, receiver) {
6393
+ if (prop === "writeAddonStore") return wrapped.writeAddonStore;
6394
+ if (prop === "writeDeviceStore") return wrapped.writeDeviceStore;
6395
+ const value = Reflect.get(target, prop, receiver);
6396
+ return typeof value === "function" ? value.bind(target) : value;
6397
+ } });
6398
+ }
6399
+ /** The context the addon sees: identical, except that its settings writes are secret-aware. */
6400
+ function withSecretAwareSettings(context, opts) {
6401
+ const raw = context.settings;
6402
+ if (raw === void 0) return context;
6403
+ const settings = createSecretAwareSettings(raw, opts);
6404
+ return new Proxy(context, { get(target, prop, receiver) {
6405
+ if (prop === "settings") return settings;
6406
+ return Reflect.get(target, prop, receiver);
6407
+ } });
6408
+ }
6409
+ var LEAF = { kind: "leaf" };
6410
+ var DEFAULT_ITEM_ID_KEY = "id";
6411
+ function collectFields(fields, out) {
6412
+ for (const field of fields) {
6413
+ if (field.type === "group") {
6414
+ collectFields(field.fields, out);
6415
+ continue;
6416
+ }
6417
+ if (field.type === "sub-tabs") {
6418
+ for (const tab of field.tabs) collectFields(tab.fields, out);
6419
+ continue;
6420
+ }
6421
+ if (field.type === "editable-array") {
6422
+ const item = /* @__PURE__ */ new Map();
6423
+ for (const itemField of field.itemFields) if ("key" in itemField && isKeychainStoredField(itemField)) item.set(itemField.key, LEAF);
6424
+ if (item.size > 0) out.set(field.key, {
6425
+ kind: "array",
6426
+ idKey: field.itemIdKey ?? DEFAULT_ITEM_ID_KEY,
6427
+ item
6428
+ });
6429
+ continue;
6430
+ }
6431
+ if (!("key" in field) || typeof field.key !== "string") continue;
6432
+ if (isKeychainStoredField(field)) out.set(field.key, LEAF);
6433
+ }
6434
+ }
6435
+ /** The secret tree a settings schema declares. */
6436
+ function secretTreeFromSchema(schema) {
6437
+ const out = /* @__PURE__ */ new Map();
6438
+ if (schema === null) return out;
6439
+ for (const section of schema.sections) collectFields(section.fields, out);
6440
+ return out;
6441
+ }
6442
+ /** Parse `a.b[id].c` into segments; throws on a malformed path (a declaration bug). */
6443
+ function parseSecretStatePath(path) {
6444
+ const segments = [];
6445
+ for (const raw of path.split(".")) {
6446
+ const m = /^([^[\]]+)(?:\[([^[\]]+)\])?$/.exec(raw);
6447
+ if (m === null || m[1] === void 0) throw new Error(`secretStatePaths: malformed path "${path}"`);
6448
+ segments.push(m[2] !== void 0 ? {
6449
+ key: m[1],
6450
+ idKey: m[2]
6451
+ } : { key: m[1] });
6452
+ }
6453
+ if (segments.length === 0) throw new Error(`secretStatePaths: empty path`);
6454
+ if (segments[segments.length - 1]?.idKey !== void 0) throw new Error(`secretStatePaths: "${path}" ends on an array — name the member that is secret`);
6455
+ return segments;
6456
+ }
6457
+ function insert(into, segments) {
6458
+ const [head, ...rest] = segments;
6459
+ if (head === void 0) return;
6460
+ if (rest.length === 0) {
6461
+ into.set(head.key, LEAF);
6462
+ return;
6463
+ }
6464
+ const existing = into.get(head.key);
6465
+ if (head.idKey !== void 0) {
6466
+ const item = new Map(existing?.kind === "array" ? existing.item : []);
6467
+ insert(item, rest);
6468
+ into.set(head.key, {
6469
+ kind: "array",
6470
+ idKey: head.idKey,
6471
+ item
6472
+ });
6473
+ return;
6474
+ }
6475
+ const children = new Map(existing?.kind === "object" ? existing.children : []);
6476
+ insert(children, rest);
6477
+ into.set(head.key, {
6478
+ kind: "object",
6479
+ children
6480
+ });
6481
+ }
6482
+ /** Add hidden-state paths (`secretStatePaths`) to a schema-derived tree. */
6483
+ function mergeSecretStatePaths(tree, paths) {
6484
+ if (paths.length === 0) return tree;
6485
+ const out = new Map(tree);
6486
+ for (const path of paths) insert(out, parseSecretStatePath(path));
6487
+ return out;
6488
+ }
6489
+ /** First retry of a secret the keychain could not serve; doubles to {@link SECRET_RETRY_MAX_MS}. */
6490
+ var SECRET_RETRY_FIRST_MS = 5e3;
6491
+ var SECRET_RETRY_MAX_MS = 5 * 6e4;
6492
+ /** Coalesces a burst of `keychain.changed` events into one re-resolve. */
6493
+ var SECRET_CHANGE_DEBOUNCE_MS = 250;
5958
6494
  /**
5959
6495
  * Base class for CamStack addons. Eliminates settings boilerplate:
5960
6496
  *
@@ -6024,6 +6560,25 @@ var BaseAddon = class {
6024
6560
  settingsStoreReady = false;
6025
6561
  /** Default config values. Provided via constructor. */
6026
6562
  defaults;
6563
+ /**
6564
+ * Hidden-state paths that hold secrets (D787) — values the addon writes
6565
+ * itself (`updateGlobalSettings`, `state()`) that no form field declares.
6566
+ * Stored in the keychain exactly like a `password` field. Syntax:
6567
+ * `'snapshotSigningKey'`, `'identity.pincode'`, `'tokens[userId].accessToken'`
6568
+ * (`key[<id field>]` addresses array items by id, never by index).
6569
+ */
6570
+ secretStatePaths = [];
6571
+ _addonSecretTree = null;
6572
+ _deviceSecretTree = null;
6573
+ /** Refs the last resolved config pointed at — a change to one re-resolves. */
6574
+ _configSecretRefs = /* @__PURE__ */ new Set();
6575
+ _unresolvedSecrets = [];
6576
+ _secretRetryTimer = null;
6577
+ _secretRetryDelayMs = SECRET_RETRY_FIRST_MS;
6578
+ _secretChangeTimer = null;
6579
+ _secretChangeUnsub = null;
6580
+ /** >0 while an OPERATOR's settings write is being applied. */
6581
+ _operatorWriteDepth = 0;
6027
6582
  constructor(defaults) {
6028
6583
  this.defaults = defaults;
6029
6584
  this._config = { ...defaults };
@@ -6059,8 +6614,18 @@ var BaseAddon = class {
6059
6614
  return this._config;
6060
6615
  }
6061
6616
  async initialize(context) {
6062
- this._ctx = context;
6617
+ const secrets = context.secrets;
6618
+ this._ctx = secrets !== void 0 ? withSecretAwareSettings(context, {
6619
+ secrets,
6620
+ addonTree: () => this.addonSecretTree(),
6621
+ deviceTree: () => this.deviceSecretTree(),
6622
+ onOrphaned: (refs) => context.logger.warn("keychain refs dropped by a settings write could not be deleted", { meta: { refs: [...refs] } }),
6623
+ isOperatorWrite: () => this._operatorWriteDepth > 0
6624
+ }) : context;
6063
6625
  await this.resolveConfig();
6626
+ if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
6627
+ if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
6628
+ });
6064
6629
  const result = await this.onInitialize();
6065
6630
  this.emitLifecycle(EventCategory.AddonStarted);
6066
6631
  const normalized = normalizeAddonInitResult(result);
@@ -6093,6 +6658,12 @@ var BaseAddon = class {
6093
6658
  */
6094
6659
  async onHubReachable() {}
6095
6660
  async shutdown() {
6661
+ this._secretChangeUnsub?.();
6662
+ this._secretChangeUnsub = null;
6663
+ if (this._secretRetryTimer !== null) clearTimeout(this._secretRetryTimer);
6664
+ if (this._secretChangeTimer !== null) clearTimeout(this._secretChangeTimer);
6665
+ this._secretRetryTimer = null;
6666
+ this._secretChangeTimer = null;
6096
6667
  this.emitLifecycle(EventCategory.AddonStopped);
6097
6668
  if (this.autoEmitReadiness) this.emitReadinessForProviders("down");
6098
6669
  await this.onShutdown();
@@ -6171,7 +6742,7 @@ var BaseAddon = class {
6171
6742
  return hydrateSchema(schema, overlay ? {
6172
6743
  ...projected,
6173
6744
  ...overlay
6174
- } : projected);
6745
+ } : projected, { inlineSecretsAsViews: true });
6175
6746
  }
6176
6747
  /**
6177
6748
  * The integration-level view of this addon's settings: exactly the sections
@@ -6220,7 +6791,7 @@ var BaseAddon = class {
6220
6791
  return hydrateSchema({
6221
6792
  ...schema,
6222
6793
  sections
6223
- }, projected);
6794
+ }, projected, { inlineSecretsAsViews: true });
6224
6795
  }
6225
6796
  /**
6226
6797
  * The raw addon store PROJECTED onto the target node's bare per-node keys:
@@ -6274,18 +6845,50 @@ var BaseAddon = class {
6274
6845
  value: result.value[key]
6275
6846
  };
6276
6847
  }
6848
+ /**
6849
+ * The OPERATOR's write — a settings form or an admin API call, dispatched by
6850
+ * the kernel. Identical to {@link updateGlobalSettings}, except that a secret
6851
+ * the operator sends is stored as sent even when it equals a pre-keychain
6852
+ * inline value: the operator re-entering a password is a change, an addon
6853
+ * writing back what it read is not (D787).
6854
+ */
6855
+ async updateGlobalSettingsAsOperator(patch, nodeId) {
6856
+ this._operatorWriteDepth++;
6857
+ try {
6858
+ await this.applyGlobalPatch(patch, nodeId);
6859
+ } finally {
6860
+ this._operatorWriteDepth--;
6861
+ }
6862
+ }
6277
6863
  async updateGlobalSettings(patch, nodeId) {
6864
+ await this.applyGlobalPatch(patch, nodeId);
6865
+ }
6866
+ /**
6867
+ * REMOVE stored secrets — their keychain values and their markers (D787).
6868
+ *
6869
+ * Writing `''` to a secret field KEEPS what is stored (an empty string is
6870
+ * what a defaults fallback writes back, and it must never destroy a
6871
+ * credential), so code that means "forget this token" says so here. Without
6872
+ * a keychain (an older kernel) it writes `''`, as such code always did.
6873
+ */
6874
+ async clearSecrets(keys, nodeId) {
6875
+ const tree = this.addonSecretTree();
6876
+ const keychain = this._ctx?.secrets !== void 0;
6877
+ const patch = {};
6878
+ for (const key of keys) patch[key] = keychain && tree.has(key) ? SECRET_CLEAR : "";
6879
+ await this.applyGlobalPatch(patch, nodeId);
6880
+ }
6881
+ async applyGlobalPatch(barePatch, nodeId) {
6278
6882
  const keys = this.perNodeKeys();
6279
6883
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
6280
6884
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
6281
- const barePatch = patch;
6282
6885
  const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
6283
6886
  await this._ctx?.settings?.writeAddonStore(scoped);
6284
6887
  if (target !== localNode) return;
6285
6888
  await this.resolveConfig();
6286
6889
  await this.onConfigChanged();
6287
6890
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
6288
- this.maybeAutoRestart(patch, this.globalSettingsSchema());
6891
+ this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
6289
6892
  }
6290
6893
  /**
6291
6894
  * The set of field keys the global settings schema declares `perNode: true`
@@ -6310,7 +6913,7 @@ var BaseAddon = class {
6310
6913
  * time to return its response before the addon is torn down and
6311
6914
  * re-initialised by `AddonRegistryService.restartAddon`.
6312
6915
  */
6313
- maybeAutoRestart(patch, schema) {
6916
+ maybeAutoRestart(patchKeys, schema) {
6314
6917
  if (!schema) return;
6315
6918
  const restartKeys = /* @__PURE__ */ new Set();
6316
6919
  for (const section of schema.sections) for (const field of section.fields) {
@@ -6318,13 +6921,13 @@ var BaseAddon = class {
6318
6921
  if (field.requiresRestart) restartKeys.add(field.key);
6319
6922
  }
6320
6923
  if (restartKeys.size === 0) return;
6321
- if (!Object.keys(patch).some((k) => restartKeys.has(k))) return;
6924
+ if (!patchKeys.some((k) => restartKeys.has(k))) return;
6322
6925
  const ctx = this._ctx;
6323
6926
  if (!ctx) return;
6324
6927
  const addonId = ctx.id;
6325
6928
  setImmediate(() => {
6326
6929
  ctx.api.addons?.restartAddon?.mutate({ addonId }).then(() => {
6327
- ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: Object.keys(patch).filter((k) => restartKeys.has(k)) } });
6930
+ ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: patchKeys.filter((k) => restartKeys.has(k)) } });
6328
6931
  }).catch((err) => {
6329
6932
  ctx.logger.error("addon auto-restart failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
6330
6933
  });
@@ -6333,7 +6936,7 @@ var BaseAddon = class {
6333
6936
  async getDeviceSettings(deviceId) {
6334
6937
  const schema = this.deviceSettingsSchema();
6335
6938
  if (!schema) return { sections: [] };
6336
- return hydrateSchema(schema, await this._ctx?.settings?.readDeviceStore(deviceId) ?? {});
6939
+ return hydrateSchema(schema, await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
6337
6940
  }
6338
6941
  async updateDeviceSettings(deviceId, patch) {
6339
6942
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
@@ -6463,7 +7066,7 @@ var BaseAddon = class {
6463
7066
  * selection at boot (absent scoped key ⇒ the constructor default wins).
6464
7067
  */
6465
7068
  async resolveConfig() {
6466
- const stored = await this.readAddonStoreWithRetry();
7069
+ const stored = await this.resolveStoredSecrets(await this.readAddonStoreWithRetry());
6467
7070
  const perNode = this.perNodeKeys();
6468
7071
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
6469
7072
  const resolved = { ...this.defaults };
@@ -6488,7 +7091,7 @@ var BaseAddon = class {
6488
7091
  key,
6489
7092
  schema,
6490
7093
  fallback,
6491
- read: () => this.readAddonStoreWithRetry(),
7094
+ read: async () => this.resolveStoredSecrets(await this.readAddonStoreWithRetry()),
6492
7095
  write: async (patch) => {
6493
7096
  await this._ctx?.settings?.writeAddonStore(patch);
6494
7097
  },
@@ -6505,7 +7108,7 @@ var BaseAddon = class {
6505
7108
  key,
6506
7109
  schema,
6507
7110
  fallback,
6508
- read: async () => await this._ctx?.settings?.readDeviceStore(deviceId) ?? {},
7111
+ read: async () => this.resolveStoredSecrets(await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, false),
6509
7112
  write: async (patch) => {
6510
7113
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
6511
7114
  },
@@ -6517,6 +7120,70 @@ var BaseAddon = class {
6517
7120
  } })
6518
7121
  });
6519
7122
  }
7123
+ /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
7124
+ addonSecretTree() {
7125
+ this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
7126
+ return this._addonSecretTree;
7127
+ }
7128
+ deviceSecretTree() {
7129
+ this._deviceSecretTree ??= secretTreeFromSchema(this.deviceSettingsSchema());
7130
+ return this._deviceSecretTree;
7131
+ }
7132
+ /**
7133
+ * Secrets of this addon's stored config that could not be resolved on the
7134
+ * last read, with why. Non-empty means the addon is running WITHOUT them —
7135
+ * an addon whose function needs one should report itself degraded rather
7136
+ * than start on an empty credential.
7137
+ */
7138
+ get unresolvedSecrets() {
7139
+ return this._unresolvedSecrets;
7140
+ }
7141
+ /**
7142
+ * Substitute every keychain marker in a stored blob with its value. With no
7143
+ * keychain (no `ctx.secrets`) or no marker, the blob comes back unchanged.
7144
+ * An unresolvable value is LEFT OUT (never `''`), logged by ref — never by
7145
+ * value — and, when the cause can pass, retried with backoff. Nothing is
7146
+ * ever written back from here.
7147
+ */
7148
+ async resolveStoredSecrets(blob, trackForConfig = true) {
7149
+ const secrets = this._ctx?.secrets;
7150
+ if (secrets === void 0) return blob;
7151
+ const refs = collectSecretMarkerRefs(blob);
7152
+ if (trackForConfig) this._configSecretRefs = new Set(refs);
7153
+ if (refs.length === 0) {
7154
+ if (trackForConfig) this._unresolvedSecrets = [];
7155
+ return blob;
7156
+ }
7157
+ const { value, unresolved } = await resolveSecretMarkers(blob, secrets);
7158
+ if (trackForConfig) {
7159
+ this._unresolvedSecrets = unresolved;
7160
+ if (unresolved.length > 0) {
7161
+ this._ctx?.logger?.error?.("secret unavailable — running without it, will retry", { meta: { secrets: unresolved.map((u) => ({
7162
+ ref: u.ref,
7163
+ reason: u.reason
7164
+ })) } });
7165
+ if (unresolved.some((u) => u.reason !== "missing" && u.reason !== "forbidden")) {
7166
+ this.scheduleSecretReresolve(this._secretRetryDelayMs);
7167
+ this._secretRetryDelayMs = Math.min(this._secretRetryDelayMs * 2, SECRET_RETRY_MAX_MS);
7168
+ }
7169
+ } else this._secretRetryDelayMs = SECRET_RETRY_FIRST_MS;
7170
+ }
7171
+ return value;
7172
+ }
7173
+ /** Re-resolve the config (a keychain-only change, or a retry) and apply it. */
7174
+ scheduleSecretReresolve(delayMs) {
7175
+ const timerOf = delayMs === SECRET_CHANGE_DEBOUNCE_MS ? "_secretChangeTimer" : "_secretRetryTimer";
7176
+ if (this[timerOf] !== null) return;
7177
+ const timer = setTimeout(() => {
7178
+ this[timerOf] = null;
7179
+ if (this._ctx === null) return;
7180
+ this.resolveConfig().then(() => this.onConfigChanged()).catch((err) => {
7181
+ this._ctx?.logger?.error?.("re-resolving secrets failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
7182
+ });
7183
+ }, delayMs);
7184
+ timer.unref?.();
7185
+ this[timerOf] = timer;
7186
+ }
6520
7187
  /**
6521
7188
  * Wrap `ctx.settings.readAddonStore()` with a short retry budget so a
6522
7189
  * transient settings-store outage (mid-restart of sqlite-settings, tsx-watch
@@ -16830,6 +17497,196 @@ id: union([string(), number()]) }), {
16830
17497
  kind: "mutation",
16831
17498
  auth: "admin"
16832
17499
  });
17500
+ /**
17501
+ * `keychain` — the hub's encrypted store of secret values (D787).
17502
+ *
17503
+ * Settings blobs never hold a secret: they hold a marker
17504
+ * `{"$secret":"secret://<owner>/<scope>/<fieldPath>","v":1}` and the value
17505
+ * lives here, AES-256-GCM under a data key that a master key wraps. This cap
17506
+ * is the OWNER's door to its own values: every method is owner-checked
17507
+ * against the CALLER, never against anything the caller claims.
17508
+ *
17509
+ * Who the caller is: the transport stamps `callerAddonId` into the args of a
17510
+ * call that crossed a process (the parent `LocalChildRegistry` from the UDS
17511
+ * connection, the agent from its child's connection carried in
17512
+ * `$hub-cap-fwd`'s envelope). A stamp always overwrites; a wire call with no
17513
+ * stamp has its claim stripped. Only a hub in-process caller may state its own
17514
+ * identity — `ctx.secrets` does that with the addon id closed over. This is
17515
+ * identity against BUGS and misrouting, not against a malicious addon (every
17516
+ * runner is the same OS user): see the threat model in
17517
+ * `docs/design/2026-10-08-secrets-keychain.md`.
17518
+ *
17519
+ * `callerAddonId` is deliberately NOT in any input schema: the tRPC router
17520
+ * parses the input, so a session can never supply it, and the internal mount
17521
+ * floors every method to admin anyway (D240).
17522
+ *
17523
+ * Internal: addons reach it through `ctx.secrets`, never directly.
17524
+ */
17525
+ /** Why a value cannot be served right now. Never folded into "missing". */
17526
+ var KeychainUnavailableReasonSchema = _enum([
17527
+ "key-missing",
17528
+ "wrong-key",
17529
+ "key-file-unreadable",
17530
+ "store-unavailable",
17531
+ "not-initialized",
17532
+ "decrypt-failed"
17533
+ ]);
17534
+ var KeychainStateSchema = discriminatedUnion("state", [object({ state: literal("ready") }), object({
17535
+ state: literal("locked"),
17536
+ reason: KeychainUnavailableReasonSchema
17537
+ })]);
17538
+ var KeychainResolveEntrySchema = discriminatedUnion("kind", [
17539
+ object({
17540
+ kind: literal("value"),
17541
+ value: string(),
17542
+ version: number().int()
17543
+ }),
17544
+ object({ kind: literal("missing") }),
17545
+ object({
17546
+ kind: literal("unavailable"),
17547
+ reason: KeychainUnavailableReasonSchema
17548
+ }),
17549
+ object({ kind: literal("forbidden") })
17550
+ ]);
17551
+ var KeychainResolveResultSchema = record(string(), KeychainResolveEntrySchema);
17552
+ /** Metadata of one stored value — never the value. */
17553
+ var KeychainSecretMetaSchema = object({
17554
+ ref: string(),
17555
+ owner: string(),
17556
+ /** Formatted scope (`global`, `node/<id>`, `device/<id>`, …). */
17557
+ scope: string(),
17558
+ fieldPath: string(),
17559
+ version: number().int(),
17560
+ createdAt: number(),
17561
+ updatedAt: number(),
17562
+ /** `addon:<id>` | `user:<id>` | `system`. */
17563
+ updatedBy: string(),
17564
+ /** Coarse — written at most once an hour per ref. `null` = never read. */
17565
+ lastReadAt: number().nullable()
17566
+ });
17567
+ var RefListSchema = array(string().min(1)).max(512);
17568
+ method(object({
17569
+ ref: string().min(1),
17570
+ value: string().min(1)
17571
+ }), object({
17572
+ ref: string(),
17573
+ version: number().int()
17574
+ }), {
17575
+ kind: "mutation",
17576
+ auth: "admin"
17577
+ }), method(object({ refs: RefListSchema }), KeychainResolveResultSchema, { auth: "admin" }), method(object({ refs: RefListSchema }), object({ deleted: number().int() }), {
17578
+ kind: "mutation",
17579
+ auth: "admin"
17580
+ }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
17581
+ var KeychainKeySourceSchema = object({
17582
+ kind: _enum([
17583
+ "config-file",
17584
+ "previous-config-file",
17585
+ "platform",
17586
+ "default-file"
17587
+ ]),
17588
+ /** File sources only — a path is not a secret. */
17589
+ path: string().optional(),
17590
+ /**
17591
+ * True when a whole-volume backup of the data directory carries this key
17592
+ * next to the database it protects (the default file). The page warns.
17593
+ */
17594
+ sharesBackupWithDatabase: boolean()
17595
+ });
17596
+ var KeychainStatusSchema = object({
17597
+ keychain: KeychainStateSchema,
17598
+ /** The source of the master key in use; `null` while locked. */
17599
+ keySource: KeychainKeySourceSchema.nullable(),
17600
+ /** The source configured to be primary — differs from `keySource` while a rotation is pending. */
17601
+ configuredSource: KeychainKeySourceSchema,
17602
+ /** Short fingerprint of the master key in use (`HMAC(KEK, label)`, 16 hex). */
17603
+ fingerprint: string().nullable(),
17604
+ activeKeyId: string().nullable(),
17605
+ /**
17606
+ * The keychain unlocked with a NON-primary source: the configured primary
17607
+ * key is not the one that wraps the data key yet. `rotateMasterKey` moves it.
17608
+ */
17609
+ rotationPending: boolean(),
17610
+ /** Wrapped copies of the data key under a previous master key, kept until purged. */
17611
+ retiredWraps: number().int(),
17612
+ recoveryKeyDownloadedAt: number().nullable(),
17613
+ secretCount: number().int()
17614
+ });
17615
+ /**
17616
+ * Whether a stored value is still pointed at by its owner's settings.
17617
+ * `unknown` is a first-class answer: a scope whose blob could not be read is
17618
+ * never reported as an orphan (and never deleted as one).
17619
+ */
17620
+ var KeychainSecretUsageSchema = _enum([
17621
+ "in-use",
17622
+ "orphan",
17623
+ "unknown"
17624
+ ]);
17625
+ var KeychainAdminSecretSchema = KeychainSecretMetaSchema.extend({ usage: KeychainSecretUsageSchema });
17626
+ var KeychainAuditActionSchema = _enum([
17627
+ "put",
17628
+ "delete",
17629
+ "reveal",
17630
+ "rotate-master-key",
17631
+ "purge-retired-keys",
17632
+ "download-recovery-key",
17633
+ "delete-orphans",
17634
+ "reset",
17635
+ "initialize"
17636
+ ]);
17637
+ var KeychainAuditEntrySchema = object({
17638
+ id: string(),
17639
+ at: number(),
17640
+ action: KeychainAuditActionSchema,
17641
+ /** `addon:<id>` | `user:<id>` | `system`. */
17642
+ actor: string(),
17643
+ ref: string().nullable(),
17644
+ detail: string().nullable()
17645
+ });
17646
+ method(object({}), KeychainStatusSchema, { auth: "admin" }), method(object({}), array(KeychainAdminSecretSchema).readonly(), { auth: "admin" }), method(object({ limit: number().int().min(1).max(500).optional() }), array(KeychainAuditEntrySchema).readonly(), { auth: "admin" }), method(object({ ref: string().min(1) }), object({
17647
+ value: string(),
17648
+ version: number().int()
17649
+ }), {
17650
+ kind: "mutation",
17651
+ auth: "admin",
17652
+ caller: "required"
17653
+ }), method(object({ refs: array(string().min(1)).max(1e3) }), object({
17654
+ deleted: array(string()).readonly(),
17655
+ kept: array(object({
17656
+ ref: string(),
17657
+ usage: KeychainSecretUsageSchema
17658
+ })).readonly()
17659
+ }), {
17660
+ kind: "mutation",
17661
+ auth: "admin",
17662
+ caller: "required"
17663
+ }), method(object({}), object({
17664
+ fingerprint: string(),
17665
+ keySource: KeychainKeySourceSchema
17666
+ }), {
17667
+ kind: "mutation",
17668
+ auth: "admin",
17669
+ caller: "required"
17670
+ }), method(object({}), object({ purged: number().int() }), {
17671
+ kind: "mutation",
17672
+ auth: "admin",
17673
+ caller: "required"
17674
+ }), method(object({}), object({
17675
+ key: string(),
17676
+ fingerprint: string(),
17677
+ keySource: KeychainKeySourceSchema
17678
+ }), {
17679
+ kind: "mutation",
17680
+ auth: "admin",
17681
+ caller: "required"
17682
+ }), method(object({ confirm: literal("RESET KEYCHAIN") }), object({
17683
+ archivedCollection: string().nullable(),
17684
+ archived: number().int()
17685
+ }), {
17686
+ kind: "mutation",
17687
+ auth: "admin",
17688
+ caller: "required"
17689
+ });
16833
17690
  method(_void(), object({
16834
17691
  id: string(),
16835
17692
  name: string(),
@@ -17678,6 +18535,10 @@ var DeviceInfoSchema = object({
17678
18535
  isCamera: boolean(),
17679
18536
  /** Current config values — serializable snapshot */
17680
18537
  config: record(string(), unknown()),
18538
+ /** Paths inside `config` the device's schema declares secret (D783) —
18539
+ * what the non-admin redaction withholds. Paths, never values. Absent
18540
+ * when the owning runner has not reported them. */
18541
+ secretConfigPaths: array(string()).optional(),
17681
18542
  /** Hardware + identity blob (manufacturer / model / firmware / sn /
17682
18543
  * uid / mac / …). Populated by drivers; editable via setMetadata. */
17683
18544
  metadata: record(string(), unknown()).nullable().optional(),
@@ -17883,12 +18744,22 @@ var DeviceRegisterPayloadSchema = object({
17883
18744
  * call (workers re-register on init), so probe-driven updates
17884
18745
  * (e.g. PTZ flag flipping after the abilities probe) propagate. */
17885
18746
  features: array(string()).optional(),
17886
- config: record(string(), unknown())
18747
+ config: record(string(), unknown()),
18748
+ /** Config paths the device's own schema DECLARES secret
18749
+ * (`.meta({ secret: true })` — dotted, `[]` for array items, e.g.
18750
+ * `connection.password`). Re-published on every register; the non-admin
18751
+ * redaction withholds them at any depth (D783). Optional: an older runner
18752
+ * omits it and the keyword backstop applies. */
18753
+ secretConfigPaths: array(string()).optional()
17887
18754
  });
17888
18755
  var DeviceRemovePayloadSchema = object({ deviceId: number() });
17889
18756
  var DevicePersistConfigPayloadSchema = object({
17890
18757
  deviceId: number(),
17891
- data: record(string(), unknown())
18758
+ data: record(string(), unknown()),
18759
+ /** The paths the device's schema declares secret (D783), when the caller
18760
+ * knows them — those values are kept in the keychain, not the blob (D787).
18761
+ * Absent: the paths the device last reported at `registerDevice` apply. */
18762
+ secretConfigPaths: array(string()).optional()
17892
18763
  });
17893
18764
  /** What a migration actually did, per switch. `unreachable` is a first-class
17894
18765
  * answer: a camera that could not be asked is not a camera that was silenced. */
@@ -19596,7 +20467,14 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
19596
20467
  icon: string().optional(),
19597
20468
  /** Addon-owned HTTP route the button navigates to (GET). */
19598
20469
  startUrl: string(),
19599
- stage: LoginStageEnum
20470
+ stage: LoginStageEnum,
20471
+ /**
20472
+ * The provider names an external identity a signed-in user can link to
20473
+ * their own account (D782): `auth.listLinkableProviders` /
20474
+ * `beginExternalLink` offer exactly the redirect methods that set this,
20475
+ * whatever their id. Absent / false — a login button only (magic-link).
20476
+ */
20477
+ linkable: boolean().optional()
19600
20478
  }),
19601
20479
  object({
19602
20480
  kind: literal("widget"),
@@ -25304,8 +26182,23 @@ var RetrainMacroClassSchema = _enum([
25304
26182
  ]);
25305
26183
  /** A subject to learn, or a phantom to unlearn (taught by OMISSION). */
25306
26184
  var RetrainAnnotationKindSchema = _enum(["subject", "model_error"]);
25307
- /** Did a human draw this box, or did the assist propose it? */
25308
- var RetrainAnnotationSourceSchema = _enum(["operator", "assist"]);
26185
+ /**
26186
+ * Where a box came from. A machine-sourced box is a DRAFT on the page until
26187
+ * the operator confirms it; once saved it keeps its source as provenance.
26188
+ *
26189
+ * - `operator` — drawn by hand.
26190
+ * - `assist` — proposed by a detector run on the copy (the camera's own
26191
+ * model, or a larger teacher model).
26192
+ * - `pipeline` — the box the live pipeline actually drew on that frame, read
26193
+ * back from the track store (never a re-run).
26194
+ * - `llm` — proposed by a vision-capable LLM profile.
26195
+ */
26196
+ var RetrainAnnotationSourceSchema = _enum([
26197
+ "operator",
26198
+ "assist",
26199
+ "pipeline",
26200
+ "llm"
26201
+ ]);
25309
26202
  /** Normalised `[0,1]` rectangle against the FULL frame — the canonical form. */
25310
26203
  var RetrainBboxSchema = object({
25311
26204
  x: number(),
@@ -25323,7 +26216,8 @@ var RetrainBboxSchema = object({
25323
26216
  */
25324
26217
  var RetrainAnnotationSchema = object({
25325
26218
  id: string(),
25326
- trackId: string(),
26219
+ /** The frame's track. Absent on a recording still, which has none. */
26220
+ trackId: string().optional(),
25327
26221
  deviceId: number(),
25328
26222
  /** The COPY in retrain storage — never the source track's media key. */
25329
26223
  mediaKey: string(),
@@ -25372,18 +26266,63 @@ var RetrainFrameCandidateSchema = object({
25372
26266
  * fail, whatever became of the original. */
25373
26267
  copied: boolean()
25374
26268
  });
26269
+ /**
26270
+ * One box the live pipeline drew on a copied frame — what a correction starts
26271
+ * from. Derived when the frame is listed, from the track store: the subject
26272
+ * box recorded on the source media row when there is one, else the track
26273
+ * position nearest the frame's capture time. Never stored as an annotation
26274
+ * until the operator confirms it.
26275
+ */
26276
+ var RetrainPipelineBoxSchema = object({
26277
+ /** Normalised `[0,1]` against the full frame. */
26278
+ bbox: RetrainBboxSchema,
26279
+ macroClass: RetrainMacroClassSchema,
26280
+ label: string().optional(),
26281
+ subLabel: string().optional(),
26282
+ /** The detection score of the position used, when it was recorded. */
26283
+ score: number().optional(),
26284
+ /** The root model that produced the track, when it was recorded. */
26285
+ modelId: string().optional(),
26286
+ trackId: string(),
26287
+ /** `subject-box`: recorded on this very frame. `position`: the track's
26288
+ * position nearest the capture time, `deltaMs` away. */
26289
+ basis: _enum(["subject-box", "position"]),
26290
+ deltaMs: number()
26291
+ });
26292
+ /**
26293
+ * Where a retrain frame came from: a TRACK's stored media (copy-on-select),
26294
+ * or the RECORDING at an instant — the frame a model saw nothing in has no
26295
+ * track at all.
26296
+ */
26297
+ var RetrainFrameOriginSchema = _enum(["track", "recording"]);
25375
26298
  /** A frame the dataset OWNS: bytes copied at selection time. */
25376
26299
  var RetrainFrameSchema = object({
25377
26300
  frameId: string(),
25378
26301
  deviceId: number(),
25379
- trackId: string(),
25380
- /** Provenance only. It may already point at nothing — that is expected. */
25381
- sourceMediaId: number().int(),
26302
+ origin: RetrainFrameOriginSchema,
26303
+ /** The track it was copied from. Absent on a recording still. */
26304
+ trackId: string().optional(),
26305
+ /** Provenance only. It may already point at nothing — that is expected.
26306
+ * Absent on a recording still. */
26307
+ sourceMediaId: number().int().optional(),
26308
+ /** A recording still: the playhead instant it was taken at. */
26309
+ epochMs: number().optional(),
25382
26310
  sourceKind: MediaFileKindEnum,
25383
26311
  sizeBytes: number().int(),
25384
26312
  width: number().int(),
25385
26313
  height: number().int(),
25386
- copiedAt: number()
26314
+ copiedAt: number(),
26315
+ /** When the CAMERA captured the frame. Absent on a copy taken before D785. */
26316
+ capturedAt: number().optional(),
26317
+ /** The operator's "every subject in this frame is boxed". Detector
26318
+ * evaluation keeps only complete frames: an unboxed subject would count as
26319
+ * a false positive. */
26320
+ complete: boolean(),
26321
+ /** The boxes the pipeline drew on this frame. Present on list answers;
26322
+ * empty when nothing the track store holds can be placed on it. */
26323
+ pipelineBoxes: array(RetrainPipelineBoxSchema).readonly().optional(),
26324
+ /** The named collections this frame belongs to. Present on list answers. */
26325
+ collectionIds: array(string()).readonly().optional()
25387
26326
  });
25388
26327
  /** Why a copy-on-select could not be honoured — named, never a silent skip. */
25389
26328
  var RetrainCopyRefusalSchema = _enum([
@@ -25404,6 +26343,53 @@ var RetrainFrameListSchema = object({
25404
26343
  /** What the page pre-selects — the native key frame when one survives. */
25405
26344
  autoPickMediaId: number().int().optional()
25406
26345
  });
26346
+ /**
26347
+ * Which frames an annotated export carries. Every field narrows; an empty
26348
+ * filter is every annotated copy. A dataset VERSION is a frozen `frameIds`
26349
+ * list owned by whoever trains (the training orchestrator), so `frameIds` is
26350
+ * the primitive a version export uses; `batchId` re-selects what an earlier
26351
+ * export carried (`exportedInBatch`).
26352
+ */
26353
+ var RetrainExportFilterSchema = object({
26354
+ deviceIds: array(number()).optional(),
26355
+ frameIds: array(string()).max(2e4).optional(),
26356
+ /** Only frames in at least one of these named collections. */
26357
+ collectionIds: array(string()).max(200).optional(),
26358
+ batchId: string().optional(),
26359
+ /** Only frames the operator marked complete (all subjects boxed). */
26360
+ completeOnly: boolean().optional()
26361
+ });
26362
+ /**
26363
+ * `frames`: the full frame copies, boxes in `annotations.json`.
26364
+ * `crops`: additionally one JPEG per subject, cut with the runtime detail-crop
26365
+ * convention — the classifier layout.
26366
+ */
26367
+ var RetrainExportLayoutSchema = _enum(["frames", "crops"]);
26368
+ /**
26369
+ * Where `writeRetrainArchive` streams the tar: one HTTP request whose body is
26370
+ * the archive, sent chunk by chunk. Covers a presigned PUT and a provider
26371
+ * addon's data-plane upload route alike. Headers are sent verbatim and never
26372
+ * logged.
26373
+ */
26374
+ var RetrainArchiveTargetSchema = object({
26375
+ kind: literal("http"),
26376
+ method: _enum(["PUT", "POST"]),
26377
+ url: string().url(),
26378
+ headers: record(string(), string()).optional()
26379
+ });
26380
+ var RetrainArchiveResultSchema = object({
26381
+ batchId: string(),
26382
+ frames: number().int(),
26383
+ crops: number().int(),
26384
+ annotations: number().int(),
26385
+ /** Frames planned but not shipped (unreadable copy, path too long). */
26386
+ missing: number().int(),
26387
+ bytes: number().int(),
26388
+ /** sha256 of the exact bytes sent. */
26389
+ sha256: string(),
26390
+ /** The target's HTTP status. */
26391
+ status: number().int()
26392
+ });
25407
26393
  /** What the operator asked the assist to look for. */
25408
26394
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
25409
26395
  kind: literal("package"),
@@ -25433,6 +26419,109 @@ var RetrainAssistResultSchema = discriminatedUnion("kind", [object({
25433
26419
  reason: string(),
25434
26420
  detail: string().optional()
25435
26421
  })]);
26422
+ /** A named set over the retrain corpus — membership only, it owns no pixels. */
26423
+ var RetrainCollectionSchema = object({
26424
+ id: string(),
26425
+ name: string(),
26426
+ createdAt: number(),
26427
+ frameCount: number().int()
26428
+ });
26429
+ var RetrainCollectionCreateResultSchema = discriminatedUnion("kind", [object({
26430
+ kind: literal("created"),
26431
+ collection: RetrainCollectionSchema
26432
+ }), object({
26433
+ kind: literal("refused"),
26434
+ reason: _enum(["empty-name", "duplicate-name"])
26435
+ })]);
26436
+ var RetrainCollectionRenameResultSchema = discriminatedUnion("kind", [object({
26437
+ kind: literal("renamed"),
26438
+ collection: RetrainCollectionSchema
26439
+ }), object({
26440
+ kind: literal("refused"),
26441
+ reason: _enum([
26442
+ "empty-name",
26443
+ "duplicate-name",
26444
+ "unknown-collection"
26445
+ ])
26446
+ })]);
26447
+ /**
26448
+ * The outcome of adding the frame at a recording instant. Every refusal is
26449
+ * named: `still-unavailable` (not recording, a gap, or an undecodable frame —
26450
+ * `detail` says which), `still-busy` (the recorder is at its decode bound; try
26451
+ * again), `unreadable-image`, `write-failed`, `unknown-collection`.
26452
+ */
26453
+ var AddRetrainFrameFromRecordingResultSchema = discriminatedUnion("kind", [object({
26454
+ kind: literal("added"),
26455
+ collectionId: string(),
26456
+ frame: RetrainFrameSchema,
26457
+ /** The moment was already in the dataset; nothing was decoded. */
26458
+ reused: boolean()
26459
+ }), object({
26460
+ kind: literal("refused"),
26461
+ reason: _enum([
26462
+ "unknown-collection",
26463
+ "still-unavailable",
26464
+ "still-busy",
26465
+ "unreadable-image",
26466
+ "write-failed"
26467
+ ]),
26468
+ detail: string().optional()
26469
+ })]);
26470
+ /** A vision LLM profile the AI assist may use, and where it sends the frame. */
26471
+ var RetrainAssistProfileSchema = object({
26472
+ id: string(),
26473
+ name: string(),
26474
+ model: string(),
26475
+ kind: string(),
26476
+ /** `true` when the frame would leave the house (a cloud vendor, or an
26477
+ * endpoint that is not on the local network). The page confirms each such
26478
+ * run, and the provider refuses one without `allowCloud`. */
26479
+ leavesHouse: boolean()
26480
+ });
26481
+ /** A box the AI assist proposed: a draft, plus the model's own reason. */
26482
+ var RetrainLlmProposalSchema = RetrainAnnotationDraftSchema.extend({ reason: string() });
26483
+ /** What the AI assist said about a box that was already on the canvas. */
26484
+ var RetrainLlmReviewSchema = object({
26485
+ /** The `ref` the page sent the box under. */
26486
+ ref: string(),
26487
+ verdict: _enum([
26488
+ "confirmed",
26489
+ "wrong-class",
26490
+ "not-an-object",
26491
+ "uncertain"
26492
+ ]),
26493
+ suggestedClass: RetrainMacroClassSchema.optional(),
26494
+ reason: string()
26495
+ });
26496
+ /**
26497
+ * The AI assist's answer. `proposed` carries drafts (`source: 'llm'`, the
26498
+ * model and its confidence) and reviews; items the model got wrong (a box off
26499
+ * the frame, an unasked class, an unknown ref) are dropped and counted in
26500
+ * `rejected`. A whole answer that is not the schema is `malformed-answer`.
26501
+ */
26502
+ var RetrainLlmAssistResultSchema = discriminatedUnion("kind", [object({
26503
+ kind: literal("proposed"),
26504
+ profileId: string(),
26505
+ model: string(),
26506
+ /** The frame was sent outside the house for this answer. */
26507
+ leftHouse: boolean(),
26508
+ proposals: array(RetrainLlmProposalSchema).readonly(),
26509
+ reviews: array(RetrainLlmReviewSchema).readonly(),
26510
+ rejected: number().int()
26511
+ }), object({
26512
+ kind: literal("refused"),
26513
+ reason: _enum([
26514
+ "unknown-frame",
26515
+ "unreadable-frame",
26516
+ "unknown-profile",
26517
+ "profile-disabled",
26518
+ "no-vision",
26519
+ "cloud-not-confirmed",
26520
+ "llm-error",
26521
+ "malformed-answer"
26522
+ ]),
26523
+ detail: string().optional()
26524
+ })]);
25436
26525
  /** The outcome of a lifecycle move owned by the retrain page. */
25437
26526
  var RetrainTransitionResultSchema = object({
25438
26527
  trackId: string(),
@@ -26150,7 +27239,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
26150
27239
  auth: "admin"
26151
27240
  }), method(object({
26152
27241
  deviceId: number(),
26153
- trackId: string(),
27242
+ trackId: string().optional(),
26154
27243
  frameId: string()
26155
27244
  }), object({
26156
27245
  removed: boolean(),
@@ -26167,7 +27256,8 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
26167
27256
  auth: "admin"
26168
27257
  }), method(object({
26169
27258
  deviceId: number(),
26170
- trackId: string(),
27259
+ /** The frame's track, when it has one — checked against the frame. */
27260
+ trackId: string().optional(),
26171
27261
  frameId: string(),
26172
27262
  subject: RetrainAssistSubjectSchema,
26173
27263
  /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
@@ -26175,12 +27265,16 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
26175
27265
  }), RetrainAssistResultSchema, {
26176
27266
  kind: "mutation",
26177
27267
  auth: "admin"
26178
- }), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
27268
+ }), method(object({
27269
+ trackId: string().optional(),
27270
+ frameIds: array(string()).max(2e3).optional()
27271
+ }), array(RetrainAnnotationSchema).readonly(), {
26179
27272
  kind: "query",
26180
27273
  auth: "admin"
26181
27274
  }), method(object({
26182
27275
  deviceId: number(),
26183
- trackId: string(),
27276
+ /** The frame's track, when it has one — checked against the frame. */
27277
+ trackId: string().optional(),
26184
27278
  frameId: string(),
26185
27279
  annotations: array(RetrainAnnotationDraftSchema)
26186
27280
  }), array(RetrainAnnotationSchema).readonly(), {
@@ -26198,7 +27292,91 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
26198
27292
  }), RetrainTransitionResultSchema, {
26199
27293
  kind: "mutation",
26200
27294
  auth: "admin"
26201
- }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
27295
+ }), method(RetrainExportFilterSchema.extend({ layout: RetrainExportLayoutSchema.optional() }), object({ url: string() }), {
27296
+ kind: "query",
27297
+ auth: "admin"
27298
+ }), method(object({
27299
+ deviceId: number(),
27300
+ trackId: string().optional(),
27301
+ frameId: string(),
27302
+ complete: boolean()
27303
+ }), RetrainFrameSchema, {
27304
+ kind: "mutation",
27305
+ auth: "admin"
27306
+ }), method(object({
27307
+ filter: RetrainExportFilterSchema,
27308
+ layout: RetrainExportLayoutSchema,
27309
+ target: RetrainArchiveTargetSchema
27310
+ }), RetrainArchiveResultSchema, {
27311
+ kind: "mutation",
27312
+ auth: "admin",
27313
+ timeoutMs: 60 * 6e4
27314
+ }), method(object({}), array(RetrainAssistProfileSchema).readonly(), {
27315
+ kind: "query",
27316
+ auth: "admin"
27317
+ }), method(object({
27318
+ deviceId: number(),
27319
+ trackId: string().optional(),
27320
+ frameId: string(),
27321
+ profileId: string(),
27322
+ classes: array(RetrainMacroClassSchema).min(1),
27323
+ boxes: array(object({
27324
+ ref: string(),
27325
+ bbox: RetrainBboxSchema,
27326
+ macroClass: RetrainMacroClassSchema,
27327
+ label: string().optional()
27328
+ })).max(50),
27329
+ allowCloud: boolean()
27330
+ }), RetrainLlmAssistResultSchema, {
27331
+ kind: "mutation",
27332
+ auth: "admin",
27333
+ timeoutMs: 18e4
27334
+ }), method(object({ name: string().max(120) }), RetrainCollectionCreateResultSchema, {
27335
+ kind: "mutation",
27336
+ auth: "admin"
27337
+ }), method(object({}), array(RetrainCollectionSchema).readonly(), {
27338
+ kind: "query",
27339
+ auth: "admin"
27340
+ }), method(object({
27341
+ id: string(),
27342
+ name: string().max(120)
27343
+ }), RetrainCollectionRenameResultSchema, {
27344
+ kind: "mutation",
27345
+ auth: "admin"
27346
+ }), method(object({ id: string() }), object({
27347
+ deleted: boolean(),
27348
+ removedFrames: number().int()
27349
+ }), {
27350
+ kind: "mutation",
27351
+ auth: "admin"
27352
+ }), method(object({
27353
+ collectionId: string(),
27354
+ deviceId: number(),
27355
+ epochMs: number()
27356
+ }), AddRetrainFrameFromRecordingResultSchema, {
27357
+ kind: "mutation",
27358
+ auth: "admin",
27359
+ timeoutMs: 6e4
27360
+ }), method(object({
27361
+ collectionId: string(),
27362
+ frameIds: array(string()).min(1).max(500)
27363
+ }), object({
27364
+ added: number().int(),
27365
+ alreadyMember: number().int(),
27366
+ unknownFrameIds: array(string()).readonly()
27367
+ }), {
27368
+ kind: "mutation",
27369
+ auth: "admin"
27370
+ }), method(object({
27371
+ collectionId: string(),
27372
+ frameId: string()
27373
+ }), object({
27374
+ removed: boolean(),
27375
+ deletedFrame: boolean()
27376
+ }), {
27377
+ kind: "mutation",
27378
+ auth: "admin"
27379
+ }), method(object({ collectionId: string() }), array(RetrainFrameSchema).readonly(), {
26202
27380
  kind: "query",
26203
27381
  auth: "admin"
26204
27382
  }), method(object({
@@ -27026,13 +28204,31 @@ var RecommendationReasonSchema = discriminatedUnion("kind", [
27026
28204
  object({
27027
28205
  kind: literal("no-family-build"),
27028
28206
  format: ModelFormatSchema$1
28207
+ }),
28208
+ object({
28209
+ kind: literal("kept-custom-pin"),
28210
+ modelId: string(),
28211
+ deviceKey: string()
27029
28212
  })
27030
28213
  ]);
28214
+ /** Why a device is left exactly as it is, whatever the plan would pick (D786). */
28215
+ var KeptReasonSchema = _enum(["custom-pin"]);
28216
+ /**
28217
+ * Where a choice's latency came from (D791): this node's own benchmark
28218
+ * result, or the hardware-class table. Measured on the node beats the table.
28219
+ */
28220
+ var LatencySourceSchema = discriminatedUnion("kind", [object({ kind: literal("table") }), object({
28221
+ kind: literal("node-benchmark"),
28222
+ resultId: string(),
28223
+ measuredAtMs: number()
28224
+ })]);
27031
28225
  var DeviceModelChoiceSchema = object({
27032
28226
  modelId: string(),
27033
28227
  p50Ms: number(),
27034
28228
  fps: number(),
27035
28229
  measured: boolean(),
28230
+ /** Absent on a plan from before D791 — read as the table. */
28231
+ source: LatencySourceSchema.optional(),
27036
28232
  /** Parallel sessions at the camera rate with the headroom kept free: ⌊fps × (1 − headroom) / camera fps⌋. */
27037
28233
  capacitySessions: number(),
27038
28234
  personRecall: number()
@@ -27043,6 +28239,8 @@ var DeviceRecommendationSchema = object({
27043
28239
  enabled: boolean(),
27044
28240
  available: boolean(),
27045
28241
  chosen: DeviceModelChoiceSchema.nullable(),
28242
+ /** Present when the device is kept as is: Apply writes nothing on it. */
28243
+ kept: KeptReasonSchema.optional(),
27046
28244
  reasons: array(RecommendationReasonSchema).readonly()
27047
28245
  });
27048
28246
  var NodeRecommendationSchema = object({
@@ -27096,6 +28294,17 @@ var AutoTuneWarningSchema = discriminatedUnion("kind", [
27096
28294
  kind: literal("coral-enabled"),
27097
28295
  nodeId: string(),
27098
28296
  deviceKey: string()
28297
+ }),
28298
+ object({
28299
+ kind: literal("custom-pin-kept"),
28300
+ nodeId: string(),
28301
+ deviceKey: string().nullable(),
28302
+ deviceId: number().nullable(),
28303
+ modelId: string()
28304
+ }),
28305
+ object({
28306
+ kind: literal("custom-registry-unreadable"),
28307
+ error: string()
27099
28308
  })
27100
28309
  ]);
27101
28310
  var AutoTuneOptionsSchema = object({
@@ -27274,13 +28483,14 @@ var SuggestSharesResultSchema = object({
27274
28483
  nodes: array(SuggestedNodeShareSchema).readonly()
27275
28484
  });
27276
28485
  /**
27277
- * Where the node and device shares come from (D750, D755).
27278
- * - `hardware` (default) — computed by the system from each node's static
27279
- * capacity (its accelerators at their Auto tune models, from the measured
27280
- * latency table), recomputed whenever a node or device joins, leaves, is
27281
- * switched on or off, or changes model. No load input.
27282
- * - `benchmark` — the same, from each node's measured capacity (node
27283
- * benchmark results); a node without results falls back to `hardware`.
28486
+ * Where the node and device shares come from (D750, D755, D791).
28487
+ * - `benchmark` (default since D791) — each node's capacity as its own node
28488
+ * benchmark measured it; a node without fresh results is sized from
28489
+ * `hardware` and says so. Measured on the node beats the table.
28490
+ * - `hardware` — computed from each node's static capacity only (its
28491
+ * accelerators at their Auto tune models, from the latency table).
28492
+ * Both are recomputed whenever a node or device joins, leaves, is switched
28493
+ * on or off, changes model, or a benchmark result is written. No load input.
27284
28494
  * - `manual` — the operator's stored sliders.
27285
28495
  * Either way a node or device that cannot take sessions gives its share to
27286
28496
  * the others in proportion; the stored values are never rewritten for that.
@@ -27329,6 +28539,362 @@ var EffectiveSharesSchema = object({
27329
28539
  nodeWeights: record(string(), number()),
27330
28540
  deviceWeights: record(string(), record(string(), number()))
27331
28541
  });
28542
+ /**
28543
+ * Wire shapes of the node benchmark (D791): the operator's plan, the
28544
+ * preflight the confirmation shows, the run the orchestrator owns, what the
28545
+ * measuring runner reports, and the result rows Auto tune and the automatic
28546
+ * shares read.
28547
+ *
28548
+ * ONE node at a time, owned by the pipeline orchestrator. While a run is
28549
+ * active the cluster is in BENCHMARK MODE for that node: it is not a
28550
+ * detection candidate, camera pins naming it are ignored so its sessions move
28551
+ * elsewhere, and both come back when the mode ends — after a crash or a hub
28552
+ * restart too (a persisted manifest, reconciled against the registry).
28553
+ *
28554
+ * Two measurements, both on the node, driven by its own runner (frames never
28555
+ * cross a process at frame rate, D9):
28556
+ * - `device` — one accelerator × one candidate detector, root only, open
28557
+ * loop: how many streams at the camera rate it carries inside the frame
28558
+ * interval;
28559
+ * - `pipeline` — the chosen detector plus the operator's detail steps
28560
+ * (faces, plates, classifiers, CLIP…), on varied frames from the bench
28561
+ * clip: how many parallel sessions the node carries before latency, drops,
28562
+ * CPU, RAM or the event loop give out.
28563
+ */
28564
+ /** Defaults and bounds, one place. */
28565
+ var NODE_BENCHMARK_DEFAULTS = {
28566
+ fpsPerStream: 10,
28567
+ budgetMinutes: 45,
28568
+ minBudgetMinutes: 10,
28569
+ maxBudgetMinutes: 120,
28570
+ /** A result older than this no longer beats the table. */
28571
+ staleAfterDays: 30,
28572
+ /** Warm-in per ramp step (not accounted). */
28573
+ settleSec: 5,
28574
+ /** Accounted window per ramp step. */
28575
+ measureSec: 20,
28576
+ /** Longest the drain may take before the run is refused. */
28577
+ drainTimeoutSec: 120,
28578
+ /** Candidate models per device in one run. */
28579
+ maxModelsPerDevice: 6
28580
+ };
28581
+ /** The two measurements a run can make. */
28582
+ var NodeBenchmarkPhaseSchema = _enum(["device", "pipeline"]);
28583
+ var NodeBenchmarkDevicePlanSchema = object({
28584
+ /** `<backend>:<device>` — `openvino:npu`, `coreml:all`, `cpu`. */
28585
+ deviceKey: string().min(1),
28586
+ /**
28587
+ * Root detector candidates for the `device` measurement, in the order
28588
+ * measured. The FIRST is the one the `pipeline` measurement runs.
28589
+ */
28590
+ modelIds: array(string().min(1)).min(1).max(NODE_BENCHMARK_DEFAULTS.maxModelsPerDevice)
28591
+ });
28592
+ /** What the operator confirmed. */
28593
+ var NodeBenchmarkPlanSchema = object({
28594
+ nodeId: string().min(1),
28595
+ devices: array(NodeBenchmarkDevicePlanSchema).min(1),
28596
+ /** Which measurements run. At least one. */
28597
+ phases: array(NodeBenchmarkPhaseSchema).min(1).default(["device", "pipeline"]),
28598
+ /**
28599
+ * Detail steps (catalog step ids under the root: faces, plates,
28600
+ * classifiers, CLIP…) the `pipeline` measurement enables. The root
28601
+ * detector always runs.
28602
+ */
28603
+ steps: array(string().min(1)).default([]),
28604
+ /** Offered rate of one stream / session. */
28605
+ fpsPerStream: number().int().min(1).max(30).default(NODE_BENCHMARK_DEFAULTS.fpsPerStream),
28606
+ /** Hard deadline: the mode ends and the cluster is restored when it passes. */
28607
+ budgetMinutes: number().int().min(NODE_BENCHMARK_DEFAULTS.minBudgetMinutes).max(NODE_BENCHMARK_DEFAULTS.maxBudgetMinutes).default(NODE_BENCHMARK_DEFAULTS.budgetMinutes),
28608
+ /** Keep the models the run downloaded (default: delete what was not installed before). */
28609
+ keepDownloadedModels: boolean().default(false),
28610
+ /** Start although the other nodes' capacity does not cover the sessions running now. */
28611
+ acceptInsufficientHeadroom: boolean().default(false)
28612
+ });
28613
+ var NodeBenchmarkRefusalSchema = object({
28614
+ kind: _enum([
28615
+ "benchmark-active",
28616
+ "node-offline",
28617
+ "unknown-device",
28618
+ "no-phase",
28619
+ "insufficient-headroom"
28620
+ ]),
28621
+ detail: string(),
28622
+ overridable: boolean()
28623
+ });
28624
+ /** A camera whose detection leaves the node for the run. */
28625
+ var NodeBenchmarkMovingCameraSchema = object({
28626
+ deviceId: number(),
28627
+ /** Placed on the node now (a standing attach or a running session). */
28628
+ placedOnNode: boolean(),
28629
+ /** A node pin naming this node, ignored for the run. */
28630
+ pinnedToNode: boolean()
28631
+ });
28632
+ /** A camera that cannot leave: no other node can obtain its frames. */
28633
+ var NodeBenchmarkStayingCameraSchema = object({
28634
+ deviceId: number(),
28635
+ reason: literal("only-frame-source")
28636
+ });
28637
+ var NodeBenchmarkPreflightSchema = object({
28638
+ nodeId: string(),
28639
+ online: boolean(),
28640
+ moving: array(NodeBenchmarkMovingCameraSchema).readonly(),
28641
+ /** Keep running on the node during the run; the result is flagged contaminated. */
28642
+ staying: array(NodeBenchmarkStayingCameraSchema).readonly(),
28643
+ estimatedDurationMs: number(),
28644
+ /** The run ends and the cluster is restored at the latest after this long. */
28645
+ budgetMs: number(),
28646
+ phases: array(NodeBenchmarkPhaseSchema).readonly(),
28647
+ steps: array(string()).readonly(),
28648
+ devices: array(NodeBenchmarkDevicePlanSchema).readonly(),
28649
+ /** Parallel sessions the cluster runs now, and what the other nodes can carry. */
28650
+ headroom: object({
28651
+ sessionsNow: number(),
28652
+ otherNodesCapacitySessions: number().nullable()
28653
+ }),
28654
+ refusals: array(NodeBenchmarkRefusalSchema).readonly(),
28655
+ canStart: boolean()
28656
+ });
28657
+ /** Where the measuring runner is. */
28658
+ var NodeBenchProgressSchema = object({
28659
+ phase: _enum([
28660
+ "preparing",
28661
+ "device",
28662
+ "pipeline",
28663
+ "cleanup"
28664
+ ]),
28665
+ deviceKey: string().nullable(),
28666
+ modelId: string().nullable(),
28667
+ /** Streams (device) or sessions (pipeline) of the step running now. */
28668
+ load: number().nullable(),
28669
+ cellsDone: number(),
28670
+ cellsTotal: number(),
28671
+ message: string()
28672
+ });
28673
+ /** What limited a ramp. `none` = the ramp ran out of steps still passing. */
28674
+ var NodeBenchLimitSchema = _enum([
28675
+ "p95",
28676
+ "drops",
28677
+ "errors",
28678
+ "stall",
28679
+ "cpu",
28680
+ "ram",
28681
+ "none"
28682
+ ]);
28683
+ var NodeBenchRampStepSchema = object({
28684
+ load: number(),
28685
+ pass: boolean(),
28686
+ p95Ms: number(),
28687
+ dropRatio: number(),
28688
+ /** Completed frames per second over the accounted window. */
28689
+ fps: number(),
28690
+ cpuPct: number().nullable(),
28691
+ ramFreePct: number().nullable(),
28692
+ failReasons: array(string()).readonly()
28693
+ });
28694
+ var NodeBenchCellSchema = discriminatedUnion("phase", [object({
28695
+ phase: literal("device"),
28696
+ deviceKey: string(),
28697
+ modelId: string(),
28698
+ /** Single-stream latency (the K = 1 step). */
28699
+ p50Ms: number(),
28700
+ p95Ms: number(),
28701
+ p99Ms: number(),
28702
+ /** Frames per second sustained at the last passing step. */
28703
+ fps: number(),
28704
+ /** Streams at the camera rate the device carried inside the frame interval; 0 = not even one. */
28705
+ kMax: number(),
28706
+ limitedBy: NodeBenchLimitSchema,
28707
+ /** First call (model load + warm-up). */
28708
+ warmupMs: number(),
28709
+ ramp: array(NodeBenchRampStepSchema).readonly()
28710
+ }), object({
28711
+ phase: literal("pipeline"),
28712
+ deviceKey: string(),
28713
+ modelId: string(),
28714
+ steps: array(string()).readonly(),
28715
+ /** Parallel sessions at the camera rate the node carried; 0 = not even one. */
28716
+ maxSessions: number(),
28717
+ limitedBy: NodeBenchLimitSchema,
28718
+ /** Detail steps that produced at least one result on the clip — a step that never fired measured nothing. */
28719
+ stepsFired: array(string()).readonly(),
28720
+ warmupMs: number(),
28721
+ ramp: array(NodeBenchRampStepSchema).readonly()
28722
+ })]);
28723
+ /**
28724
+ * What a result was measured ON. A result whose fingerprint no longer matches
28725
+ * the node no longer beats the table.
28726
+ */
28727
+ var NodeBenchFingerprintSchema = object({
28728
+ pipelineVersion: string().nullable(),
28729
+ cpuModel: string().nullable(),
28730
+ ramGb: number().nullable(),
28731
+ os: string().nullable(),
28732
+ /** Per device: the pool the cells ran on (runtime + tuning). */
28733
+ pools: record(string(), object({
28734
+ runtime: string(),
28735
+ numWorkers: number(),
28736
+ concurrency: number(),
28737
+ batchMode: string()
28738
+ })).default({})
28739
+ });
28740
+ /** The frames the run used. */
28741
+ var NodeBenchClipSchema = object({
28742
+ /** `camstack-bench-v1` or `reference-images` (the fallback when the clip is not reachable). */
28743
+ id: string(),
28744
+ revision: string().nullable(),
28745
+ frames: number(),
28746
+ width: number(),
28747
+ height: number()
28748
+ });
28749
+ var NodeBenchRunnerInputSchema = object({
28750
+ runId: string().min(1),
28751
+ devices: array(NodeBenchmarkDevicePlanSchema).min(1),
28752
+ phases: array(NodeBenchmarkPhaseSchema).min(1),
28753
+ /** The full detail step tree root for the `pipeline` measurement is built on the node from these ids. */
28754
+ steps: array(string()).default([]),
28755
+ fpsPerStream: number().int().min(1).max(30),
28756
+ /** The runner stops on its own past this instant, whatever the hub does. */
28757
+ deadlineAtMs: number(),
28758
+ settleSec: number().min(0).max(60).default(NODE_BENCHMARK_DEFAULTS.settleSec),
28759
+ measureSec: number().min(2).max(300).default(NODE_BENCHMARK_DEFAULTS.measureSec),
28760
+ keepDownloadedModels: boolean().default(false)
28761
+ });
28762
+ var NodeBenchRunnerStateSchema = _enum([
28763
+ "running",
28764
+ "done",
28765
+ "failed",
28766
+ "aborted"
28767
+ ]);
28768
+ var NodeBenchRunnerStatusSchema = object({
28769
+ runId: string(),
28770
+ nodeId: string(),
28771
+ state: NodeBenchRunnerStateSchema,
28772
+ progress: NodeBenchProgressSchema.nullable(),
28773
+ /** Every finished cell so far — a run stopped half way keeps what it measured. */
28774
+ cells: array(NodeBenchCellSchema).readonly(),
28775
+ fingerprint: NodeBenchFingerprintSchema.nullable(),
28776
+ clip: NodeBenchClipSchema.nullable(),
28777
+ /** Cameras seen running on the node during the run (they share its pools). */
28778
+ activeCameraIds: array(number()).readonly(),
28779
+ /** Models the run downloaded and then deleted (not installed before). */
28780
+ deletedModels: array(string()).readonly(),
28781
+ error: string().nullable(),
28782
+ startedAtMs: number(),
28783
+ finishedAtMs: number().nullable()
28784
+ });
28785
+ var NodeBenchmarkStateSchema = _enum([
28786
+ "draining",
28787
+ "measuring",
28788
+ "restoring",
28789
+ "done",
28790
+ "failed",
28791
+ "aborted"
28792
+ ]);
28793
+ /** Why a run ended. */
28794
+ var NodeBenchmarkEndReasonSchema = _enum([
28795
+ "completed",
28796
+ "operator-abort",
28797
+ "deadline",
28798
+ "node-lost",
28799
+ "runner-lost",
28800
+ "hub-restart",
28801
+ "drain-timeout",
28802
+ "failed"
28803
+ ]);
28804
+ var NodeBenchmarkRunSchema = object({
28805
+ runId: string(),
28806
+ nodeId: string(),
28807
+ state: NodeBenchmarkStateSchema,
28808
+ plan: NodeBenchmarkPlanSchema,
28809
+ startedAtMs: number(),
28810
+ deadlineAtMs: number(),
28811
+ finishedAtMs: number().nullable(),
28812
+ endReason: NodeBenchmarkEndReasonSchema.nullable(),
28813
+ error: string().nullable(),
28814
+ /** Cameras moved off the node for the run. */
28815
+ moved: array(number()).readonly(),
28816
+ /** Cameras that stayed (their only frame source is the node). */
28817
+ staying: array(number()).readonly(),
28818
+ /** Something else ran on the node's pools during the run. */
28819
+ contaminated: boolean(),
28820
+ progress: NodeBenchProgressSchema.nullable(),
28821
+ resultIds: array(string()).readonly()
28822
+ });
28823
+ /** `getNodeBenchmarkStatus`: the active run (or the last one), and whether benchmark mode is on. */
28824
+ var NodeBenchmarkStatusSchema = object({
28825
+ /** The node the cluster is in benchmark mode for; null = none. */
28826
+ modeNodeId: string().nullable(),
28827
+ run: NodeBenchmarkRunSchema.nullable()
28828
+ });
28829
+ /**
28830
+ * One stored measurement — the only authority Auto tune and the automatic
28831
+ * shares read measured capacity from. Written by the orchestrator when a run
28832
+ * ends, never by anyone else.
28833
+ */
28834
+ var NodeBenchmarkResultSchema = object({
28835
+ id: string(),
28836
+ runId: string(),
28837
+ nodeId: string(),
28838
+ cell: NodeBenchCellSchema,
28839
+ fpsPerStream: number(),
28840
+ fingerprint: NodeBenchFingerprintSchema.nullable(),
28841
+ clip: NodeBenchClipSchema.nullable(),
28842
+ /** Other work shared the node's pools (a camera that could not move, or the hub's own duties). */
28843
+ contaminated: boolean(),
28844
+ stayingCameras: array(number()).readonly(),
28845
+ /** The run ended early; the cell itself completed. */
28846
+ partialRun: boolean(),
28847
+ measuredAtMs: number()
28848
+ });
28849
+ var ListNodeBenchmarkResultsInputSchema = object({
28850
+ nodeId: string().optional(),
28851
+ deviceKey: string().optional()
28852
+ });
28853
+ /** A result with its standing today: does it still beat the table? */
28854
+ var NodeBenchmarkResultViewSchema = NodeBenchmarkResultSchema.extend({
28855
+ /** `fresh` beats the table; `stale` falls back to it; `superseded` = a newer row covers the same cell. */
28856
+ standing: _enum([
28857
+ "fresh",
28858
+ "stale",
28859
+ "superseded"
28860
+ ]),
28861
+ staleWhy: _enum([
28862
+ "age",
28863
+ "runtime",
28864
+ "pool-tuning"
28865
+ ]).nullable()
28866
+ });
28867
+ object({
28868
+ runId: string(),
28869
+ nodeId: string(),
28870
+ startedAtMs: number(),
28871
+ deadlineAtMs: number(),
28872
+ plan: NodeBenchmarkPlanSchema,
28873
+ moved: array(number()).readonly(),
28874
+ staying: array(number()).readonly()
28875
+ });
28876
+ /** Candidate detectors per device, for the plan editor. */
28877
+ var NodeBenchmarkCandidatesSchema = object({
28878
+ nodeId: string(),
28879
+ reachable: boolean(),
28880
+ devices: array(object({
28881
+ deviceKey: string(),
28882
+ enabled: boolean(),
28883
+ available: boolean(),
28884
+ /** Ordered: what Auto tune would pick first, then the rest of the family by accuracy. */
28885
+ suggested: array(string()).readonly(),
28886
+ /** Every root detector the device has a build for. */
28887
+ all: array(string()).readonly(),
28888
+ /** The model the device runs now, when pinned. */
28889
+ currentModelId: string().nullable()
28890
+ })).readonly(),
28891
+ /** Detail steps the node's catalog offers under the root (for the `pipeline` measurement). */
28892
+ steps: array(object({
28893
+ id: string(),
28894
+ name: string(),
28895
+ slot: string()
28896
+ })).readonly()
28897
+ });
27332
28898
  object({
27333
28899
  activeCameras: number(),
27334
28900
  throttledCameras: number(),
@@ -28607,7 +30173,30 @@ var pipelineRunnerCapability = {
28607
30173
  releaseParkedTrackFrames: method(object({
28608
30174
  deviceId: number(),
28609
30175
  trackId: string()
28610
- }), object({ released: number().int().nonnegative() }), { kind: "mutation" })
30176
+ }), object({ released: number().int().nonnegative() }), { kind: "mutation" }),
30177
+ /**
30178
+ * Start measuring THIS node for the orchestrator's node benchmark: the
30179
+ * bench clip decoded once into RAM, each device × candidate detector on an
30180
+ * open-loop ramp (`device`), then the chosen pipeline with the requested
30181
+ * detail steps (`pipeline`). Everything runs in-process with the executor
30182
+ * — no frame crosses a node (D9) — and writes nothing: no track, event,
30183
+ * media or notification. Reached by `nodePin(nodeId)`. Answers at once;
30184
+ * one run per node; the run stops itself at `deadlineAtMs`.
30185
+ */
30186
+ startNodeBench: method(NodeBenchRunnerInputSchema, object({
30187
+ accepted: literal(true),
30188
+ runId: string()
30189
+ }), {
30190
+ kind: "mutation",
30191
+ auth: "admin"
30192
+ }),
30193
+ /** The run's progress and every finished cell; null when this runner does not know `runId`. */
30194
+ getNodeBenchStatus: method(object({ runId: string() }), NodeBenchRunnerStatusSchema.nullable(), { auth: "admin" }),
30195
+ /** Stop the run; what it finished is kept. */
30196
+ abortNodeBench: method(object({ runId: string() }), object({ aborted: boolean() }), {
30197
+ kind: "mutation",
30198
+ auth: "admin"
30199
+ })
28611
30200
  }
28612
30201
  };
28613
30202
  var CameraPipelineConfigSchema = object({
@@ -29248,6 +30837,21 @@ method(object({
29248
30837
  }), method(PlacementStatsInputSchema, PlacementStatsSchema, { auth: "admin" }), method(SuggestSharesInputSchema, SuggestSharesResultSchema, { auth: "admin" }), method(_void(), EffectiveSharesSchema, { auth: "admin" }), method(SetShareSourceInputSchema, object({ success: literal(true) }), {
29249
30838
  kind: "mutation",
29250
30839
  auth: "admin"
30840
+ }), method(NodeBenchmarkPlanSchema, NodeBenchmarkPreflightSchema, { auth: "admin" }), method(NodeBenchmarkPlanSchema, discriminatedUnion("kind", [object({
30841
+ kind: literal("started"),
30842
+ runId: string()
30843
+ }), object({
30844
+ kind: literal("refused"),
30845
+ preflight: NodeBenchmarkPreflightSchema
30846
+ })]), {
30847
+ kind: "mutation",
30848
+ auth: "admin"
30849
+ }), method(_void(), NodeBenchmarkStatusSchema, { auth: "admin" }), method(object({ runId: string() }), object({ aborted: boolean() }), {
30850
+ kind: "mutation",
30851
+ auth: "admin"
30852
+ }), method(object({ nodeId: string() }), NodeBenchmarkCandidatesSchema, { auth: "admin" }), method(ListNodeBenchmarkResultsInputSchema, array(NodeBenchmarkResultViewSchema).readonly(), { auth: "admin" }), method(object({ id: string() }), object({ deleted: boolean() }), {
30853
+ kind: "mutation",
30854
+ auth: "admin"
29251
30855
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
29252
30856
  deviceId: number(),
29253
30857
  addonId: string(),
@@ -29884,7 +31488,19 @@ var SsoBridgeClaimsSchema = object({
29884
31488
  * editing a descriptor must not retroactively extend or shorten a link the
29885
31489
  * operator already consented to.
29886
31490
  */
29887
- refreshTtl: union([number().int().positive(), literal("never")]).optional()
31491
+ refreshTtl: union([number().int().positive(), literal("never")]).optional(),
31492
+ /**
31493
+ * Opaque handle of a hub-side LINK intent (D782). Set by an OIDC callback
31494
+ * that was started from a signed-in user's "Link" button: the finish then
31495
+ * links this identity to the intent's user instead of signing anyone in.
31496
+ * The handle carries no user id — the hub resolves it, single-use.
31497
+ */
31498
+ linkIntent: string().optional(),
31499
+ /**
31500
+ * Verified `iss` of the id_token behind an OIDC login bridge (D782). A
31501
+ * subject is unique only per issuer, so a link is bound to it.
31502
+ */
31503
+ issuer: string().optional()
29888
31504
  });
29889
31505
  method(object({
29890
31506
  claims: SsoBridgeClaimsSchema,
@@ -34813,6 +36429,9 @@ var CreateIntegrationInputSchema = object({
34813
36429
  enabled: boolean().optional(),
34814
36430
  info: record(string(), unknown()).optional(),
34815
36431
  settings: record(string(), unknown()).optional(),
36432
+ /** Keys of `settings` the form's schema declares secret (by type) — kept in
36433
+ * the keychain, not the table (D787). */
36434
+ secretKeys: array(string()).optional(),
34816
36435
  skipRestart: boolean().optional()
34817
36436
  });
34818
36437
  var UpdateIntegrationInputSchema = object({
@@ -34836,7 +36455,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
34836
36455
  auth: "admin"
34837
36456
  }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
34838
36457
  id: string(),
34839
- settings: record(string(), unknown())
36458
+ settings: record(string(), unknown()),
36459
+ /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
36460
+ secretKeys: array(string()).optional()
34840
36461
  }), object({ success: boolean() }), {
34841
36462
  kind: "mutation",
34842
36463
  auth: "admin"
@@ -40865,6 +42486,32 @@ var TotpStatusSchema = object({
40865
42486
  /** Null when no row exists OR the row is still pending confirmation. */
40866
42487
  confirmedAt: number().nullable()
40867
42488
  });
42489
+ /**
42490
+ * user-management — singleton capability for user accounts, API keys,
42491
+ * and scoped tokens.
42492
+ *
42493
+ * v2 model: every method gated by `auth: 'admin'`. Admin-only delegation
42494
+ * — the subset check on `setUserScopes` / `createScopedToken` is moot
42495
+ * because non-admins never reach these. If/when we open non-admin
42496
+ * delegation, structural caller-scope subset check goes inside the
42497
+ * provider.
42498
+ */
42499
+ var ExternalIdentitySchema = object({
42500
+ /** Login-method id of the provider instance, e.g. `auth-oidc/authentik`. */
42501
+ provider: string(),
42502
+ /** The IdP's stable subject (`sub`) for this account. */
42503
+ subject: string(),
42504
+ /** The verified `iss` of the IdP the identity was linked under — `sub` is unique only per issuer. */
42505
+ issuer: string(),
42506
+ userId: string(),
42507
+ /** What the IdP called the account at link time (username / email) — display only. */
42508
+ label: string().optional(),
42509
+ linkedAt: number()
42510
+ });
42511
+ var ExternalIdentityKeySchema = object({
42512
+ provider: string().min(1),
42513
+ subject: string().min(1)
42514
+ });
40868
42515
  method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
40869
42516
  kind: "mutation",
40870
42517
  auth: "admin",
@@ -40978,7 +42625,22 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
40978
42625
  userId: string(),
40979
42626
  username: string(),
40980
42627
  scopes: array(TokenScopeSchema)
40981
- }).nullable(), { access: "view" }), method(_void(), array(OauthSessionSummarySchema), { auth: "admin" }), method(object({ id: string() }), object({ success: boolean() }), {
42628
+ }).nullable(), { access: "view" }), method(object({ userId: string() }), array(ExternalIdentitySchema), { auth: "admin" }), method(ExternalIdentityKeySchema, object({
42629
+ userId: string(),
42630
+ issuer: string()
42631
+ }).nullable(), { auth: "admin" }), method(ExternalIdentityKeySchema.extend({
42632
+ userId: string(),
42633
+ issuer: string().min(1),
42634
+ label: string().optional()
42635
+ }), object({ success: literal(true) }), {
42636
+ kind: "mutation",
42637
+ auth: "admin",
42638
+ access: "create"
42639
+ }), method(ExternalIdentityKeySchema.extend({ userId: string() }), object({ success: boolean() }), {
42640
+ kind: "mutation",
42641
+ auth: "admin",
42642
+ access: "delete"
42643
+ }), method(_void(), array(OauthSessionSummarySchema), { auth: "admin" }), method(object({ id: string() }), object({ success: boolean() }), {
40982
42644
  kind: "mutation",
40983
42645
  auth: "admin",
40984
42646
  access: "delete"
@@ -44040,6 +45702,90 @@ Object.freeze({
44040
45702
  addonId: null,
44041
45703
  access: "create"
44042
45704
  },
45705
+ "keychain.delete": {
45706
+ capName: "keychain",
45707
+ capScope: "system",
45708
+ addonId: null,
45709
+ access: "delete"
45710
+ },
45711
+ "keychain.getState": {
45712
+ capName: "keychain",
45713
+ capScope: "system",
45714
+ addonId: null,
45715
+ access: "view"
45716
+ },
45717
+ "keychain.listMeta": {
45718
+ capName: "keychain",
45719
+ capScope: "system",
45720
+ addonId: null,
45721
+ access: "view"
45722
+ },
45723
+ "keychain.put": {
45724
+ capName: "keychain",
45725
+ capScope: "system",
45726
+ addonId: null,
45727
+ access: "create"
45728
+ },
45729
+ "keychain.resolve": {
45730
+ capName: "keychain",
45731
+ capScope: "system",
45732
+ addonId: null,
45733
+ access: "view"
45734
+ },
45735
+ "keychainAdmin.deleteOrphans": {
45736
+ capName: "keychain-admin",
45737
+ capScope: "system",
45738
+ addonId: null,
45739
+ access: "delete"
45740
+ },
45741
+ "keychainAdmin.downloadRecoveryKey": {
45742
+ capName: "keychain-admin",
45743
+ capScope: "system",
45744
+ addonId: null,
45745
+ access: "create"
45746
+ },
45747
+ "keychainAdmin.listAudit": {
45748
+ capName: "keychain-admin",
45749
+ capScope: "system",
45750
+ addonId: null,
45751
+ access: "view"
45752
+ },
45753
+ "keychainAdmin.listSecrets": {
45754
+ capName: "keychain-admin",
45755
+ capScope: "system",
45756
+ addonId: null,
45757
+ access: "view"
45758
+ },
45759
+ "keychainAdmin.purgeRetiredKeys": {
45760
+ capName: "keychain-admin",
45761
+ capScope: "system",
45762
+ addonId: null,
45763
+ access: "delete"
45764
+ },
45765
+ "keychainAdmin.resetKeychain": {
45766
+ capName: "keychain-admin",
45767
+ capScope: "system",
45768
+ addonId: null,
45769
+ access: "delete"
45770
+ },
45771
+ "keychainAdmin.reveal": {
45772
+ capName: "keychain-admin",
45773
+ capScope: "system",
45774
+ addonId: null,
45775
+ access: "create"
45776
+ },
45777
+ "keychainAdmin.rotateMasterKey": {
45778
+ capName: "keychain-admin",
45779
+ capScope: "system",
45780
+ addonId: null,
45781
+ access: "create"
45782
+ },
45783
+ "keychainAdmin.status": {
45784
+ capName: "keychain-admin",
45785
+ capScope: "system",
45786
+ addonId: null,
45787
+ access: "view"
45788
+ },
44043
45789
  "lawnMowerControl.dock": {
44044
45790
  capName: "lawn-mower-control",
44045
45791
  capScope: "device",
@@ -45414,6 +47160,18 @@ Object.freeze({
45414
47160
  addonId: null,
45415
47161
  access: "create"
45416
47162
  },
47163
+ "pipelineAnalytics.addRetrainFrameFromRecording": {
47164
+ capName: "pipeline-analytics",
47165
+ capScope: "device",
47166
+ addonId: null,
47167
+ access: "create"
47168
+ },
47169
+ "pipelineAnalytics.addRetrainFramesToCollection": {
47170
+ capName: "pipeline-analytics",
47171
+ capScope: "device",
47172
+ addonId: null,
47173
+ access: "create"
47174
+ },
45417
47175
  "pipelineAnalytics.applyDeviceSettingsPatch": {
45418
47176
  capName: "pipeline-analytics",
45419
47177
  capScope: "device",
@@ -45462,12 +47220,24 @@ Object.freeze({
45462
47220
  addonId: null,
45463
47221
  access: "view"
45464
47222
  },
47223
+ "pipelineAnalytics.createRetrainCollection": {
47224
+ capName: "pipeline-analytics",
47225
+ capScope: "device",
47226
+ addonId: null,
47227
+ access: "create"
47228
+ },
45465
47229
  "pipelineAnalytics.deleteDeviceEvents": {
45466
47230
  capName: "pipeline-analytics",
45467
47231
  capScope: "device",
45468
47232
  addonId: null,
45469
47233
  access: "delete"
45470
47234
  },
47235
+ "pipelineAnalytics.deleteRetrainCollection": {
47236
+ capName: "pipeline-analytics",
47237
+ capScope: "device",
47238
+ addonId: null,
47239
+ access: "delete"
47240
+ },
45471
47241
  "pipelineAnalytics.deleteTracks": {
45472
47242
  capName: "pipeline-analytics",
45473
47243
  capScope: "device",
@@ -45672,6 +47442,24 @@ Object.freeze({
45672
47442
  addonId: null,
45673
47443
  access: "view"
45674
47444
  },
47445
+ "pipelineAnalytics.listRetrainAssistProfiles": {
47446
+ capName: "pipeline-analytics",
47447
+ capScope: "device",
47448
+ addonId: null,
47449
+ access: "view"
47450
+ },
47451
+ "pipelineAnalytics.listRetrainCollectionFrames": {
47452
+ capName: "pipeline-analytics",
47453
+ capScope: "device",
47454
+ addonId: null,
47455
+ access: "view"
47456
+ },
47457
+ "pipelineAnalytics.listRetrainCollections": {
47458
+ capName: "pipeline-analytics",
47459
+ capScope: "device",
47460
+ addonId: null,
47461
+ access: "view"
47462
+ },
45675
47463
  "pipelineAnalytics.listRetrainFrames": {
45676
47464
  capName: "pipeline-analytics",
45677
47465
  capScope: "device",
@@ -45720,6 +47508,12 @@ Object.freeze({
45720
47508
  addonId: null,
45721
47509
  access: "create"
45722
47510
  },
47511
+ "pipelineAnalytics.proposeRetrainAnnotationsWithLlm": {
47512
+ capName: "pipeline-analytics",
47513
+ capScope: "device",
47514
+ addonId: null,
47515
+ access: "create"
47516
+ },
45723
47517
  "pipelineAnalytics.pruneEvents": {
45724
47518
  capName: "pipeline-analytics",
45725
47519
  capScope: "device",
@@ -45774,6 +47568,18 @@ Object.freeze({
45774
47568
  addonId: null,
45775
47569
  access: "create"
45776
47570
  },
47571
+ "pipelineAnalytics.removeRetrainFrameFromCollection": {
47572
+ capName: "pipeline-analytics",
47573
+ capScope: "device",
47574
+ addonId: null,
47575
+ access: "delete"
47576
+ },
47577
+ "pipelineAnalytics.renameRetrainCollection": {
47578
+ capName: "pipeline-analytics",
47579
+ capScope: "device",
47580
+ addonId: null,
47581
+ access: "create"
47582
+ },
45777
47583
  "pipelineAnalytics.restageRetrainTrack": {
45778
47584
  capName: "pipeline-analytics",
45779
47585
  capScope: "device",
@@ -45804,6 +47610,12 @@ Object.freeze({
45804
47610
  addonId: null,
45805
47611
  access: "create"
45806
47612
  },
47613
+ "pipelineAnalytics.setRetrainFrameComplete": {
47614
+ capName: "pipeline-analytics",
47615
+ capScope: "device",
47616
+ addonId: null,
47617
+ access: "create"
47618
+ },
45807
47619
  "pipelineAnalytics.setTrackFlags": {
45808
47620
  capName: "pipeline-analytics",
45809
47621
  capScope: "device",
@@ -45840,6 +47652,12 @@ Object.freeze({
45840
47652
  addonId: null,
45841
47653
  access: "delete"
45842
47654
  },
47655
+ "pipelineAnalytics.writeRetrainArchive": {
47656
+ capName: "pipeline-analytics",
47657
+ capScope: "device",
47658
+ addonId: null,
47659
+ access: "create"
47660
+ },
45843
47661
  "pipelineExecutor.cacheFrameInPool": {
45844
47662
  capName: "pipeline-executor",
45845
47663
  capScope: "system",
@@ -46050,6 +47868,12 @@ Object.freeze({
46050
47868
  addonId: null,
46051
47869
  access: "view"
46052
47870
  },
47871
+ "pipelineOrchestrator.abortNodeBenchmark": {
47872
+ capName: "pipeline-orchestrator",
47873
+ capScope: "system",
47874
+ addonId: null,
47875
+ access: "create"
47876
+ },
46053
47877
  "pipelineOrchestrator.applyAutoTune": {
46054
47878
  capName: "pipeline-orchestrator",
46055
47879
  capScope: "system",
@@ -46074,6 +47898,12 @@ Object.freeze({
46074
47898
  addonId: null,
46075
47899
  access: "create"
46076
47900
  },
47901
+ "pipelineOrchestrator.deleteNodeBenchmarkResult": {
47902
+ capName: "pipeline-orchestrator",
47903
+ capScope: "system",
47904
+ addonId: null,
47905
+ access: "delete"
47906
+ },
46077
47907
  "pipelineOrchestrator.getAgentLoad": {
46078
47908
  capName: "pipeline-orchestrator",
46079
47909
  capScope: "system",
@@ -46182,6 +48012,24 @@ Object.freeze({
46182
48012
  addonId: null,
46183
48013
  access: "view"
46184
48014
  },
48015
+ "pipelineOrchestrator.getNodeBenchmarkCandidates": {
48016
+ capName: "pipeline-orchestrator",
48017
+ capScope: "system",
48018
+ addonId: null,
48019
+ access: "view"
48020
+ },
48021
+ "pipelineOrchestrator.getNodeBenchmarkPreflight": {
48022
+ capName: "pipeline-orchestrator",
48023
+ capScope: "system",
48024
+ addonId: null,
48025
+ access: "view"
48026
+ },
48027
+ "pipelineOrchestrator.getNodeBenchmarkStatus": {
48028
+ capName: "pipeline-orchestrator",
48029
+ capScope: "system",
48030
+ addonId: null,
48031
+ access: "view"
48032
+ },
46185
48033
  "pipelineOrchestrator.getNodeInferenceDevices": {
46186
48034
  capName: "pipeline-orchestrator",
46187
48035
  capScope: "system",
@@ -46224,6 +48072,12 @@ Object.freeze({
46224
48072
  addonId: null,
46225
48073
  access: "view"
46226
48074
  },
48075
+ "pipelineOrchestrator.listNodeBenchmarkResults": {
48076
+ capName: "pipeline-orchestrator",
48077
+ capScope: "system",
48078
+ addonId: null,
48079
+ access: "view"
48080
+ },
46227
48081
  "pipelineOrchestrator.pauseForStorageMigration": {
46228
48082
  capName: "pipeline-orchestrator",
46229
48083
  capScope: "system",
@@ -46368,6 +48222,12 @@ Object.freeze({
46368
48222
  addonId: null,
46369
48223
  access: "create"
46370
48224
  },
48225
+ "pipelineOrchestrator.startNodeBenchmark": {
48226
+ capName: "pipeline-orchestrator",
48227
+ capScope: "system",
48228
+ addonId: null,
48229
+ access: "create"
48230
+ },
46371
48231
  "pipelineOrchestrator.suggestShares": {
46372
48232
  capName: "pipeline-orchestrator",
46373
48233
  capScope: "system",
@@ -46386,6 +48246,12 @@ Object.freeze({
46386
48246
  addonId: null,
46387
48247
  access: "create"
46388
48248
  },
48249
+ "pipelineRunner.abortNodeBench": {
48250
+ capName: "pipeline-runner",
48251
+ capScope: "system",
48252
+ addonId: null,
48253
+ access: "create"
48254
+ },
46389
48255
  "pipelineRunner.attachCamera": {
46390
48256
  capName: "pipeline-runner",
46391
48257
  capScope: "system",
@@ -46434,6 +48300,12 @@ Object.freeze({
46434
48300
  addonId: null,
46435
48301
  access: "view"
46436
48302
  },
48303
+ "pipelineRunner.getNodeBenchStatus": {
48304
+ capName: "pipeline-runner",
48305
+ capScope: "system",
48306
+ addonId: null,
48307
+ access: "view"
48308
+ },
46437
48309
  "pipelineRunner.getParkedTrackFrame": {
46438
48310
  capName: "pipeline-runner",
46439
48311
  capScope: "system",
@@ -46476,6 +48348,12 @@ Object.freeze({
46476
48348
  addonId: null,
46477
48349
  access: "create"
46478
48350
  },
48351
+ "pipelineRunner.startNodeBench": {
48352
+ capName: "pipeline-runner",
48353
+ capScope: "system",
48354
+ addonId: null,
48355
+ access: "create"
48356
+ },
46479
48357
  "plateGallery.assignPlate": {
46480
48358
  capName: "plate-gallery",
46481
48359
  capScope: "system",
@@ -48192,18 +50070,36 @@ Object.freeze({
48192
50070
  addonId: null,
48193
50071
  access: "delete"
48194
50072
  },
50073
+ "userManagement.findUserByExternalIdentity": {
50074
+ capName: "user-management",
50075
+ capScope: "system",
50076
+ addonId: null,
50077
+ access: "view"
50078
+ },
48195
50079
  "userManagement.getTotpStatus": {
48196
50080
  capName: "user-management",
48197
50081
  capScope: "system",
48198
50082
  addonId: null,
48199
50083
  access: "view"
48200
50084
  },
50085
+ "userManagement.linkExternalIdentity": {
50086
+ capName: "user-management",
50087
+ capScope: "system",
50088
+ addonId: null,
50089
+ access: "create"
50090
+ },
48201
50091
  "userManagement.listApiKeys": {
48202
50092
  capName: "user-management",
48203
50093
  capScope: "system",
48204
50094
  addonId: null,
48205
50095
  access: "view"
48206
50096
  },
50097
+ "userManagement.listExternalIdentities": {
50098
+ capName: "user-management",
50099
+ capScope: "system",
50100
+ addonId: null,
50101
+ access: "view"
50102
+ },
48207
50103
  "userManagement.listOauthSessions": {
48208
50104
  capName: "user-management",
48209
50105
  capScope: "system",
@@ -48282,6 +50178,12 @@ Object.freeze({
48282
50178
  addonId: null,
48283
50179
  access: "create"
48284
50180
  },
50181
+ "userManagement.unlinkExternalIdentity": {
50182
+ capName: "user-management",
50183
+ capScope: "system",
50184
+ addonId: null,
50185
+ access: "delete"
50186
+ },
48285
50187
  "userManagement.updateUser": {
48286
50188
  capName: "user-management",
48287
50189
  capScope: "system",
@@ -51381,6 +53283,11 @@ Object.freeze({
51381
53283
  form: "single",
51382
53284
  optional: false
51383
53285
  }],
53286
+ "pipelineAnalytics.addRetrainFrameFromRecording": [{
53287
+ name: "deviceId",
53288
+ form: "single",
53289
+ optional: false
53290
+ }],
51384
53291
  "pipelineAnalytics.applyDeviceSettingsPatch": [{
51385
53292
  name: "deviceId",
51386
53293
  form: "single",
@@ -51571,6 +53478,11 @@ Object.freeze({
51571
53478
  form: "single",
51572
53479
  optional: false
51573
53480
  }],
53481
+ "pipelineAnalytics.proposeRetrainAnnotationsWithLlm": [{
53482
+ name: "deviceId",
53483
+ form: "single",
53484
+ optional: false
53485
+ }],
51574
53486
  "pipelineAnalytics.pruneEventsBefore": [{
51575
53487
  name: "deviceId",
51576
53488
  form: "single",
@@ -51621,6 +53533,11 @@ Object.freeze({
51621
53533
  form: "single",
51622
53534
  optional: false
51623
53535
  }],
53536
+ "pipelineAnalytics.setRetrainFrameComplete": [{
53537
+ name: "deviceId",
53538
+ form: "single",
53539
+ optional: false
53540
+ }],
51624
53541
  "pipelineAnalytics.setTrackFlags": [{
51625
53542
  name: "deviceId",
51626
53543
  form: "single",
@@ -53627,6 +55544,73 @@ function collectModelBoundKeys(fields) {
53627
55544
  return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
53628
55545
  });
53629
55546
  }
55547
+ /** Ramp steps the runner tries per cell, at most. */
55548
+ var DEVICE_RAMP = [
55549
+ 1,
55550
+ 2,
55551
+ 4,
55552
+ 6,
55553
+ 8,
55554
+ 12,
55555
+ 16,
55556
+ 24,
55557
+ 32
55558
+ ];
55559
+ var PIPELINE_RAMP = [
55560
+ 1,
55561
+ 2,
55562
+ 3,
55563
+ 4,
55564
+ 6,
55565
+ 8,
55566
+ 10,
55567
+ 12,
55568
+ 16
55569
+ ];
55570
+ var BENCH_ROOT_STEP_ID = "object-detection";
55571
+ function slotAddons(schema, slot) {
55572
+ return schema.slots.find((s) => s.id === slot)?.addons ?? [];
55573
+ }
55574
+ var feeds = (from, to) => to.inputClasses.some((c) => from.outputClasses.includes(c));
55575
+ function layout(schema) {
55576
+ const root = slotAddons(schema, "detector").find((a) => a.id === BENCH_ROOT_STEP_ID);
55577
+ if (root === void 0) return null;
55578
+ const classifiers = slotAddons(schema, "classifier");
55579
+ const croppers = slotAddons(schema, "cropper").filter((c) => feeds(root, c)).map((cropper) => ({
55580
+ cropper,
55581
+ recognizers: classifiers.filter((k) => feeds(cropper, k))
55582
+ }));
55583
+ const underCropper = new Set(croppers.flatMap((c) => c.recognizers.map((r) => r.id)));
55584
+ return {
55585
+ root,
55586
+ croppers,
55587
+ classifiers: classifiers.filter((k) => feeds(root, k) && !underCropper.has(k.id)),
55588
+ refiners: slotAddons(schema, "refiner")
55589
+ };
55590
+ }
55591
+ /** Build the tree. Throws when the node's catalog has no object-detection root. */
55592
+ function buildBenchStepTree(schema, rootModelId, enabledStepIds) {
55593
+ const l = layout(schema);
55594
+ if (l === null) throw new Error(`the node's catalog has no ${BENCH_ROOT_STEP_ID} step`);
55595
+ const on = new Set(enabledStepIds);
55596
+ const step = (a, enabled) => ({
55597
+ addonId: a.id,
55598
+ enabled
55599
+ });
55600
+ return [{
55601
+ addonId: l.root.id,
55602
+ modelId: rootModelId,
55603
+ enabled: true,
55604
+ children: [
55605
+ ...l.croppers.map(({ cropper, recognizers }) => ({
55606
+ ...step(cropper, on.has(cropper.id) || recognizers.some((r) => on.has(r.id))),
55607
+ children: recognizers.map((r) => step(r, on.has(r.id)))
55608
+ })),
55609
+ ...l.classifiers.map((k) => step(k, on.has(k.id))),
55610
+ ...l.refiners.map((r) => step(r, on.has(r.id)))
55611
+ ]
55612
+ }];
55613
+ }
53630
55614
  /** Side of the square, as a multiple of the face box's longer side. */
53631
55615
  var ROOT_FACE_REFINE_SCALE = 1.6;
53632
55616
  /**
@@ -54845,4 +56829,4 @@ function enumerateInferenceDevices(hw) {
54845
56829
  return out;
54846
56830
  }
54847
56831
  //#endregion
54848
- export { cameraOwnsMotionViaSensor as $, preprocess as $n, recordingSignalCapability as $t, NativeLeaseAdmissionSchema as A, CAM_PROFILE_ORDER as An, linkedStepOf as At, RecordingConfigSchema as B, makeSourceBrokerId as Bn, modelBindingKey as Bt, EncodeProfileSchema as C, invocationFromEncodeProfile as Cn, gbToBytes as Ct, HF_REPO as D, errMsg as Dn, inferModelProvider as Dt, G711_SCALE_CORRECTION_DB as E, DeviceType as En, hfModelUrl as Et, RATE_CONTROL_TIGHT as F, defineCustomActions as Fn, mayReadLocation as Ft, YAMNET_TO_MACRO as G, _enum as Gn, pickDetailCropConvention as Gt, RingBuffer as H, parseProfileBrokerId as Hn, overlayClusterStepSettings as Ht, RECORDING_EXPORT_MAX_READ_BYTES as I, expandAudioChunkToF32le as In, mayWriteToLocation as It, applyModelBoundSettings as J, discriminatedUnion as Jn, pipelineRunnerCapability as Jt, addonWidgetsSourceCapability as K, array as Kn, pickNativeLeaseOverride as Kt, RECORDING_SOURCE_CAMSTACK as L, hydrateSchema as Ln, memoizePinnedFfmpeg as Lt, OpsLogEntrySchema as M, audioChunkBytesPerSample as Mn, logChannelsCapability as Mt, PoolMemoryWatchdog as N, createEvent as Nn, mapAudioLabelToMacro as Nt, LABEL_SUPPLY_MACRO as O, BOOT_RECOVERY_BACKOFF_MS as On, isFirstLevelMacroClass as Ot, RATE_CONTROL_RELAXED as P, customAction as Pn, maskUrlCredentials as Pt, batteryCapability as Q, object as Qn, recordingExportCapability as Qt, RECORDING_SOURCE_CAMSTACK_ADDON as R, isEvent as Rn, migrateLegacyDeviceSignalConfig as Rt, EVENT_PAD_MS as S, evaluateSensorEdge as Sn, ffmpegRate as St, FailureCounters as T, DeviceFeature as Tn, gifFromMp4Args as Tt, VIDEOCLIPS_MAX_OFFER_BYTES as U, selectAssignedProfileSlots as Un, parseProcStatus as Ut, RecordingSignalStatusSchema as V, nodePin as Vn, motionDetectionCapability as Vt, VISIT_MERGE_GAP_MS as W, sleep as Wn, pickClusterStepSettings as Wt, audioAnalyzerCapability as X, literal as Xn, recordingArchiveCapability as Xt, audioAnalysisCapability as Y, lazy as Yn, rankStreams as Yt, audioKindId as Z, number as Zn, recordingCapability as Zt, DEFAULT_DETAIL_CROP_CONVENTION as _, webrtcSessionCapability as _n, evaluateZoneRules as _t, BackgroundRunRegistry as a, resolvePoolMemoryPolicy as an, createHwAccelCache as at, DEVICE_BACKEND_TO_FORMAT as b, Fmp4BoxSplitter as bn, exportSubjectOf as bt, CLIP_STILL_REASON_CODE_HEADER as c, rootSuppliesOf as cn, defaultDeviceFor as ct, CLUSTER_MODEL_SCOPED_STEPS as d, storageEvictableCapability as dn, deriveRecordingMode as dt, resolveClusterStepModelId as en, record as er, cameraStreamsCapability as et, COCO_80_LABELS as f, storageOccupancyCapability as fn, deriveRootFaceRefineRect as ft, DEFAULT_CLUSTER_STEP_SETTINGS as g, videoclipsCapability as gn, enumerateInferenceDevices as gt, DEFAULT_AUDIO_ANALYZER_CONFIG as h, supportedRuntimes as hn, egressTransportFromRequest as ht, AUDIO_MACRO_LABELS as i, resolveModelBoundSetting as in, containingParentIndex as it, NativeLeaseSettingsSchema as j, CamProfileSchema as jn, loadContributionCapability as jt, LABEL_SUPPLY_VOCABULARIES as k, BaseAddon as kn, linkedLabelStepId as kt, CLIP_STILL_REASON_HEADER as l, runtimeDevices as ln, deriveBatteryPresence as lt, CROP_SUPPLY_SOURCES as m, stripRetiredBandPreBufferSec as mn, egressTranscodeSharingKey as mt, APPLE_SA_TO_MACRO as n, resolveEgressDecodeHwAccel as nn, union as nr, clipExportFailureOf as nt, BatteryStatusSchema as o, resolveRecordingProfiles as on, createLogChannelsProvider as ot, COCO_TO_MACRO as p, streamBrokerCapability as pn, detectionPipelineCapability as pt, allowedSuppliedEffect as q, boolean as qn, pipelineExecutorCapability as qt, AUDIO_BACKEND_CHOICES as r, resolveLocationMode as rn, EventCategory as rr, collectModelBoundKeys as rt, CLIP_BROKER_PACED_RATES as s, resolveRootSupply as sn, declareLogChannel as st, APPEARANCE_BLOCKS as t, resolveCreateExportSubject as tn, string as tr, clipExportFailure as tt, CLIP_STREAM_TRANSPORT_HEADER as u, stepModelRunsOnFormat as un, deriveDetailCropRect as ut, DEFAULT_EVENTS_BAND_BUFFER_SEC as v, AUDIO_PRESETS as vn, evictionPolicyForMode as vt, ExportRecordSchema as w, isSoftwareDecode as wn, gifDeriveSpeed as wt, EVENT_OWNER_TYPES as x, buildFfmpegArgs as xn, failureContributionCapability as xt, DEFAULT_NATIVE_LEASE_SETTINGS as y, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as yn, evictionPolicyOfLocation as yt, ROOT_SUPPLIABLE_STEP_IDS as z, makeProfileBrokerId as zn, modeMayWrite as zt };
56832
+ export { audioAnalysisCapability as $, discriminatedUnion as $n, pipelineRunnerCapability as $t, LABEL_SUPPLY_VOCABULARIES as A, DeviceFeature as An, gifFromMp4Args as At, RECORDING_EXPORT_MAX_READ_BYTES as B, defineCustomActions as Bn, mayReadLocation as Bt, EVENT_PAD_MS as C, AUDIO_PRESETS as Cn, evictionPolicyForMode as Ct, G711_SCALE_CORRECTION_DB as D, evaluateSensorEdge as Dn, ffmpegRate as Dt, FailureCounters as E, buildFfmpegArgs as En, failureContributionCapability as Et, OpsLogEntrySchema as F, CAM_PROFILE_ORDER as Fn, linkedStepOf as Ft, RecordingSignalStatusSchema as G, makeSourceBrokerId as Gn, modelBindingKey as Gt, RECORDING_SOURCE_CAMSTACK_ADDON as H, hydrateSchema as Hn, memoizePinnedFfmpeg as Ht, PIPELINE_RAMP as I, CamProfileSchema as In, loadContributionCapability as It, VISIT_MERGE_GAP_MS as J, selectAssignedProfileSlots as Jn, parseProcStatus as Jt, RingBuffer as K, nodePin as Kn, motionDetectionCapability as Kt, PoolMemoryWatchdog as L, audioChunkBytesPerSample as Ln, logChannelsCapability as Lt, NativeLeaseAdmissionSchema as M, errMsg as Mn, inferModelProvider as Mt, NativeLeaseSettingsSchema as N, BOOT_RECOVERY_BACKOFF_MS as Nn, isFirstLevelMacroClass as Nt, HF_REPO as O, invocationFromEncodeProfile as On, gbToBytes as Ot, NodeBenchRunnerInputSchema as P, BaseAddon as Pn, linkedLabelStepId as Pt, applyModelBoundSettings as Q, boolean as Qn, pipelineExecutorCapability as Qt, RATE_CONTROL_RELAXED as R, createEvent as Rn, mapAudioLabelToMacro as Rt, EVENT_OWNER_TYPES as S, webrtcSessionCapability as Sn, evaluateZoneRules as St, ExportRecordSchema as T, Fmp4BoxSplitter as Tn, exportSubjectOf as Tt, ROOT_SUPPLIABLE_STEP_IDS as U, isEvent as Un, migrateLegacyDeviceSignalConfig as Ut, RECORDING_SOURCE_CAMSTACK as V, expandAudioChunkToF32le as Vn, mayWriteToLocation as Vt, RecordingConfigSchema as W, makeProfileBrokerId as Wn, modeMayWrite as Wt, addonWidgetsSourceCapability as X, _enum as Xn, pickDetailCropConvention as Xt, YAMNET_TO_MACRO as Y, sleep as Yn, pickClusterStepSettings as Yt, allowedSuppliedEffect as Z, array as Zn, pickNativeLeaseOverride as Zt, DEFAULT_DETAIL_CROP_CONVENTION as _, storageOccupancyCapability as _n, deriveRootFaceRefineRect as _t, BackgroundRunRegistry as a, resolveClusterStepModelId as an, record as ar, cameraStreamsCapability as at, DEVICE_BACKEND_TO_FORMAT as b, supportedRuntimes as bn, egressTransportFromRequest as bt, CLIP_STILL_REASON_CODE_HEADER as c, resolveLocationMode as cn, EventCategory as cr, collectModelBoundKeys as ct, CLUSTER_MODEL_SCOPED_STEPS as d, resolveRecordingProfiles as dn, createLogChannelsProvider as dt, rankStreams as en, lazy as er, audioAnalyzerCapability as et, COCO_80_LABELS as f, resolveRootSupply as fn, declareLogChannel as ft, DEFAULT_CLUSTER_STEP_SETTINGS as g, storageEvictableCapability as gn, deriveRecordingMode as gt, DEFAULT_AUDIO_ANALYZER_CONFIG as h, stepModelRunsOnFormat as hn, deriveDetailCropRect as ht, AUDIO_MACRO_LABELS as i, recordingSignalCapability as in, preprocess as ir, cameraOwnsMotionViaSensor as it, MODEL_FORMATS as j, DeviceType as jn, hfModelUrl as jt, LABEL_SUPPLY_MACRO as k, isSoftwareDecode as kn, gifDeriveSpeed as kt, CLIP_STILL_REASON_HEADER as l, resolveModelBoundSetting as ln, containingParentIndex as lt, CROP_SUPPLY_SOURCES as m, runtimeDevices as mn, deriveBatteryPresence as mt, APPLE_SA_TO_MACRO as n, recordingCapability as nn, number as nr, batteryCapability as nt, BatteryStatusSchema as o, resolveCreateExportSubject as on, string as or, clipExportFailure as ot, COCO_TO_MACRO as p, rootSuppliesOf as pn, defaultDeviceFor as pt, VIDEOCLIPS_MAX_OFFER_BYTES as q, parseProfileBrokerId as qn, overlayClusterStepSettings as qt, AUDIO_BACKEND_CHOICES as r, recordingExportCapability as rn, object as rr, buildBenchStepTree as rt, CLIP_BROKER_PACED_RATES as s, resolveEgressDecodeHwAccel as sn, union as sr, clipExportFailureOf as st, APPEARANCE_BLOCKS as t, recordingArchiveCapability as tn, literal as tr, audioKindId as tt, CLIP_STREAM_TRANSPORT_HEADER as u, resolvePoolMemoryPolicy as un, createHwAccelCache as ut, DEFAULT_EVENTS_BAND_BUFFER_SEC as v, streamBrokerCapability as vn, detectionPipelineCapability as vt, EncodeProfileSchema as w, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as wn, evictionPolicyOfLocation as wt, DEVICE_RAMP as x, videoclipsCapability as xn, enumerateInferenceDevices as xt, DEFAULT_NATIVE_LEASE_SETTINGS as y, stripRetiredBandPreBufferSec as yn, egressTranscodeSharingKey as yt, RATE_CONTROL_TIGHT as z, customAction as zn, maskUrlCredentials as zt };