@camstack/system 1.2.381 → 1.2.382

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 (114) hide show
  1. package/dist/addon-runner.js +4 -2
  2. package/dist/addon-runner.mjs +4 -2
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/auth/admin-token-manager.d.ts +75 -0
  6. package/dist/auth/api-key-manager.d.ts +1 -14
  7. package/dist/auth/auth-audit-log.d.ts +59 -0
  8. package/dist/auth/auth-manager.d.ts +28 -16
  9. package/dist/auth/cidr.d.ts +22 -0
  10. package/dist/auth/login-session-manager.d.ts +158 -0
  11. package/dist/auth/user-management-internal.d.ts +69 -0
  12. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  13. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  14. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  15. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  16. package/dist/builtins/alerts/alerts.addon.js +1 -1
  17. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  18. package/dist/builtins/autotrack/index.js +1 -1
  19. package/dist/builtins/autotrack/index.mjs +1 -1
  20. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +14 -0
  21. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +291 -3631
  22. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +284 -3617
  23. package/dist/builtins/backup-orchestrator/encryption-key-store.d.ts +36 -0
  24. package/dist/builtins/backup-orchestrator/manifest-store.d.ts +19 -1
  25. package/dist/builtins/backup-orchestrator/restore-archive.d.ts +24 -0
  26. package/dist/builtins/calendars/calendars.addon.js +1 -1
  27. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  28. package/dist/builtins/camera-grid/index.js +1 -1
  29. package/dist/builtins/camera-grid/index.mjs +1 -1
  30. package/dist/builtins/composer/composer.addon.js +2 -2
  31. package/dist/builtins/composer/composer.addon.mjs +2 -2
  32. package/dist/builtins/console-logging/index.js +1 -1
  33. package/dist/builtins/console-logging/index.mjs +1 -1
  34. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  35. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  36. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  37. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  38. package/dist/builtins/hub-forwarder/index.js +1 -1
  39. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  40. package/dist/builtins/keychain/index.js +1108 -733
  41. package/dist/builtins/keychain/index.mjs +1108 -733
  42. package/dist/builtins/keychain/keychain-reset-trigger.d.ts +25 -0
  43. package/dist/builtins/keychain/keychain-service.d.ts +36 -3
  44. package/dist/builtins/keychain/keychain-store.d.ts +24 -1
  45. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  46. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  47. package/dist/builtins/local-auth/local-auth.addon.d.ts +16 -0
  48. package/dist/builtins/local-auth/local-auth.addon.js +1021 -118
  49. package/dist/builtins/local-auth/local-auth.addon.mjs +1034 -141
  50. package/dist/builtins/local-network/local-network.addon.js +3 -3
  51. package/dist/builtins/local-network/local-network.addon.mjs +3 -3
  52. package/dist/builtins/loki-logging/index.js +97 -29
  53. package/dist/builtins/loki-logging/index.mjs +97 -29
  54. package/dist/builtins/loki-logging/loki-logging.addon.d.ts +7 -23
  55. package/dist/builtins/loki-logging/retire-enabled-flag.d.ts +23 -0
  56. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  57. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  58. package/dist/builtins/platform-probe/index.js +1 -1
  59. package/dist/builtins/platform-probe/index.mjs +1 -1
  60. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  61. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  62. package/dist/builtins/snapshot/index.js +1 -1
  63. package/dist/builtins/snapshot/index.mjs +1 -1
  64. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  65. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  66. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  67. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  68. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
  69. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
  70. package/dist/builtins/system-backup/backup-encryption.d.ts +57 -0
  71. package/dist/builtins/system-backup/system-backup.service.d.ts +16 -1
  72. package/dist/builtins/system-config/cluster-secret-keeper.d.ts +7 -0
  73. package/dist/builtins/system-config/system-config.addon.d.ts +13 -3
  74. package/dist/builtins/system-config/system-config.addon.js +324 -38
  75. package/dist/builtins/system-config/system-config.addon.mjs +312 -39
  76. package/dist/builtins/winston-logging/index.js +1 -1
  77. package/dist/builtins/winston-logging/index.mjs +1 -1
  78. package/dist/{child-cap-dispatch-hf-Gce_4.js → child-cap-dispatch-BrMdKHfp.js} +6 -3
  79. package/dist/{child-cap-dispatch-BTe4eKep.mjs → child-cap-dispatch-WsYPNvnK.mjs} +10 -7
  80. package/dist/{cluster-secret-BUiG8DCo.js → cluster-secret-BEPLh3LL.js} +1 -1
  81. package/dist/{cluster-secret-DgJTZ4qY.mjs → cluster-secret-DhPdZ0VA.mjs} +1 -1
  82. package/dist/{composition-sources-4SmsP0O4.js → composition-sources-C6ZynCZ2.js} +1 -1
  83. package/dist/{composition-sources-CgOQVIwa.mjs → composition-sources-XqRX51db.mjs} +1 -1
  84. package/dist/{dist-BC0GnGeg.mjs → dist-CP5TTjwd.mjs} +1710 -158
  85. package/dist/{dist-BugrtARh.js → dist-tnAO12ak.js} +1721 -157
  86. package/dist/{event-loop-stall-monitor-CnQ-K6Gn.js → event-loop-stall-monitor-9kR0sAPS.js} +15 -15
  87. package/dist/{event-loop-stall-monitor-GgpnfAC0.mjs → event-loop-stall-monitor-WkssIXjq.mjs} +15 -15
  88. package/dist/{hub-cap-forward-CH6_VWVO.mjs → hub-cap-forward-CDiN1p98.mjs} +18 -2
  89. package/dist/{hub-cap-forward-COCmYaup.js → hub-cap-forward-W8oEm4ME.js} +23 -1
  90. package/dist/index.d.ts +16 -1
  91. package/dist/index.js +3341 -79
  92. package/dist/index.mjs +3567 -354
  93. package/dist/{jwt-signing-key-CsuVKA1W.mjs → jwt-signing-key-CAbozwvF.mjs} +1 -1
  94. package/dist/{jwt-signing-key-CjoZcol8.js → jwt-signing-key-Dgf8sIXY.js} +1 -1
  95. package/dist/kernel/index.d.ts +12 -0
  96. package/dist/kernel/moleculer/broker-factory.d.ts +8 -1
  97. package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +1 -0
  98. package/dist/kernel/moleculer/hub-service.d.ts +9 -0
  99. package/dist/kernel/moleculer/mesh-access-middleware.d.ts +28 -0
  100. package/dist/kernel/moleculer/mesh-auth-mode.d.ts +64 -0
  101. package/dist/kernel/moleculer/mesh-authenticator.d.ts +71 -0
  102. package/dist/kernel/moleculer/mesh-auto-enforce.d.ts +26 -0
  103. package/dist/kernel/moleculer/mesh-join-client.d.ts +51 -0
  104. package/dist/kernel/moleculer/mesh-join-proof.d.ts +39 -0
  105. package/dist/kernel/system-settings-policy.d.ts +4 -3
  106. package/dist/kernel/transport/hub-cap-forward.d.ts +14 -3
  107. package/dist/kernel/transport/index.d.ts +1 -1
  108. package/dist/restore-archive-BV39nY46.mjs +4045 -0
  109. package/dist/restore-archive-CbvrfPuB.js +4139 -0
  110. package/dist/{retired-settings-keys-DCuetwOt.js → retired-settings-keys-4bHpL-0m.js} +1 -1
  111. package/dist/{retired-settings-keys-MCVhxPkJ.mjs → retired-settings-keys-CzpIkwS-.mjs} +1 -1
  112. package/dist/{tls-CJHogwTl.mjs → tls-4cRH24Id.mjs} +3 -3
  113. package/package.json +1 -1
  114. package/dist/{tls-CCMX7MjX.js → tls-CljF057D.js} +1 -1
@@ -708,7 +708,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
708
708
  return EventCategory;
709
709
  }({});
710
710
  //#endregion
711
- //#region ../types/dist/sleep-DujUvOcb.mjs
711
+ //#region ../types/dist/sleep-r3RWkGVx.mjs
712
712
  /**
713
713
  * The audio chunk plane's byte format, and the ONE expansion from a coded
714
714
  * window to float samples (D455).
@@ -820,21 +820,21 @@ function collectSecretConfigKeys(schema) {
820
820
  for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkField(field, keys);
821
821
  return keys;
822
822
  }
823
- function isRecord$2(value) {
823
+ function isRecord$3(value) {
824
824
  return typeof value === "object" && value !== null && !Array.isArray(value);
825
825
  }
826
826
  function sectionsOf(schema) {
827
- if (!isRecord$2(schema)) return [];
827
+ if (!isRecord$3(schema)) return [];
828
828
  const sections = schema["sections"];
829
829
  return Array.isArray(sections) ? sections : [];
830
830
  }
831
831
  function fieldsOf(node) {
832
- if (!isRecord$2(node)) return [];
832
+ if (!isRecord$3(node)) return [];
833
833
  const fields = node["fields"];
834
834
  return Array.isArray(fields) ? fields : [];
835
835
  }
836
836
  function walkField(field, out) {
837
- if (!isRecord$2(field)) return;
837
+ if (!isRecord$3(field)) return;
838
838
  const type = field["type"];
839
839
  const key = field["key"];
840
840
  if ((type === "password" || field["secret"] === true) && typeof key === "string" && key.length > 0) out.add(key);
@@ -872,7 +872,7 @@ function walkField(field, out) {
872
872
  var SECRET_CLEAR = { $secretClear: true };
873
873
  /** The `'***'` token D634 redaction writes for non-admins. */
874
874
  var LEGACY_DEVICE_REDACTION = "***";
875
- function isRecord$1(value) {
875
+ function isRecord$2(value) {
876
876
  return typeof value === "object" && value !== null && !Array.isArray(value);
877
877
  }
878
878
  function makeSecretMarker(ref) {
@@ -883,16 +883,16 @@ function makeSecretMarker(ref) {
883
883
  }
884
884
  /** A stored marker: an object whose `$secret` is a non-empty string. */
885
885
  function isSecretMarker(value) {
886
- if (!isRecord$1(value)) return false;
886
+ if (!isRecord$2(value)) return false;
887
887
  const ref = value["$secret"];
888
888
  return typeof ref === "string" && ref.length > 0;
889
889
  }
890
890
  /** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
891
891
  function isSecretMarkerLike(value) {
892
- return isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
892
+ return isRecord$2(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
893
893
  }
894
894
  function isSecretClear(value) {
895
- return isRecord$1(value) && value["$secretClear"] === true;
895
+ return isRecord$2(value) && value["$secretClear"] === true;
896
896
  }
897
897
  /**
898
898
  * The UI view of a stored secret field value: a marker becomes "set", inline
@@ -1561,7 +1561,7 @@ var KeychainWriteRefusedError = class extends Error {
1561
1561
  }
1562
1562
  };
1563
1563
  var KEEP = { kind: "keep" };
1564
- function isRecord(value) {
1564
+ function isRecord$1(value) {
1565
1565
  return typeof value === "object" && value !== null && !Array.isArray(value);
1566
1566
  }
1567
1567
  function defaultMintId() {
@@ -1574,7 +1574,7 @@ function collectSecretMarkerRefs(value, out = []) {
1574
1574
  return out;
1575
1575
  }
1576
1576
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
1577
- else if (isRecord(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
1577
+ else if (isRecord$1(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
1578
1578
  return out;
1579
1579
  }
1580
1580
  async function storedValueEquals(secrets, marker, candidate) {
@@ -1629,7 +1629,7 @@ function rowId(row, idKey) {
1629
1629
  return null;
1630
1630
  }
1631
1631
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
1632
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord) : [];
1632
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$1) : [];
1633
1633
  if (!Array.isArray(incoming)) {
1634
1634
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
1635
1635
  return {
@@ -1645,7 +1645,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
1645
1645
  const kept = /* @__PURE__ */ new Set();
1646
1646
  const rows = [];
1647
1647
  for (const row of incoming) {
1648
- if (!isRecord(row)) {
1648
+ if (!isRecord$1(row)) {
1649
1649
  rows.push(row);
1650
1650
  continue;
1651
1651
  }
@@ -1670,7 +1670,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
1670
1670
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
1671
1671
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
1672
1672
  case "object": {
1673
- if (!isRecord(incoming)) {
1673
+ if (!isRecord$1(incoming)) {
1674
1674
  if (incoming === void 0) return KEEP;
1675
1675
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
1676
1676
  return {
@@ -1678,7 +1678,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
1678
1678
  value: incoming
1679
1679
  };
1680
1680
  }
1681
- const storedObj = isRecord(stored) ? stored : {};
1681
+ const storedObj = isRecord$1(stored) ? stored : {};
1682
1682
  return {
1683
1683
  kind: "value",
1684
1684
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -1737,12 +1737,19 @@ async function transformSecretWrite(input) {
1737
1737
  }
1738
1738
  };
1739
1739
  }
1740
+ /** Is `ref` one of `owner`'s refs? */
1741
+ function isSecretRefOwnedBy(ref, owner) {
1742
+ return parseSecretRef(ref)?.owner === owner;
1743
+ }
1740
1744
  /**
1741
1745
  * Replace every marker in `value` with its keychain value. Returns the SAME
1742
1746
  * reference when there is no marker (no allocation, no keychain call).
1743
1747
  */
1744
- async function resolveSecretMarkers(value, secrets) {
1745
- const refs = collectSecretMarkerRefs(value);
1748
+ async function resolveSecretMarkers(value, secrets, options = {}) {
1749
+ const keepMarker = options.unresolved === "keep-marker";
1750
+ const owner = options.ownedBy;
1751
+ const inScope = (ref) => owner === void 0 || isSecretRefOwnedBy(ref, owner);
1752
+ const refs = collectSecretMarkerRefs(value).filter(inScope);
1746
1753
  if (refs.length === 0) return {
1747
1754
  value,
1748
1755
  unresolved: []
@@ -1766,10 +1773,18 @@ async function resolveSecretMarkers(value, secrets) {
1766
1773
  }
1767
1774
  const substitute = (v) => {
1768
1775
  if (isSecretMarker(v)) {
1776
+ if (!inScope(v.$secret)) return {
1777
+ keep: true,
1778
+ value: v
1779
+ };
1769
1780
  const answer = answers[v.$secret];
1770
- return answer?.kind === "value" ? {
1781
+ if (answer?.kind === "value") return {
1771
1782
  keep: true,
1772
1783
  value: answer.value
1784
+ };
1785
+ return keepMarker ? {
1786
+ keep: true,
1787
+ value: v
1773
1788
  } : {
1774
1789
  keep: false,
1775
1790
  value: void 0
@@ -1786,7 +1801,7 @@ async function resolveSecretMarkers(value, secrets) {
1786
1801
  value: items
1787
1802
  };
1788
1803
  }
1789
- if (isRecord(v)) {
1804
+ if (isRecord$1(v)) {
1790
1805
  const out = {};
1791
1806
  for (const [k, child] of Object.entries(v)) {
1792
1807
  const s = substitute(child);
@@ -1807,14 +1822,152 @@ async function resolveSecretMarkers(value, secrets) {
1807
1822
  unresolved
1808
1823
  };
1809
1824
  }
1810
- function touchesTree(tree, patch) {
1811
- if (tree.size === 0) return false;
1812
- for (const key of Object.keys(patch)) {
1813
- const at = key.indexOf("@");
1814
- if (tree.has(at > 0 ? key.slice(0, at) : key)) return true;
1825
+ /** An addon tried to replace a stored secret it could not read. Nothing was written. */
1826
+ var SecretOverwriteRefusedError = class extends Error {
1827
+ ref;
1828
+ reason;
1829
+ constructor(ref, reason) {
1830
+ super(`secret ${ref} was not replaced: its stored value cannot be read right now (${reason}), and an addon may not overwrite a secret it cannot see. Nothing was written.`);
1831
+ this.ref = ref;
1832
+ this.reason = reason;
1833
+ this.name = "SecretOverwriteRefusedError";
1834
+ }
1835
+ };
1836
+ function isRecord(value) {
1837
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1838
+ }
1839
+ function resolvedValue(pass, marker) {
1840
+ const answer = pass.answers[marker.$secret];
1841
+ return answer?.kind === "value" ? answer.value : null;
1842
+ }
1843
+ async function atMarker(pass, incoming, stored) {
1844
+ if (incoming === null) return stored;
1845
+ if (typeof incoming !== "string") return incoming;
1846
+ if (incoming === resolvedValue(pass, stored)) return stored;
1847
+ const intent = classifySecretWrite(incoming);
1848
+ if (intent.kind !== "set") return stored;
1849
+ const ref = stored.$secret;
1850
+ if (!isSecretRefOwnedBy(ref, pass.secrets.owner)) return incoming;
1851
+ const answer = pass.answers[ref];
1852
+ if ((answer === void 0 || answer.kind === "unavailable" || answer.kind === "forbidden") && !pass.operatorWrite) throw new SecretOverwriteRefusedError(ref, answer === void 0 ? "not-initialized" : answer.kind === "unavailable" ? answer.reason : answer.kind);
1853
+ try {
1854
+ await pass.secrets.put(ref, intent.value);
1855
+ } catch (err) {
1856
+ throw new KeychainWriteRefusedError(ref, err);
1857
+ }
1858
+ pass.changedRefs.push(ref);
1859
+ return stored;
1860
+ }
1861
+ function inArray(pass, incoming, stored) {
1862
+ return incoming.map((item, index) => {
1863
+ const twin = stored[index];
1864
+ if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
1865
+ if (isRecord(item) && isRecord(twin)) return inArrayRow(pass, item, twin);
1866
+ if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
1867
+ return item;
1868
+ });
1869
+ }
1870
+ function inArrayRow(pass, incoming, stored) {
1871
+ const out = { ...incoming };
1872
+ for (const [key, value] of Object.entries(incoming)) {
1873
+ const twin = stored[key];
1874
+ if (isSecretMarker(twin)) {
1875
+ if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
1876
+ } else if (isRecord(value) && isRecord(twin)) out[key] = inArrayRow(pass, value, twin);
1877
+ else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
1878
+ }
1879
+ return out;
1880
+ }
1881
+ async function walk(pass, incoming, stored) {
1882
+ if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
1883
+ if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
1884
+ if (!isRecord(incoming) || !isRecord(stored)) return incoming;
1885
+ const out = { ...incoming };
1886
+ for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
1887
+ return out;
1888
+ }
1889
+ /** Restore markers a write-back carries as values. See the file doc. */
1890
+ async function restoreUnchangedSecrets(input) {
1891
+ const refs = [];
1892
+ for (const key of Object.keys(input.patch)) collectSecretMarkerRefs(input.stored[key], refs);
1893
+ const owned = refs.filter((ref) => isSecretRefOwnedBy(ref, input.secrets.owner));
1894
+ if (owned.length === 0) return {
1895
+ patch: input.patch,
1896
+ changedRefs: []
1897
+ };
1898
+ const pass = {
1899
+ secrets: input.secrets,
1900
+ answers: await input.secrets.resolve([...new Set(owned)]),
1901
+ operatorWrite: input.operatorWrite,
1902
+ changedRefs: []
1903
+ };
1904
+ const out = {};
1905
+ for (const [key, value] of Object.entries(input.patch)) out[key] = await walk(pass, value, input.stored[key]);
1906
+ return {
1907
+ patch: out,
1908
+ changedRefs: pass.changedRefs
1909
+ };
1910
+ }
1911
+ /** Structural equality over JSON-shaped values (key order does not matter). */
1912
+ function isJsonEqual(a, b) {
1913
+ if (Object.is(a, b)) return true;
1914
+ if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
1915
+ if (isRecord(a) && isRecord(b)) {
1916
+ const keys = Object.keys(a);
1917
+ if (keys.length !== Object.keys(b).length) return false;
1918
+ return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
1815
1919
  }
1816
1920
  return false;
1817
1921
  }
1922
+ /** The patch without the keys whose value already equals the stored one. */
1923
+ function dropUnchangedKeys(patch, stored) {
1924
+ const out = {};
1925
+ for (const [key, value] of Object.entries(patch)) {
1926
+ if (Object.prototype.hasOwnProperty.call(stored, key) && isJsonEqual(value, stored[key])) continue;
1927
+ out[key] = value;
1928
+ }
1929
+ return out;
1930
+ }
1931
+ /** The top-level keys of a blob that hold at least one of `owner`'s markers. */
1932
+ function markerKeysOf(blob, owner) {
1933
+ const keys = /* @__PURE__ */ new Set();
1934
+ for (const [key, value] of Object.entries(blob)) if (collectSecretMarkerRefs(value).some((ref) => isSecretRefOwnedBy(ref, owner))) keys.add(key);
1935
+ return keys;
1936
+ }
1937
+ function nodeFromStored(value, owner) {
1938
+ if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
1939
+ if (!isRecord(value)) return null;
1940
+ const children = /* @__PURE__ */ new Map();
1941
+ for (const [key, child] of Object.entries(value)) {
1942
+ const node = nodeFromStored(child, owner);
1943
+ if (node !== null) children.set(key, node);
1944
+ }
1945
+ return children.size > 0 ? {
1946
+ kind: "object",
1947
+ children
1948
+ } : null;
1949
+ }
1950
+ /**
1951
+ * The declared secret tree, widened by every place the STORED blob holds a
1952
+ * marker under one of `keys` — so a marker the schema no longer declares (or
1953
+ * never declared: Wyze before b4906cec6) still gets the save path's
1954
+ * KEEP / SET / CLEAR semantics instead of being overwritten by whatever the
1955
+ * addon writes. Markers inside arrays are not added: a row has no id field to
1956
+ * match by unless the schema declares one. Only `owner`'s markers count: a
1957
+ * builtin that keeps another addon's marker (device-manager) never takes it
1958
+ * over.
1959
+ */
1960
+ function widenTreeWithStoredMarkers(declared, stored, keys, owner) {
1961
+ const added = /* @__PURE__ */ new Map();
1962
+ for (const key of keys) {
1963
+ const at = key.indexOf("@");
1964
+ const base = at > 0 ? key.slice(0, at) : key;
1965
+ if (declared.has(base) || added.has(base)) continue;
1966
+ const node = nodeFromStored(stored[key], owner);
1967
+ if (node !== null) added.set(base, node);
1968
+ }
1969
+ return added.size === 0 ? declared : new Map([...declared, ...added]);
1970
+ }
1818
1971
  /** The blob as stored now; a failed read REFUSES the write instead of guessing. */
1819
1972
  async function readStoredAddonBlob(raw) {
1820
1973
  const threeWay = raw.readAddonStoreResult;
@@ -1823,7 +1976,41 @@ async function readStoredAddonBlob(raw) {
1823
1976
  if (result.kind === "failed") throw new Error(`settings write refused: the stored blob could not be read (${result.error})`);
1824
1977
  return result.kind === "missing" ? {} : result.value;
1825
1978
  }
1826
- function createSecretAwareSettings(raw, opts) {
1979
+ function touchesTree(tree, patch) {
1980
+ if (tree.size === 0) return false;
1981
+ for (const key of Object.keys(patch)) {
1982
+ const at = key.indexOf("@");
1983
+ if (tree.has(at > 0 ? key.slice(0, at) : key)) return true;
1984
+ }
1985
+ return false;
1986
+ }
1987
+ /**
1988
+ * Which top-level keys of each store held markers when last seen. `undefined`
1989
+ * for a store not seen yet: its first write reads it, to be safe.
1990
+ */
1991
+ var MarkerKeyIndex = class {
1992
+ owner;
1993
+ byStore = /* @__PURE__ */ new Map();
1994
+ constructor(owner) {
1995
+ this.owner = owner;
1996
+ }
1997
+ seen(store, blob) {
1998
+ this.byStore.set(store, markerKeysOf(blob, this.owner));
1999
+ }
2000
+ wrote(store, patch) {
2001
+ const keys = this.byStore.get(store);
2002
+ if (keys === void 0) return;
2003
+ const now = markerKeysOf(patch, this.owner);
2004
+ for (const key of Object.keys(patch)) if (now.has(key)) keys.add(key);
2005
+ else keys.delete(key);
2006
+ }
2007
+ mayHoldMarkers(store, patch) {
2008
+ const keys = this.byStore.get(store);
2009
+ if (keys === void 0) return true;
2010
+ return Object.keys(patch).some((key) => keys.has(key));
2011
+ }
2012
+ };
2013
+ function createHandle(raw, opts) {
1827
2014
  const chains = /* @__PURE__ */ new Map();
1828
2015
  const serialize = (key, fn) => {
1829
2016
  const next = (chains.get(key) ?? Promise.resolve()).then(fn, fn);
@@ -1834,46 +2021,130 @@ function createSecretAwareSettings(raw, opts) {
1834
2021
  });
1835
2022
  return next;
1836
2023
  };
1837
- const write = async (chainKey, tree, scope, patch, readStored, rawWrite) => {
1838
- if (!touchesTree(tree, patch)) return rawWrite(patch);
1839
- return serialize(chainKey, async () => {
2024
+ const index = new MarkerKeyIndex(opts.secrets.owner);
2025
+ const resolveRead = async (store, blob) => {
2026
+ index.seen(store, blob);
2027
+ const { value, unresolved } = await resolveSecretMarkers(blob, opts.secrets, {
2028
+ unresolved: "keep-marker",
2029
+ ownedBy: opts.secrets.owner
2030
+ });
2031
+ if (unresolved.length > 0) opts.onUnresolved?.(unresolved);
2032
+ return value;
2033
+ };
2034
+ const write = async (store, declared, scope, patch, readStored, rawWrite) => {
2035
+ if (!touchesTree(declared, patch) && !index.mayHoldMarkers(store, patch)) {
2036
+ await rawWrite(patch);
2037
+ index.wrote(store, patch);
2038
+ return { changed: true };
2039
+ }
2040
+ return serialize(store, async () => {
1840
2041
  const stored = await readStored();
2042
+ index.seen(store, stored);
2043
+ const operatorWrite = opts.isOperatorWrite?.() === true;
2044
+ const restored = await restoreUnchangedSecrets({
2045
+ patch,
2046
+ stored,
2047
+ secrets: opts.secrets,
2048
+ operatorWrite
2049
+ });
1841
2050
  const result = await transformSecretWrite({
1842
- tree,
2051
+ tree: widenTreeWithStoredMarkers(declared, stored, Object.keys(restored.patch), opts.secrets.owner),
1843
2052
  secrets: opts.secrets,
1844
- patch,
2053
+ patch: restored.patch,
1845
2054
  stored,
1846
2055
  ...scope !== void 0 ? { scope } : {},
1847
- operatorWrite: opts.isOperatorWrite?.() === true
2056
+ operatorWrite
1848
2057
  });
1849
- if (Object.keys(result.patch).length > 0) await rawWrite(result.patch);
2058
+ const effective = dropUnchangedKeys(result.patch, stored);
2059
+ const wroteBlob = Object.keys(effective).length > 0;
2060
+ if (wroteBlob) {
2061
+ await rawWrite(effective);
2062
+ index.wrote(store, effective);
2063
+ }
1850
2064
  const { orphaned } = await result.afterWrite();
1851
2065
  if (orphaned.length > 0) opts.onOrphaned(orphaned);
2066
+ return { changed: wroteBlob || restored.changedRefs.length > 0 };
1852
2067
  });
1853
2068
  };
1854
- const wrapped = {
1855
- writeAddonStore: (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p)),
1856
- writeDeviceStore: (deviceId, patch) => write(`device:${deviceId}`, opts.deviceTree(), {
1857
- kind: "device",
1858
- deviceId
1859
- }, patch, () => raw.readDeviceStore(deviceId), (p) => raw.writeDeviceStore(deviceId, p))
2069
+ const writeAddon = (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p));
2070
+ const readAddonStoreResult = raw.readAddonStoreResult;
2071
+ const readAddonStoreKeyResult = raw.readAddonStoreKeyResult;
2072
+ const readDeviceStoreBatch = raw.readDeviceStoreBatch;
2073
+ const overrides = {
2074
+ readAddonStore: async () => resolveRead("addon", await raw.readAddonStore()),
2075
+ ...readAddonStoreResult !== void 0 ? { readAddonStoreResult: async () => {
2076
+ const result = await readAddonStoreResult.call(raw);
2077
+ return result.kind === "value" ? {
2078
+ kind: "value",
2079
+ value: await resolveRead("addon", result.value)
2080
+ } : result;
2081
+ } } : {},
2082
+ ...readAddonStoreKeyResult !== void 0 ? { readAddonStoreKeyResult: async (key) => {
2083
+ const result = await readAddonStoreKeyResult.call(raw, key);
2084
+ if (result.kind !== "present") return result;
2085
+ const { value, unresolved } = await resolveSecretMarkers({ [key]: result.value }, opts.secrets, {
2086
+ unresolved: "keep-marker",
2087
+ ownedBy: opts.secrets.owner
2088
+ });
2089
+ if (unresolved.length > 0) opts.onUnresolved?.(unresolved);
2090
+ return {
2091
+ kind: "present",
2092
+ value: value[key]
2093
+ };
2094
+ } } : {},
2095
+ readDeviceStore: async (deviceId) => resolveRead(`device:${deviceId}`, await raw.readDeviceStore(deviceId)),
2096
+ ...readDeviceStoreBatch !== void 0 ? { readDeviceStoreBatch: async (deviceIds) => {
2097
+ const rows = await readDeviceStoreBatch.call(raw, deviceIds);
2098
+ const out = /* @__PURE__ */ new Map();
2099
+ for (const [deviceId, blob] of rows) out.set(deviceId, await resolveRead(`device:${deviceId}`, blob));
2100
+ return out;
2101
+ } } : {},
2102
+ writeAddonStore: async (patch) => {
2103
+ await writeAddon(patch);
2104
+ },
2105
+ writeDeviceStore: async (deviceId, patch) => {
2106
+ await write(`device:${deviceId}`, opts.deviceTree(), {
2107
+ kind: "device",
2108
+ deviceId
2109
+ }, patch, () => raw.readDeviceStore(deviceId), (p) => raw.writeDeviceStore(deviceId, p));
2110
+ }
2111
+ };
2112
+ return {
2113
+ settings: new Proxy(raw, { get(target, prop, receiver) {
2114
+ if (typeof prop === "string" && Object.prototype.hasOwnProperty.call(overrides, prop)) return Reflect.get(overrides, prop);
2115
+ const value = Reflect.get(target, prop, receiver);
2116
+ return typeof value === "function" ? value.bind(target) : value;
2117
+ } }),
2118
+ raw: {
2119
+ readAddonStore: async () => {
2120
+ const blob = await raw.readAddonStore();
2121
+ index.seen("addon", blob);
2122
+ return blob;
2123
+ },
2124
+ readDeviceStore: async (deviceId) => {
2125
+ const blob = await raw.readDeviceStore(deviceId);
2126
+ index.seen(`device:${deviceId}`, blob);
2127
+ return blob;
2128
+ }
2129
+ },
2130
+ writeAddonStore: writeAddon
1860
2131
  };
1861
- return new Proxy(raw, { get(target, prop, receiver) {
1862
- if (prop === "writeAddonStore") return wrapped.writeAddonStore;
1863
- if (prop === "writeDeviceStore") return wrapped.writeDeviceStore;
1864
- const value = Reflect.get(target, prop, receiver);
1865
- return typeof value === "function" ? value.bind(target) : value;
1866
- } });
1867
- }
1868
- /** The context the addon sees: identical, except that its settings writes are secret-aware. */
1869
- function withSecretAwareSettings(context, opts) {
2132
+ }
2133
+ /** The context the addon sees, plus the private raw view and write outcome. */
2134
+ function createSecretAwareContext(context, opts) {
1870
2135
  const raw = context.settings;
1871
- if (raw === void 0) return context;
1872
- const settings = createSecretAwareSettings(raw, opts);
1873
- return new Proxy(context, { get(target, prop, receiver) {
1874
- if (prop === "settings") return settings;
1875
- return Reflect.get(target, prop, receiver);
1876
- } });
2136
+ if (raw === void 0) return {
2137
+ context,
2138
+ handle: null
2139
+ };
2140
+ const handle = createHandle(raw, opts);
2141
+ return {
2142
+ context: new Proxy(context, { get(target, prop, receiver) {
2143
+ if (prop === "settings") return handle.settings;
2144
+ return Reflect.get(target, prop, receiver);
2145
+ } }),
2146
+ handle
2147
+ };
1877
2148
  }
1878
2149
  var LEAF = { kind: "leaf" };
1879
2150
  var DEFAULT_ITEM_ID_KEY = "id";
@@ -2061,6 +2332,14 @@ var BaseAddon = class {
2061
2332
  _secretChangeUnsub = null;
2062
2333
  /** >0 while an OPERATOR's settings write is being applied. */
2063
2334
  _operatorWriteDepth = 0;
2335
+ /**
2336
+ * The keychain-aware settings handle (D798). Its `raw` view is the ONLY way
2337
+ * to the marker-bearing blobs and stays private to this class: the form
2338
+ * hydrate and config resolution need markers, addon code never does.
2339
+ */
2340
+ _secretSettings = null;
2341
+ /** Refs an addon-facing read could not resolve — each is logged once per process. */
2342
+ _loggedUnresolvedReads = /* @__PURE__ */ new Set();
2064
2343
  constructor(defaults) {
2065
2344
  this.defaults = defaults;
2066
2345
  this._config = { ...defaults };
@@ -2097,13 +2376,21 @@ var BaseAddon = class {
2097
2376
  }
2098
2377
  async initialize(context) {
2099
2378
  const secrets = context.secrets;
2100
- this._ctx = secrets !== void 0 ? withSecretAwareSettings(context, {
2101
- secrets,
2102
- addonTree: () => this.addonSecretTree(),
2103
- deviceTree: () => this.deviceSecretTree(),
2104
- onOrphaned: (refs) => context.logger.warn("keychain refs dropped by a settings write could not be deleted", { meta: { refs: [...refs] } }),
2105
- isOperatorWrite: () => this._operatorWriteDepth > 0
2106
- }) : context;
2379
+ if (secrets !== void 0) {
2380
+ const { context: wrapped, handle } = createSecretAwareContext(context, {
2381
+ secrets,
2382
+ addonTree: () => this.addonSecretTree(),
2383
+ deviceTree: () => this.deviceSecretTree(),
2384
+ onOrphaned: (refs) => context.logger.warn("keychain refs dropped by a settings write could not be deleted", { meta: { refs: [...refs] } }),
2385
+ isOperatorWrite: () => this._operatorWriteDepth > 0,
2386
+ onUnresolved: (unresolved) => this.logUnresolvedRead(unresolved)
2387
+ });
2388
+ this._ctx = wrapped;
2389
+ this._secretSettings = handle;
2390
+ } else {
2391
+ this._ctx = context;
2392
+ this._secretSettings = null;
2393
+ }
2107
2394
  await this.resolveConfig();
2108
2395
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
2109
2396
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -2220,11 +2507,8 @@ var BaseAddon = class {
2220
2507
  async getGlobalSettings(overlay, cap, nodeId) {
2221
2508
  const schema = this.globalSettingsSchema(cap);
2222
2509
  if (!schema) return { sections: [] };
2223
- const projected = await this.resolveGlobalStore(nodeId, cap);
2224
- return hydrateSchema(schema, overlay ? {
2225
- ...projected,
2226
- ...overlay
2227
- } : projected, { inlineSecretsAsViews: true });
2510
+ const projected = await this.readRawProjectedStore(nodeId, cap);
2511
+ return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
2228
2512
  }
2229
2513
  /**
2230
2514
  * The integration-level view of this addon's settings: exactly the sections
@@ -2269,7 +2553,7 @@ var BaseAddon = class {
2269
2553
  });
2270
2554
  }
2271
2555
  if (sections.length === 0) return null;
2272
- const projected = await this.resolveGlobalStore(nodeId);
2556
+ const projected = await this.readRawProjectedStore(nodeId);
2273
2557
  return hydrateSchema({
2274
2558
  ...schema,
2275
2559
  sections
@@ -2286,12 +2570,50 @@ var BaseAddon = class {
2286
2570
  * the store for custom option logic (option narrowing, value snapping) to
2287
2571
  * read it per-node — never `ctx.settings.readAddonStore()` directly.
2288
2572
  * `nodeId` omitted ⇒ the local node (node-blind injection from ctx).
2573
+ *
2574
+ * Keychain values come back RESOLVED (D798): a secret reads as its value,
2575
+ * never as a `{ $secret }` marker. One the keychain cannot serve right now
2576
+ * stays a marker — "stored, unavailable" (`isSecretMarker()` is true, and it
2577
+ * is listed in {@link unresolvedSecrets}) — never `''` and never absent, so
2578
+ * it cannot be mistaken for "nothing stored, mint a new one". Writing the
2579
+ * resolved blob back is a no-op for every unchanged secret.
2289
2580
  */
2290
2581
  async resolveGlobalStore(nodeId, cap) {
2291
2582
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
2583
+ return this.projectForNode(raw, nodeId, cap);
2584
+ }
2585
+ projectForNode(store, nodeId, cap) {
2292
2586
  const keys = this.perNodeKeys(cap);
2293
2587
  const node = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
2294
- return keys.size > 0 ? projectStore(raw, keys, node) : raw;
2588
+ return keys.size > 0 ? projectStore(store, keys, node) : store;
2589
+ }
2590
+ /** The PROJECTED store with its markers — the form hydrate only (D798). */
2591
+ async readRawProjectedStore(nodeId, cap) {
2592
+ const raw = await this.rawSettings()?.readAddonStore() ?? {};
2593
+ return this.projectForNode(raw, nodeId, cap);
2594
+ }
2595
+ /**
2596
+ * `{ ...stored, ...overlay }`, except that an overlay member equal to the
2597
+ * RESOLVED stored one keeps the stored (marker-bearing) member — so a form
2598
+ * hydrated from a resolved overlay still shows a secret as "set", never as
2599
+ * its value.
2600
+ */
2601
+ async overlayKeepingMarkers(stored, overlay) {
2602
+ const secrets = this._ctx?.secrets;
2603
+ const merged = {
2604
+ ...stored,
2605
+ ...overlay
2606
+ };
2607
+ if (secrets === void 0 || collectSecretMarkerRefs(stored).length === 0) return merged;
2608
+ const { value: resolved } = await resolveSecretMarkers(stored, secrets, {
2609
+ unresolved: "keep-marker",
2610
+ ownedBy: secrets.owner
2611
+ });
2612
+ for (const key of Object.keys(overlay)) {
2613
+ if (collectSecretMarkerRefs(stored[key]).length === 0) continue;
2614
+ if (isJsonEqual(overlay[key], resolved[key])) merged[key] = stored[key];
2615
+ }
2616
+ return merged;
2295
2617
  }
2296
2618
  /**
2297
2619
  * Whether ONE global key of this addon's store was written, read three-way
@@ -2356,8 +2678,12 @@ var BaseAddon = class {
2356
2678
  async clearSecrets(keys, nodeId) {
2357
2679
  const tree = this.addonSecretTree();
2358
2680
  const keychain = this._ctx?.secrets !== void 0;
2681
+ const stored = keychain ? await this.readAddonStoreWithRetry() : {};
2359
2682
  const patch = {};
2360
- for (const key of keys) patch[key] = keychain && tree.has(key) ? SECRET_CLEAR : "";
2683
+ for (const key of keys) {
2684
+ const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
2685
+ patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
2686
+ }
2361
2687
  await this.applyGlobalPatch(patch, nodeId);
2362
2688
  }
2363
2689
  async applyGlobalPatch(barePatch, nodeId) {
@@ -2365,7 +2691,11 @@ var BaseAddon = class {
2365
2691
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
2366
2692
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
2367
2693
  const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
2368
- await this._ctx?.settings?.writeAddonStore(scoped);
2694
+ const handle = this._secretSettings;
2695
+ if (handle !== null) {
2696
+ const { changed } = await handle.writeAddonStore(scoped);
2697
+ if (!changed) return;
2698
+ } else await this._ctx?.settings?.writeAddonStore(scoped);
2369
2699
  if (target !== localNode) return;
2370
2700
  await this.resolveConfig();
2371
2701
  await this.onConfigChanged();
@@ -2418,7 +2748,7 @@ var BaseAddon = class {
2418
2748
  async getDeviceSettings(deviceId) {
2419
2749
  const schema = this.deviceSettingsSchema();
2420
2750
  if (!schema) return { sections: [] };
2421
- return hydrateSchema(schema, await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
2751
+ return hydrateSchema(schema, await this.rawSettings()?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
2422
2752
  }
2423
2753
  async updateDeviceSettings(deviceId, patch) {
2424
2754
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
@@ -2590,7 +2920,7 @@ var BaseAddon = class {
2590
2920
  key,
2591
2921
  schema,
2592
2922
  fallback,
2593
- read: async () => this.resolveStoredSecrets(await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, false),
2923
+ read: async () => this.resolveStoredSecrets(await this.rawSettings()?.readDeviceStore(deviceId) ?? {}, false),
2594
2924
  write: async (patch) => {
2595
2925
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
2596
2926
  },
@@ -2652,6 +2982,23 @@ var BaseAddon = class {
2652
2982
  }
2653
2983
  return value;
2654
2984
  }
2985
+ /**
2986
+ * The marker-bearing settings reads (D798) — private on purpose. Without a
2987
+ * keychain the context's own view is already raw.
2988
+ */
2989
+ rawSettings() {
2990
+ return this._secretSettings?.raw ?? this._ctx?.settings;
2991
+ }
2992
+ /** An addon-facing read met markers it could not resolve: say so once per ref. */
2993
+ logUnresolvedRead(unresolved) {
2994
+ const fresh = unresolved.filter((u) => !this._loggedUnresolvedReads.has(u.ref));
2995
+ if (fresh.length === 0) return;
2996
+ for (const u of fresh) this._loggedUnresolvedReads.add(u.ref);
2997
+ this._ctx?.logger?.warn?.("settings read: a stored secret is unavailable — it reads as its marker, not as a value", { meta: { secrets: fresh.map((u) => ({
2998
+ ref: u.ref,
2999
+ reason: u.reason
3000
+ })) } });
3001
+ }
2655
3002
  /** Re-resolve the config (a keychain-only change, or a retry) and apply it. */
2656
3003
  scheduleSecretReresolve(delayMs) {
2657
3004
  const timerOf = delayMs === SECRET_CHANGE_DEBOUNCE_MS ? "_secretChangeTimer" : "_secretRetryTimer";
@@ -2678,7 +3025,7 @@ var BaseAddon = class {
2678
3025
  * `{}` (defaults) — the addon's first successful patch will rehydrate.
2679
3026
  */
2680
3027
  async readAddonStoreWithRetry() {
2681
- const settings = this._ctx?.settings;
3028
+ const settings = this.rawSettings();
2682
3029
  if (!settings) return {};
2683
3030
  const delaysMs = [
2684
3031
  150,
@@ -6652,6 +6999,278 @@ var DERIVED_CAMERA_SILENCED_CAP_NAMES = [
6652
6999
  "scene-monitor"
6653
7000
  ];
6654
7001
  /**
7002
+ * The trainer contract — what the hub hands the cloud trainer and what it
7003
+ * gets back (cloud training, D805+).
7004
+ *
7005
+ * The trainer (`hf/camstack-trainer`, published as the Hugging Face repo
7006
+ * `camstack/camstack-trainer`) is a separate AGPL program the hub never
7007
+ * imports: `job.json` goes in, files come out, and progress is `CAMSTACK_EVENT`
7008
+ * lines on stdout. These schemas are the hub's half of that boundary; the
7009
+ * trainer's pydantic models (and the JSON Schemas generated from them under
7010
+ * `hf/camstack-trainer/schemas/`) are the other half. A change here is a
7011
+ * change there, in the same piece of work.
7012
+ *
7013
+ * No `.default()` anywhere: defaults do not run on the addon cap path, so the
7014
+ * orchestrator fills every field of a job spec explicitly.
7015
+ */
7016
+ var TRAINING_JOB_SCHEMA = "camstack.training/v1";
7017
+ var TRAINING_RESULT_SCHEMA = "camstack.training-result/v1";
7018
+ /** What the trainer can export. Edge TPU is a later, separate image. */
7019
+ var TrainingExportSchema = z.enum([
7020
+ "onnx",
7021
+ "openvino-fp16",
7022
+ "openvino-int8",
7023
+ "coreml-fp16"
7024
+ ]);
7025
+ var FloorsSchema = z.record(z.string(), z.number().min(0).max(1));
7026
+ z.object({
7027
+ schema: z.literal(TRAINING_JOB_SCHEMA),
7028
+ jobKey: z.string().uuid(),
7029
+ target: z.object({
7030
+ kind: z.literal("detector"),
7031
+ step: z.literal("object-detection")
7032
+ }),
7033
+ base: z.object({
7034
+ modelId: z.string().min(1),
7035
+ /** Ultralytics weights id, e.g. `yolov9m.pt`. */
7036
+ weights: z.string().min(1),
7037
+ imgsz: z.number().int().positive()
7038
+ }),
7039
+ baseline: z.object({
7040
+ modelId: z.string().min(1),
7041
+ /** The baseline ONNX the holdout comparison runs; public mirror URL. */
7042
+ onnxUrl: z.string().url(),
7043
+ imgsz: z.number().int().positive(),
7044
+ floors: FloorsSchema
7045
+ }),
7046
+ dataset: z.object({
7047
+ versionId: z.string().min(1),
7048
+ annotationHash: z.string(),
7049
+ /** sha256 of the uploaded archive; the trainer refuses a mismatch (exit 65). */
7050
+ sha256: z.string().regex(/^[0-9a-f]{64}$/)
7051
+ }),
7052
+ split: z.object({
7053
+ holdoutCameras: z.array(z.number().int()),
7054
+ /** When no camera is held out: the last N days of every camera. */
7055
+ holdoutLastDays: z.number().int().nonnegative(),
7056
+ valFraction: z.number().min(0).max(.5),
7057
+ seed: z.number().int()
7058
+ }),
7059
+ hyper: z.object({
7060
+ epochs: z.number().int().positive(),
7061
+ batch: z.number().int().positive(),
7062
+ lr0: z.number().positive(),
7063
+ /** Layers frozen from the start of the backbone; 0 = none. */
7064
+ freeze: z.number().int().nonnegative(),
7065
+ patience: z.number().int().nonnegative(),
7066
+ seed: z.number().int()
7067
+ }),
7068
+ replay: z.object({
7069
+ source: z.literal("coco2017"),
7070
+ count: z.number().int().nonnegative()
7071
+ }),
7072
+ exports: z.array(TrainingExportSchema).min(1),
7073
+ calibration: z.object({ maxFrames: z.number().int().positive() }),
7074
+ floors: z.object({
7075
+ minSupport: z.number().int().positive(),
7076
+ clamp: z.number().min(0).max(1)
7077
+ }),
7078
+ int8Gate: z.object({ maxMap50Drop: z.number().min(0).max(1) }),
7079
+ catalog: z.object({
7080
+ id: z.string().regex(/^[a-z0-9][a-z0-9-]{2,80}$/),
7081
+ name: z.string().min(1).max(160)
7082
+ })
7083
+ });
7084
+ /** One regular file of the bundle. An mlpackage contributes one row per member. */
7085
+ var TrainingResultFileSchema = z.object({
7086
+ path: z.string().min(1),
7087
+ bytes: z.number().int().nonnegative(),
7088
+ sha256: z.string().regex(/^[0-9a-f]{64}$/),
7089
+ format: z.enum([
7090
+ "onnx",
7091
+ "openvino",
7092
+ "coreml",
7093
+ "pt"
7094
+ ]),
7095
+ variant: z.enum(["base", "int8"])
7096
+ });
7097
+ var DraftFormatSchema = z.object({
7098
+ /** RELATIVE path inside the bundle; the hub rewrites it to `camstack-local://`. */
7099
+ url: z.string().min(1),
7100
+ sizeMB: z.number().nonnegative(),
7101
+ isDirectory: z.boolean().optional(),
7102
+ files: z.array(z.string()).optional(),
7103
+ runtimes: z.array(z.literal("python")).optional(),
7104
+ revision: z.string().min(1)
7105
+ });
7106
+ /** The trainer's half of a catalog entry; the hub completes it (labels, licence, group). */
7107
+ var TrainingCatalogDraftSchema = z.object({
7108
+ id: z.string().min(1),
7109
+ name: z.string().min(1),
7110
+ description: z.string(),
7111
+ labels: z.array(z.unknown()),
7112
+ inputSize: z.object({
7113
+ width: z.number().int().positive(),
7114
+ height: z.number().int().positive()
7115
+ }),
7116
+ preprocessMode: z.literal("letterbox"),
7117
+ defaultClassFloors: FloorsSchema,
7118
+ formats: z.object({
7119
+ onnx: DraftFormatSchema.optional(),
7120
+ openvino: DraftFormatSchema.optional(),
7121
+ coreml: DraftFormatSchema.optional()
7122
+ })
7123
+ });
7124
+ /** One class of one model's holdout report (the eval harness's summary). */
7125
+ var TrainingEvalClassSchema = z.object({
7126
+ positives: z.number().int(),
7127
+ floor: z.number().nullable(),
7128
+ tp: z.number().int(),
7129
+ fp: z.number().int(),
7130
+ fn: z.number().int(),
7131
+ precision: z.number().nullable(),
7132
+ recall: z.number().nullable(),
7133
+ ap50: z.number().nullable(),
7134
+ recallByCamera: z.record(z.string(), z.number().nullable()).optional(),
7135
+ recallByLight: z.record(z.string(), z.number().nullable()).optional(),
7136
+ fpByCamera: z.record(z.string(), z.number()).optional(),
7137
+ smallFound: z.number().int().optional(),
7138
+ smallTotal: z.number().int().optional()
7139
+ }).passthrough();
7140
+ var TrainingEvalReportSchema = z.object({
7141
+ frames: z.number().int(),
7142
+ classes: z.record(z.string(), TrainingEvalClassSchema)
7143
+ }).passthrough();
7144
+ /**
7145
+ * A succeeded result carries every block; a failed one carries the error and
7146
+ * whatever was known by then — the other blocks are ABSENT, not null. The
7147
+ * orchestrator checks the blocks it needs before it trusts a success.
7148
+ */
7149
+ var TrainingResultSchema = z.object({
7150
+ schema: z.literal(TRAINING_RESULT_SCHEMA),
7151
+ jobKey: z.string(),
7152
+ status: z.enum(["succeeded", "failed"]),
7153
+ error: z.object({
7154
+ stage: z.string(),
7155
+ code: z.string(),
7156
+ message: z.string()
7157
+ }).nullable(),
7158
+ trainer: z.object({
7159
+ version: z.string(),
7160
+ revision: z.string()
7161
+ }),
7162
+ toolchain: z.record(z.string(), z.string().nullable()),
7163
+ dataset: z.object({
7164
+ versionId: z.string(),
7165
+ annotationHash: z.string(),
7166
+ sha256: z.string(),
7167
+ frames: z.object({
7168
+ train: z.number().int(),
7169
+ val: z.number().int(),
7170
+ holdout: z.number().int()
7171
+ }),
7172
+ holdoutCameras: z.array(z.number().int()),
7173
+ replayImages: z.number().int()
7174
+ }).optional(),
7175
+ training: z.object({
7176
+ epochsRun: z.number().int(),
7177
+ bestEpoch: z.number().int(),
7178
+ bestMap50: z.number(),
7179
+ bestMap50_95: z.number(),
7180
+ seconds: z.number()
7181
+ }).optional(),
7182
+ evaluation: z.object({
7183
+ holdoutFrames: z.number().int(),
7184
+ baseline: TrainingEvalReportSchema.nullable(),
7185
+ candidate: TrainingEvalReportSchema.nullable(),
7186
+ candidateInt8: TrainingEvalReportSchema.nullable(),
7187
+ recallDeltaByCamera: z.record(z.string(), z.record(z.string(), z.number())).nullable(),
7188
+ map50: z.record(z.string(), z.number().nullable())
7189
+ }).optional(),
7190
+ floors: z.object({
7191
+ baseline: FloorsSchema,
7192
+ candidate: FloorsSchema,
7193
+ candidateInt8: FloorsSchema.nullable(),
7194
+ notes: z.array(z.string())
7195
+ }).optional(),
7196
+ int8: z.object({
7197
+ kept: z.boolean(),
7198
+ map50Fp16: z.number().nullable(),
7199
+ map50Int8: z.number().nullable(),
7200
+ reason: z.string().nullable()
7201
+ }).optional(),
7202
+ exportsDropped: z.array(z.object({
7203
+ export: z.string(),
7204
+ reason: z.string()
7205
+ })).optional(),
7206
+ files: z.array(TrainingResultFileSchema).optional(),
7207
+ bundle: z.object({
7208
+ path: z.literal("bundle.tar"),
7209
+ bytes: z.number().int().nonnegative(),
7210
+ sha256: z.string().regex(/^[0-9a-f]{64}$/)
7211
+ }).nullable().optional(),
7212
+ catalogDraft: z.object({
7213
+ base: TrainingCatalogDraftSchema,
7214
+ int8: TrainingCatalogDraftSchema.nullable()
7215
+ }).nullable().optional(),
7216
+ licence: z.string()
7217
+ });
7218
+ var TrainingStageSchema = z.enum([
7219
+ "prepare",
7220
+ "replay",
7221
+ "train",
7222
+ "eval",
7223
+ "export",
7224
+ "package"
7225
+ ]);
7226
+ var EventBase = {
7227
+ v: z.literal(1),
7228
+ ts: z.number()
7229
+ };
7230
+ z.discriminatedUnion("type", [
7231
+ z.object({
7232
+ ...EventBase,
7233
+ type: z.literal("stage"),
7234
+ stage: TrainingStageSchema
7235
+ }),
7236
+ z.object({
7237
+ ...EventBase,
7238
+ type: z.literal("epoch"),
7239
+ epoch: z.number().int(),
7240
+ epochs: z.number().int(),
7241
+ boxLoss: z.number().nullable().optional(),
7242
+ map50: z.number().nullable().optional(),
7243
+ map50_95: z.number().nullable().optional(),
7244
+ etaSec: z.number().nullable().optional()
7245
+ }),
7246
+ z.object({
7247
+ ...EventBase,
7248
+ type: z.literal("metric"),
7249
+ name: z.string(),
7250
+ value: z.number()
7251
+ }),
7252
+ z.object({
7253
+ ...EventBase,
7254
+ type: z.literal("artifact"),
7255
+ path: z.string(),
7256
+ bytes: z.number(),
7257
+ sha256: z.string()
7258
+ }),
7259
+ z.object({
7260
+ ...EventBase,
7261
+ type: z.literal("done"),
7262
+ status: z.enum(["succeeded", "failed"]),
7263
+ resultSha256: z.string().nullable().optional()
7264
+ }),
7265
+ z.object({
7266
+ ...EventBase,
7267
+ type: z.literal("error"),
7268
+ stage: z.string(),
7269
+ code: z.string(),
7270
+ message: z.string()
7271
+ })
7272
+ ]);
7273
+ /**
6655
7274
  * What the gate decided about a birth, from the CALLER's point of view.
6656
7275
  *
6657
7276
  * Deliberately not `ConfirmationVerdict`: `undecided` is not a birth decision
@@ -9408,7 +10027,9 @@ var AddonHttpRouteSchema = z.object({
9408
10027
  "authenticated",
9409
10028
  "admin"
9410
10029
  ]).optional(),
9411
- description: z.string().optional()
10030
+ description: z.string().optional(),
10031
+ /** The hub limits this route per client address before the handler runs. */
10032
+ rateLimit: z.enum(["auth"]).optional()
9412
10033
  });
9413
10034
  /**
9414
10035
  * Cross-process route invocation envelope. The hub captures the
@@ -10372,6 +10993,37 @@ var BackupDestinationInfoSchema = z.object({
10372
10993
  lastError: z.string().optional()
10373
10994
  });
10374
10995
  /**
10996
+ * How an archive is encrypted (operator decision 2026-10-09): to a backup
10997
+ * PUBLIC key the hub holds, so only the operator's secret key opens it.
10998
+ * Absent on a plain `.tar.gz` — every archive made before encryption, or
10999
+ * with it off — which restores without a key.
11000
+ */
11001
+ var BackupArchiveEncryptionSchema = z.object({
11002
+ /** `x25519-hkdf-aes256gcm-stream-v1`. */
11003
+ scheme: z.string(),
11004
+ /** 16 hex — the backup key whose secret restores this archive. */
11005
+ keyId: z.string()
11006
+ });
11007
+ /**
11008
+ * The backup encryption setting. The hub keeps PUBLIC keys only: the active
11009
+ * one new archives are encrypted to, and the retired ones older archives
11010
+ * still need. The secret key is shown once, at `setupEncryption`.
11011
+ */
11012
+ var BackupEncryptionStatusSchema = z.object({
11013
+ enabled: z.boolean(),
11014
+ active: z.object({
11015
+ keyId: z.string(),
11016
+ publicKey: z.string(),
11017
+ createdAt: z.number()
11018
+ }).nullable(),
11019
+ /** Every key ever set up, newest first. */
11020
+ keys: z.array(z.object({
11021
+ keyId: z.string(),
11022
+ createdAt: z.number(),
11023
+ retiredAt: z.number().nullable()
11024
+ })).readonly()
11025
+ });
11026
+ /**
10375
11027
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
10376
11028
  * Same shape the destination drill-in renders. Sourced from the per-
10377
11029
  * location `manifests.json` (no per-archive tarball read required).
@@ -10383,7 +11035,9 @@ var BackupArchiveEntrySchema = z.object({
10383
11035
  sizeBytes: z.number(),
10384
11036
  label: z.string().optional(),
10385
11037
  /** Top-level locations included in the archive (db, addons, tls, …). */
10386
- locations: z.array(z.string()).readonly()
11038
+ locations: z.array(z.string()).readonly(),
11039
+ /** Present when the archive is encrypted; restore then needs that key. */
11040
+ encryption: BackupArchiveEncryptionSchema.optional()
10387
11041
  });
10388
11042
  var BackupEntrySchema = z.object({
10389
11043
  id: z.string(),
@@ -10392,7 +11046,8 @@ var BackupEntrySchema = z.object({
10392
11046
  label: z.string().optional(),
10393
11047
  createdAt: z.number(),
10394
11048
  sizeBytes: z.number(),
10395
- locations: z.array(z.string()).optional()
11049
+ locations: z.array(z.string()).optional(),
11050
+ encryption: BackupArchiveEncryptionSchema.optional()
10396
11051
  });
10397
11052
  var ArchiveEntrySchema = z.object({
10398
11053
  path: z.string(),
@@ -10647,11 +11302,58 @@ var backupCapability = {
10647
11302
  * this list and skips entries whose path doesn't start with
10648
11303
  * any of them.
10649
11304
  */
10650
- locations: z.array(z.string()).optional()
11305
+ locations: z.array(z.string()).optional(),
11306
+ /**
11307
+ * The backup SECRET key, for an encrypted archive (ignored for a plain
11308
+ * one). The archive is decrypted into the hub's private staging
11309
+ * before the restart; the key is not stored. A wrong or missing key
11310
+ * is refused before the archive is downloaded.
11311
+ */
11312
+ backupSecretKey: z.string().optional()
10651
11313
  }), z.void(), {
10652
11314
  kind: "mutation",
10653
11315
  auth: "admin"
10654
11316
  }),
11317
+ /** The encryption setting — public keys only. */
11318
+ getEncryption: method(z.void(), BackupEncryptionStatusSchema, { auth: "admin" }),
11319
+ /**
11320
+ * Turn encryption on, or replace the key: generates a new key pair, keeps
11321
+ * the PUBLIC key, and returns the SECRET key — once. It is never stored
11322
+ * on the hub and cannot be shown again; archives made from now on need
11323
+ * it to be restored. A previous key is retired: archives made with it
11324
+ * still need IT.
11325
+ */
11326
+ setupEncryption: method(z.object({}), z.object({
11327
+ keyId: z.string(),
11328
+ secretKey: z.string(),
11329
+ publicKey: z.string(),
11330
+ createdAt: z.number(),
11331
+ replacedKeyId: z.string().nullable()
11332
+ }), {
11333
+ kind: "mutation",
11334
+ auth: "admin"
11335
+ }),
11336
+ /** New archives are plain again. Existing encrypted archives keep needing their key. */
11337
+ disableEncryption: method(z.object({}), z.object({ disabledKeyId: z.string().nullable() }), {
11338
+ kind: "mutation",
11339
+ auth: "admin"
11340
+ }),
11341
+ /**
11342
+ * Does this secret key parse, and which backup key is it? A mutation so
11343
+ * the key never travels in a URL. Nothing is stored.
11344
+ */
11345
+ checkBackupKey: method(z.object({ backupSecretKey: z.string() }), z.object({
11346
+ valid: z.boolean(),
11347
+ keyId: z.string().nullable(),
11348
+ /** The hub knows this key (active or retired). */
11349
+ known: z.boolean(),
11350
+ /** It is the key new archives are encrypted to. */
11351
+ active: z.boolean(),
11352
+ error: z.string().optional()
11353
+ }), {
11354
+ kind: "mutation",
11355
+ auth: "admin"
11356
+ }),
10655
11357
  delete: method(z.object({
10656
11358
  destinationId: z.string(),
10657
11359
  backupId: z.string()
@@ -14162,22 +14864,87 @@ var CustomModelDescriptorSchema = z.object({
14162
14864
  stepId: z.string(),
14163
14865
  entry: ModelCatalogEntrySchema
14164
14866
  });
14867
+ /** One regular file of an imported bundle, as the producer measured it. */
14868
+ var ModelBundleFileSchema = z.object({
14869
+ /** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
14870
+ path: z.string().min(1),
14871
+ bytes: z.number().int().nonnegative(),
14872
+ sha256: z.string().regex(/^[0-9a-f]{64}$/)
14873
+ });
14874
+ var ImportModelBundleInputSchema = z.object({
14875
+ /** The registry provider that keeps the model (Model Studio). */
14876
+ addonId: z.string(),
14877
+ stepId: z.string().min(1),
14878
+ /**
14879
+ * The catalog rows to register, complete, every format URL already
14880
+ * `camstack-local://<modelId>/<file>` naming a file of the bundle. One
14881
+ * bundle may carry several rows (a base and its INT8 variant).
14882
+ */
14883
+ entries: z.array(ModelCatalogEntrySchema).min(1).max(4),
14884
+ /** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
14885
+ bundle: z.object({
14886
+ ticket: PeerBytesTicketSchema,
14887
+ bytes: z.number().int().positive(),
14888
+ sha256: z.string().regex(/^[0-9a-f]{64}$/)
14889
+ }),
14890
+ /** Every file the tar must hold; anything missing, extra or different is refused. */
14891
+ files: z.array(ModelBundleFileSchema).min(1).max(64),
14892
+ /** Convert missing engine formats and push the model to every agent afterwards. */
14893
+ distribute: z.boolean()
14894
+ });
14895
+ var ImportModelBundleResultSchema = z.discriminatedUnion("kind", [z.object({
14896
+ kind: z.literal("imported"),
14897
+ modelIds: z.array(z.string()).readonly(),
14898
+ distributed: z.array(z.object({
14899
+ modelId: z.string(),
14900
+ nodeId: z.string(),
14901
+ format: z.enum(MODEL_FORMATS),
14902
+ ok: z.boolean(),
14903
+ error: z.string().optional()
14904
+ })).readonly()
14905
+ }), z.object({
14906
+ kind: z.literal("refused"),
14907
+ reason: z.enum([
14908
+ "transfer-failed",
14909
+ "checksum-mismatch",
14910
+ "bundle-mismatch",
14911
+ "invalid-entry",
14912
+ "id-taken"
14913
+ ]),
14914
+ detail: z.string()
14915
+ })]);
14165
14916
  var customModelRegistryCapability = {
14166
14917
  name: "custom-model-registry",
14167
14918
  scope: "system",
14168
14919
  mode: "collection",
14169
14920
  internal: true,
14170
- methods: {
14171
- /**
14172
- * `internal: true` did not gate the mount (see the 2026-08-26 note on
14173
- * `data-store-provider`) — this was reachable on the AppRouter by ANY
14174
- * authenticated session at the default `auth: 'protected'`. The only
14175
- * consumer, the detection-pipeline's model resolution
14176
- * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
14177
- * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
14178
- * never tRPC — so `auth: 'admin'` costs it nothing.
14179
- */
14180
- listModels: method(z.void(), z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }) }
14921
+ methods: {
14922
+ /**
14923
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
14924
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
14925
+ * authenticated session at the default `auth: 'protected'`. The only
14926
+ * consumer, the detection-pipeline's model resolution
14927
+ * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
14928
+ * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
14929
+ * never tRPC — so `auth: 'admin'` costs it nothing.
14930
+ */
14931
+ listModels: method(z.void(), z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }),
14932
+ /**
14933
+ * Register a model whose files arrive as ONE bundle tar (D806): the tar
14934
+ * is taken over `ctx.peerBytes`, checked against `bundle.sha256` and
14935
+ * every row of `files`, unpacked into the hub's flat modelsDir (directory
14936
+ * formats included), each format's `.revision` record written, the rows
14937
+ * upserted under the operator's name, the hub marked available and —
14938
+ * when asked — engine formats converted and the model distributed. A
14939
+ * model id already registered by another source is refused, never
14940
+ * overwritten.
14941
+ */
14942
+ importModelBundle: method(ImportModelBundleInputSchema, ImportModelBundleResultSchema, {
14943
+ kind: "mutation",
14944
+ auth: "admin",
14945
+ timeoutMs: 30 * 6e4
14946
+ })
14947
+ }
14181
14948
  };
14182
14949
  /**
14183
14950
  * Query filter for settings-store collections.
@@ -14984,6 +15751,42 @@ var KeychainKeySourceSchema = z.object({
14984
15751
  */
14985
15752
  sharesBackupWithDatabase: z.boolean()
14986
15753
  });
15754
+ /**
15755
+ * Who reset the keychain: an operator from System → Secrets (`user:<id>`), or
15756
+ * the one-shot `<dataDir>/.keychain/RESET_KEYCHAIN` trigger file at boot
15757
+ * (`trigger-file`) — the only way back into a hub whose master key is lost,
15758
+ * because a locked keychain leaves no session that could reach the page (D797).
15759
+ */
15760
+ var KEYCHAIN_RESET_TRIGGER_ACTOR = "trigger-file";
15761
+ /**
15762
+ * One keychain reset, as the Secrets page reports it until the operator
15763
+ * acknowledges it. Never a value: refs, counts and table names only.
15764
+ */
15765
+ var KeychainResetRecordSchema = z.object({
15766
+ id: z.string(),
15767
+ at: z.number(),
15768
+ /** `user:<id>` or {@link KEYCHAIN_RESET_TRIGGER_ACTOR}. */
15769
+ actor: z.string(),
15770
+ /** Why the keychain was locked when it was reset; `null` when it was not locked. */
15771
+ lockedReason: z.string().nullable(),
15772
+ /** Values moved into the archive table (ciphertext under the lost key). */
15773
+ archived: z.number().int(),
15774
+ /** `keychain:secrets-orphaned-<ts>`, or `null` when nothing was stored. */
15775
+ archivedCollection: z.string().nullable(),
15776
+ /**
15777
+ * `keychain:keys-orphaned-<ts>`: the wrapped data keys of the archive, kept
15778
+ * so the archive can still be opened if the lost master key turns up.
15779
+ */
15780
+ archivedKeysCollection: z.string().nullable(),
15781
+ /**
15782
+ * `system-settings` keys whose marker the reset cleared — the cluster
15783
+ * secret's: a marker left pointing at an archived value would refuse every
15784
+ * agent forever (D797).
15785
+ */
15786
+ clearedSettingsKeys: z.array(z.string()).readonly(),
15787
+ /** When an operator dismissed the notice on the Secrets page; `null` until then. */
15788
+ acknowledgedAt: z.number().nullable()
15789
+ });
14987
15790
  var KeychainStatusSchema = z.object({
14988
15791
  keychain: KeychainStateSchema,
14989
15792
  /** The source of the master key in use; `null` while locked. */
@@ -15001,7 +15804,9 @@ var KeychainStatusSchema = z.object({
15001
15804
  /** Wrapped copies of the data key under a previous master key, kept until purged. */
15002
15805
  retiredWraps: z.number().int(),
15003
15806
  recoveryKeyDownloadedAt: z.number().nullable(),
15004
- secretCount: z.number().int()
15807
+ secretCount: z.number().int(),
15808
+ /** The most recent reset, acknowledged or not; `null` when the keychain was never reset. */
15809
+ lastReset: KeychainResetRecordSchema.nullable()
15005
15810
  });
15006
15811
  /**
15007
15812
  * Whether a stored value is still pointed at by its owner's settings.
@@ -15023,13 +15828,14 @@ var KeychainAuditActionSchema = z.enum([
15023
15828
  "download-recovery-key",
15024
15829
  "delete-orphans",
15025
15830
  "reset",
15831
+ "acknowledge-reset",
15026
15832
  "initialize"
15027
15833
  ]);
15028
15834
  var KeychainAuditEntrySchema = z.object({
15029
15835
  id: z.string(),
15030
15836
  at: z.number(),
15031
15837
  action: KeychainAuditActionSchema,
15032
- /** `addon:<id>` | `user:<id>` | `system`. */
15838
+ /** `addon:<id>` | `user:<id>` | `system` | `trigger-file`. */
15033
15839
  actor: z.string(),
15034
15840
  ref: z.string().nullable(),
15035
15841
  detail: z.string().nullable()
@@ -15139,6 +15945,21 @@ var keychainAdminCapability = {
15139
15945
  kind: "mutation",
15140
15946
  auth: "admin",
15141
15947
  caller: "required"
15948
+ }),
15949
+ /**
15950
+ * What the last reset left to re-enter: the archived refs (owner, scope,
15951
+ * field — never a value) that hold no value again yet. Process-held refs
15952
+ * the hub regenerates itself (the session signing key) are not listed.
15953
+ */
15954
+ resetReport: method(z.object({}), z.object({
15955
+ reset: KeychainResetRecordSchema.nullable(),
15956
+ pendingReentry: z.array(KeychainSecretMetaSchema).readonly()
15957
+ }), { auth: "admin" }),
15958
+ /** Dismiss the "keychain was reset" notice. Audited. */
15959
+ acknowledgeReset: method(z.object({}), z.object({ acknowledged: z.boolean() }), {
15960
+ kind: "mutation",
15961
+ auth: "admin",
15962
+ caller: "required"
15142
15963
  })
15143
15964
  }
15144
15965
  };
@@ -18395,8 +19216,8 @@ var PUBLIC_ENDPOINT_HTTP_REASON = "The public endpoint needs an https:// address
18395
19216
  * May `baseUrl` carry `role`? The address check above, plus two role rules:
18396
19217
  * the app link carries `notificationTaps` only (it is not a hub address), and
18397
19218
  * a plain-http address carries `publicEndpoint` only when the operator
18398
- * explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password or
18399
- * magic-link sign-in works over http, a passkey or an OIDC callback does not.
19219
+ * explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password
19220
+ * sign-in works over http, a passkey or an OIDC callback does not.
18400
19221
  */
18401
19222
  function checkRoleAddress(role, baseUrl, options = {}) {
18402
19223
  if (isAppLinkBase(baseUrl)) return role === "notificationTaps" ? {
@@ -19426,7 +20247,7 @@ var logDestinationCapability = {
19426
20247
  * `login-method` — collection cap through which auth addons contribute
19427
20248
  * their pre-auth login surfaces to the login page. This is the SINGLE,
19428
20249
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
19429
- * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
20250
+ * every auth addon (OIDC, WebAuthn/passkey) registers a
19430
20251
  * `login-method` provider and the PUBLIC `auth.listLoginMethods`
19431
20252
  * procedure aggregates them for the unauthenticated login page.
19432
20253
  *
@@ -19434,8 +20255,7 @@ var logDestinationCapability = {
19434
20255
  *
19435
20256
  * - `redirect` — a declarative button. The login page renders a generic
19436
20257
  * button that navigates to `startUrl` (an addon-owned HTTP route).
19437
- * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
19438
- * ZERO shell-side JS. A future SSO addon plugs in the same way — the
20258
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) with ZERO shell-side JS. A future SSO addon plugs in the same way — the
19439
20259
  * login page needs NO change.
19440
20260
  *
19441
20261
  * - `widget` — a Module-Federation widget the login page mounts (via
@@ -19454,8 +20274,8 @@ var logDestinationCapability = {
19454
20274
  * decision.
19455
20275
  *
19456
20276
  * Every contribution carries a `stage`:
19457
- * - `primary` — shown on the first credentials screen (OIDC /
19458
- * magic-link buttons; a future usernameless passkey).
20277
+ * - `primary` — shown on the first credentials screen (OIDC
20278
+ * buttons; a usernameless passkey).
19459
20279
  * - `second-factor` — shown AFTER the password leg, gated on the
19460
20280
  * returned `factors` (passkey-as-2FA today).
19461
20281
  *
@@ -19467,7 +20287,7 @@ var logDestinationCapability = {
19467
20287
  var LoginStageEnum = z.enum(["primary", "second-factor"]);
19468
20288
  /**
19469
20289
  * A declarative redirect button — the login page navigates to `startUrl`.
19470
- * OIDC and magic-link contribute this; a future SSO addon does too.
20290
+ * OIDC contributes this; a future SSO addon does too.
19471
20291
  */
19472
20292
  var RedirectLoginMethodSchema = z.object({
19473
20293
  kind: z.literal("redirect"),
@@ -19484,7 +20304,7 @@ var RedirectLoginMethodSchema = z.object({
19484
20304
  * The provider names an external identity a signed-in user can link to
19485
20305
  * their own account (D782): `auth.listLinkableProviders` /
19486
20306
  * `beginExternalLink` offer exactly the redirect methods that set this,
19487
- * whatever their id. Absent / false — a login button only (magic-link).
20307
+ * whatever their id. Absent / false — a login button only.
19488
20308
  */
19489
20309
  linkable: z.boolean().optional()
19490
20310
  });
@@ -23755,8 +24575,7 @@ var notificationRulesCapability = {
23755
24575
  * `super_admin` — collapsed onto the single boolean because every
23756
24576
  * non-admin caller is governed by the same scope-check rules; the
23757
24577
  * distinction was only ever used to route role-specific procedures
23758
- * which we no longer have. Agents are admin sessions issued via
23759
- * `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
24578
+ * which we no longer have. CLI / webhook integrations are `cst_*` tokens
23760
24579
  * with `isAdmin: false` + an explicit scope list.
23761
24580
  */
23762
24581
  /** Per-method access flavour. Materialised on every cap method via codegen. */
@@ -27037,6 +27856,26 @@ var pipelineAnalyticsCapability = {
27037
27856
  auth: "admin",
27038
27857
  timeoutMs: 60 * 6e4
27039
27858
  }),
27859
+ /**
27860
+ * OFFER the annotated archive instead of pushing it (D805): the same tar
27861
+ * `writeRetrainArchive` streams, behind a one-shot `ctx.peerBytes` ticket
27862
+ * the caller dials at once (same host). For a consumer that cannot expose
27863
+ * an upload URL — a training provider streaming straight into its cloud.
27864
+ * The answer is the plan; the bytes and their sha256 are the consumer's
27865
+ * to measure as they arrive.
27866
+ */
27867
+ offerRetrainArchive: method(z.object({
27868
+ filter: RetrainExportFilterSchema,
27869
+ layout: RetrainExportLayoutSchema
27870
+ }), z.object({
27871
+ ticket: PeerBytesTicketSchema,
27872
+ frames: z.number().int(),
27873
+ annotations: z.number().int()
27874
+ }), {
27875
+ kind: "mutation",
27876
+ auth: "admin",
27877
+ timeoutMs: 12e4
27878
+ }),
27040
27879
  /** The counts a selection would freeze — class × camera × day/night,
27041
27880
  * completeness — without freezing anything. Index reads only. */
27042
27881
  previewRetrainDataset: method(z.object({
@@ -31783,8 +32622,8 @@ version: z.string().optional() }), ServerUpdateActionResultSchema, {
31783
32622
  * `smtp-provider` — pluggable email delivery surface.
31784
32623
  *
31785
32624
  * Collection cap: a deployment may install multiple SMTP relays (e.g.
31786
- * one for transactional / one for notifications). Consumers (magic-link,
31787
- * notifier addons, …) walk the collection and pick the first
32625
+ * one for transactional / one for notifications). Consumers (notifier
32626
+ * addons, …) walk the collection and pick the first
31788
32627
  * ready provider OR target a specific addonId.
31789
32628
  *
31790
32629
  * The shape stays small on purpose — `sendEmail` is enough for the
@@ -32080,7 +32919,7 @@ targets: z.array(z.object({
32080
32919
  };
32081
32920
  /**
32082
32921
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
32083
- * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
32922
+ * providers (OIDC, SAML, …) mint an HMAC-signed token
32084
32923
  * carrying the post-callback claims before redirecting back to
32085
32924
  * `/api/auth/sso/finish`.
32086
32925
  *
@@ -32143,7 +32982,14 @@ var SsoBridgeClaimsSchema = z.object({
32143
32982
  * Verified `iss` of the id_token behind an OIDC login bridge (D782). A
32144
32983
  * subject is unique only per issuer, so a link is bound to it.
32145
32984
  */
32146
- issuer: z.string().optional()
32985
+ issuer: z.string().optional(),
32986
+ /**
32987
+ * SHA-256 (hex) of the `camstack_sso_state` cookie the IdP `/start` set in
32988
+ * the browser that began this sign-in. `/api/auth/sso/finish` requires the
32989
+ * request to present the matching cookie — a login bridge is useless in
32990
+ * any other browser.
32991
+ */
32992
+ browserBinding: z.string().optional()
32147
32993
  });
32148
32994
  var ssoBridgeCapability = {
32149
32995
  name: "sso-bridge",
@@ -32161,7 +33007,7 @@ var ssoBridgeCapability = {
32161
33007
  * `/api/auth/sso/finish` to become admin — a full privilege escalation
32162
33008
  * through the exact surface this cap exists to close (see the module
32163
33009
  * docblock). Every real caller (`oauth-grants.ts`, `main.ts`
32164
- * registration, the OIDC/magic-link addons) reaches this through
33010
+ * registration, the OIDC addon) reaches this through
32165
33011
  * `ctx.api` / the in-process registry — never through tRPC — so
32166
33012
  * `auth: 'admin'` costs those callers nothing.
32167
33013
  */
@@ -46957,16 +47803,6 @@ var ApiKeySummarySchema = z.object({
46957
47803
  createdAt: z.number(),
46958
47804
  lastUsedAt: z.number().optional()
46959
47805
  });
46960
- var CreateApiKeyInputSchema = z.object({
46961
- label: z.string(),
46962
- isAdmin: z.boolean().default(false),
46963
- allowedProviders: z.union([z.array(z.string()), z.literal("*")]).optional(),
46964
- allowedDevices: z.record(z.string(), z.union([z.array(z.string()), z.literal("*")])).optional()
46965
- });
46966
- var CreateApiKeyResultSchema = z.object({
46967
- token: z.string(),
46968
- record: ApiKeySummarySchema
46969
- });
46970
47806
  var ScopedTokenSummarySchema = z.object({
46971
47807
  id: z.string(),
46972
47808
  userId: z.string(),
@@ -47078,20 +47914,11 @@ var userManagementCapability = {
47078
47914
  access: "view"
47079
47915
  }),
47080
47916
  listApiKeys: method(z.void(), z.array(ApiKeySummarySchema), { auth: "admin" }),
47081
- createApiKey: method(CreateApiKeyInputSchema, CreateApiKeyResultSchema, {
47082
- kind: "mutation",
47083
- auth: "admin",
47084
- access: "create"
47085
- }),
47086
47917
  revokeApiKey: method(z.object({ id: z.string() }), z.object({ success: z.literal(true) }), {
47087
47918
  kind: "mutation",
47088
47919
  auth: "admin",
47089
47920
  access: "delete"
47090
47921
  }),
47091
- validateApiKey: method(z.object({ token: z.string() }), ApiKeySummarySchema.nullable(), {
47092
- kind: "mutation",
47093
- access: "view"
47094
- }),
47095
47922
  createScopedToken: method(CreateScopedTokenInputSchema, CreateScopedTokenResultSchema, {
47096
47923
  kind: "mutation",
47097
47924
  auth: "admin",
@@ -47181,20 +48008,6 @@ var userManagementCapability = {
47181
48008
  userId: z.string(),
47182
48009
  issuer: z.string()
47183
48010
  }).nullable(), { auth: "admin" }),
47184
- /**
47185
- * Link `(provider, subject)` to `userId`. Idempotent for the same user;
47186
- * REFUSES (throws) an identity already linked to another user, and a
47187
- * user that does not exist.
47188
- */
47189
- linkExternalIdentity: method(ExternalIdentityKeySchema.extend({
47190
- userId: z.string(),
47191
- issuer: z.string().min(1),
47192
- label: z.string().optional()
47193
- }), z.object({ success: z.literal(true) }), {
47194
- kind: "mutation",
47195
- auth: "admin",
47196
- access: "create"
47197
- }),
47198
48011
  /** Remove one link of `userId`. `success: false` when it was not linked to that user. */
47199
48012
  unlinkExternalIdentity: method(ExternalIdentityKeySchema.extend({ userId: z.string() }), z.object({ success: z.boolean() }), {
47200
48013
  kind: "mutation",
@@ -47923,6 +48736,531 @@ var zoneRulesCapability = {
47923
48736
  */
47924
48737
  durability: "restored"
47925
48738
  };
48739
+ /**
48740
+ * `training-provider` — ONE cloud that can run the CamStack trainer.
48741
+ *
48742
+ * A thin, mockable translation layer: it knows how to run a container with a
48743
+ * GPU, move a dataset in, read the job's stdout and hand artefacts back. It
48744
+ * knows nothing about datasets, splits, floors, cost policy or Model Studio —
48745
+ * those are the `training` orchestrator's (D805). Every rule written there
48746
+ * once is what makes a second provider (RunPod, a local NVIDIA node) a new
48747
+ * manifest entry and nothing else.
48748
+ *
48749
+ * Collection, routed by the `addonId` in every single-row input (the `llm` /
48750
+ * `notification-output` posture); `describe` concat-fans across providers.
48751
+ * Every method is `admin`: these spend money.
48752
+ *
48753
+ * Every cloud object a provider creates is tagged with the orchestrator's
48754
+ * `jobKey`, so a retried submit finds the job it already started instead of
48755
+ * starting a second GPU, and `listJobs` can find orphans.
48756
+ *
48757
+ * Bytes never ride a cap field: the dataset arrives as a `ctx.peerBytes`
48758
+ * ticket the producer (pipeline-analytics) minted, and artefacts leave as a
48759
+ * ticket the provider mints (D613).
48760
+ */
48761
+ var TrainingGpuOptionSchema = z.object({
48762
+ id: z.string(),
48763
+ label: z.string(),
48764
+ /** List price, USD per GPU-hour, before any region multiplier. */
48765
+ usdPerHour: z.number().nonnegative(),
48766
+ vramGb: z.number().nonnegative()
48767
+ });
48768
+ var TrainingRegionOptionSchema = z.object({
48769
+ /** `''` = any region (the provider's choice). */
48770
+ id: z.string(),
48771
+ label: z.string(),
48772
+ priceMultiplier: z.number().positive()
48773
+ });
48774
+ /**
48775
+ * The provider's per-account settings, as the orchestrator reads them. They
48776
+ * live in the PROVIDER addon's settings (operator decision 2026-10-08: they
48777
+ * depend on the user's account), never hard-coded in the orchestrator.
48778
+ */
48779
+ var TrainingProviderDefaultsSchema = z.object({
48780
+ gpuType: z.string(),
48781
+ region: z.string(),
48782
+ costCapUsd: z.number().nonnegative(),
48783
+ maxRuntimeSec: z.number().int().positive(),
48784
+ /** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
48785
+ failedDatasetGraceHours: z.number().nonnegative()
48786
+ });
48787
+ var TrainingProviderDescriptorSchema = z.object({
48788
+ addonId: z.string(),
48789
+ /** `modal`, `runpod`, `fake`, … */
48790
+ kind: z.string(),
48791
+ label: z.string(),
48792
+ gpuTypes: z.array(TrainingGpuOptionSchema).readonly(),
48793
+ regions: z.array(TrainingRegionOptionSchema).readonly(),
48794
+ maxDatasetBytes: z.number().int().positive(),
48795
+ maxRuntimeSec: z.number().int().positive(),
48796
+ freeTierNote: z.string().optional(),
48797
+ /** Where the data goes, in one sentence the confirm dialog repeats. */
48798
+ dataResidencyNote: z.string(),
48799
+ /** Credentials are present — never the values. */
48800
+ configured: z.boolean(),
48801
+ defaults: TrainingProviderDefaultsSchema
48802
+ });
48803
+ var TrainingCredentialCheckSchema = z.object({
48804
+ ok: z.boolean(),
48805
+ /** Workspace / account name, when the provider says. */
48806
+ account: z.string().optional(),
48807
+ reason: z.enum([
48808
+ "missing",
48809
+ "invalid",
48810
+ "forbidden",
48811
+ "network",
48812
+ "quota"
48813
+ ]).optional(),
48814
+ detail: z.string().optional()
48815
+ });
48816
+ var ProviderJobStateSchema = z.enum([
48817
+ "pending",
48818
+ "starting",
48819
+ "running",
48820
+ "succeeded",
48821
+ "failed",
48822
+ "cancelled",
48823
+ "lost"
48824
+ ]);
48825
+ var ProviderJobStatusSchema = z.object({
48826
+ jobKey: z.string(),
48827
+ providerJobId: z.string().optional(),
48828
+ state: ProviderJobStateSchema,
48829
+ startedAt: z.number().optional(),
48830
+ endedAt: z.number().optional(),
48831
+ exitCode: z.number().int().optional(),
48832
+ failureReason: z.string().optional(),
48833
+ gpuType: z.string(),
48834
+ region: z.string().optional(),
48835
+ billedSeconds: z.number().nonnegative(),
48836
+ costUsd: z.number().nonnegative(),
48837
+ /** True when `costUsd` is runtime × list price, not a billing answer. */
48838
+ costIsEstimate: z.boolean(),
48839
+ lastFetchedAt: z.number()
48840
+ });
48841
+ /**
48842
+ * The trainer the job runs, by SOURCE revision: the provider turns it into an
48843
+ * image its own way (Modal builds the published Dockerfile at that revision;
48844
+ * a registry-pull provider maps it to a tag). Recorded on the job and echoed
48845
+ * in `result.json`.
48846
+ */
48847
+ var TrainerSourceRefSchema = z.object({
48848
+ /** Hugging Face repo id, e.g. `camstack/camstack-trainer`. */
48849
+ repo: z.string().min(1),
48850
+ /** A commit sha (never a branch) — the job must be reproducible. */
48851
+ revision: z.string().regex(/^[0-9a-f]{7,64}$/)
48852
+ });
48853
+ var JobRef = {
48854
+ addonId: z.string(),
48855
+ jobKey: z.string().uuid()
48856
+ };
48857
+ var TrainingLogLineSchema = z.object({
48858
+ ts: z.number(),
48859
+ text: z.string()
48860
+ });
48861
+ var trainingProviderCapability = {
48862
+ name: "training-provider",
48863
+ scope: "system",
48864
+ mode: "collection",
48865
+ internal: true,
48866
+ methods: {
48867
+ /** Every provider's offer and settings. Concat-fans across providers. */
48868
+ describe: method(z.object({}), z.array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }),
48869
+ /** Call the provider with the stored credentials. Never returns them. */
48870
+ validateCredentials: method(z.object({ addonId: z.string() }), TrainingCredentialCheckSchema, {
48871
+ kind: "mutation",
48872
+ auth: "admin",
48873
+ timeoutMs: 3e4
48874
+ }),
48875
+ /** USD range for a run of `gpuSeconds` on `gpuType` in `region`. */
48876
+ estimateJob: method(z.object({
48877
+ addonId: z.string(),
48878
+ gpuType: z.string(),
48879
+ region: z.string(),
48880
+ datasetBytes: z.number().int().nonnegative(),
48881
+ gpuSecondsLow: z.number().nonnegative(),
48882
+ gpuSecondsHigh: z.number().nonnegative()
48883
+ }), z.object({
48884
+ usdLow: z.number().nonnegative(),
48885
+ usdHigh: z.number().nonnegative(),
48886
+ notes: z.array(z.string()).readonly()
48887
+ }), { auth: "admin" }),
48888
+ /**
48889
+ * Take the dataset archive behind `ticket` (minted by pipeline-analytics,
48890
+ * same host) and land it as the job's dataset in the cloud, streamed —
48891
+ * nothing is staged on the hub's disk. Idempotent per `jobKey`: a second
48892
+ * call replaces a partial upload. Answers what actually landed.
48893
+ */
48894
+ uploadDataset: method(z.object({
48895
+ ...JobRef,
48896
+ ticket: PeerBytesTicketSchema
48897
+ }), z.object({
48898
+ bytes: z.number().int().nonnegative(),
48899
+ sha256: z.string()
48900
+ }), {
48901
+ kind: "mutation",
48902
+ auth: "admin",
48903
+ timeoutMs: 120 * 6e4
48904
+ }),
48905
+ /**
48906
+ * Start the trainer on a GPU. Looks the job up by `jobKey` first, so a
48907
+ * retried submit never starts a second GPU. `jobSpecJson` is written next
48908
+ * to the dataset as `job.json`; `maxRuntimeSec` is enforced by the
48909
+ * provider (a hard timeout), not by the hub.
48910
+ */
48911
+ submitJob: method(z.object({
48912
+ ...JobRef,
48913
+ trainer: TrainerSourceRefSchema,
48914
+ jobSpecJson: z.string().min(2).max(64e3),
48915
+ gpuType: z.string(),
48916
+ region: z.string(),
48917
+ maxRuntimeSec: z.number().int().positive()
48918
+ }), ProviderJobStatusSchema, {
48919
+ kind: "mutation",
48920
+ auth: "admin",
48921
+ timeoutMs: 30 * 6e4
48922
+ }),
48923
+ getJob: method(z.object(JobRef), ProviderJobStatusSchema, {
48924
+ auth: "admin",
48925
+ timeoutMs: 6e4
48926
+ }),
48927
+ /** Stdout/stderr since `cursor` (opaque), oldest first, at most `max` lines. */
48928
+ listJobEvents: method(z.object({
48929
+ ...JobRef,
48930
+ cursor: z.string().optional(),
48931
+ max: z.number().int().min(1).max(500)
48932
+ }), z.object({
48933
+ lines: z.array(TrainingLogLineSchema).readonly(),
48934
+ cursor: z.string()
48935
+ }), {
48936
+ auth: "admin",
48937
+ timeoutMs: 6e4
48938
+ }),
48939
+ /** Stop the job. Idempotent: an unknown or finished job is a no-op. */
48940
+ cancelJob: method(z.object(JobRef), z.object({ ok: z.literal(true) }), {
48941
+ kind: "mutation",
48942
+ auth: "admin",
48943
+ timeoutMs: 6e4
48944
+ }),
48945
+ /** The job's `result.json`, as text (small, JSON). `found: false` before it exists. */
48946
+ readResult: method(z.object(JobRef), z.object({
48947
+ found: z.boolean(),
48948
+ text: z.string().optional()
48949
+ }), {
48950
+ kind: "mutation",
48951
+ auth: "admin",
48952
+ timeoutMs: 5 * 6e4
48953
+ }),
48954
+ /**
48955
+ * Offer one output file of the job (`bundle.tar`) to the caller as a
48956
+ * one-shot `ctx.peerBytes` ticket, streamed from the cloud — the provider
48957
+ * stages nothing on disk. The caller dials it at once.
48958
+ */
48959
+ offerArtifact: method(z.object({
48960
+ ...JobRef,
48961
+ path: z.enum(["bundle.tar"])
48962
+ }), z.object({ ticket: PeerBytesTicketSchema }), {
48963
+ kind: "mutation",
48964
+ auth: "admin",
48965
+ timeoutMs: 5 * 6e4
48966
+ }),
48967
+ /** Delete every cloud object tagged with `jobKey` (the dataset first). Idempotent. */
48968
+ cleanupJob: method(z.object(JobRef), z.object({ deleted: z.array(z.string()).readonly() }), {
48969
+ kind: "mutation",
48970
+ auth: "admin",
48971
+ timeoutMs: 5 * 6e4
48972
+ }),
48973
+ /** Every job the provider can still see — the orphan sweep diffs it against our records. */
48974
+ listJobs: method(z.object({ addonId: z.string() }), z.array(ProviderJobStatusSchema).readonly(), {
48975
+ auth: "admin",
48976
+ timeoutMs: 2 * 6e4
48977
+ })
48978
+ }
48979
+ };
48980
+ /**
48981
+ * `training` — the cloud-training orchestrator (D805). Hub-only singleton,
48982
+ * provided by `addon-training`.
48983
+ *
48984
+ * It owns what every provider must agree on, written once:
48985
+ *
48986
+ * - **Dataset versions.** Taken over from pipeline-analytics (D796): the rows
48987
+ * live here (D808); pipeline-analytics still CUTS a version (it owns the corpus and
48988
+ * the export plan) and reaches this store through the five row methods
48989
+ * below, so the Retrain page and the export did not change.
48990
+ * - **Jobs.** A persisted state machine, a reconcile loop against the
48991
+ * provider, a cost guard, a daily orphan sweep and the retention rules.
48992
+ * - **The return flow.** Verified artefacts → Model Studio
48993
+ * (`custom-model-registry.importModelBundle`) → canary on cameras →
48994
+ * rollback. Nothing is ever registered automatically.
48995
+ *
48996
+ * Every method is `admin`.
48997
+ */
48998
+ /** A version WITH its frame list — what the export and a job read. */
48999
+ var TrainingDatasetVersionRowSchema = RetrainDatasetVersionSchema.omit({ frameCount: true }).extend({ frameIds: z.array(z.string()).max(2e4).readonly() });
49000
+ /** A base model a detector job can fine-tune, and the baseline it is judged against. */
49001
+ var TrainingBaseModelSchema = z.object({
49002
+ modelId: z.string(),
49003
+ label: z.string(),
49004
+ weights: z.string(),
49005
+ imgsz: z.number().int(),
49006
+ baselineOnnxUrl: z.string(),
49007
+ baselineFloors: z.record(z.string(), z.number()),
49008
+ /** The base the fleet runs most — the UI's default. */
49009
+ recommended: z.boolean()
49010
+ });
49011
+ var TrainingPresetSchema = z.enum([
49012
+ "quick",
49013
+ "standard",
49014
+ "thorough"
49015
+ ]);
49016
+ var TrainingJobRequestSchema = z.object({
49017
+ datasetVersionId: z.string().min(1),
49018
+ /** The `training-provider` addon that runs it. */
49019
+ addonId: z.string().min(1),
49020
+ gpuType: z.string().min(1),
49021
+ region: z.string(),
49022
+ baseModelId: z.string().min(1),
49023
+ preset: TrainingPresetSchema,
49024
+ /** Freeze the backbone's first N layers; `null` = the preset's choice. */
49025
+ freeze: z.number().int().nonnegative().nullable(),
49026
+ /** Cameras held out whole for evaluation; `null` = the orchestrator's default. */
49027
+ holdoutCameras: z.array(z.number().int()).nullable()
49028
+ });
49029
+ var TrainingSplitPlanSchema = z.object({
49030
+ holdoutCameras: z.array(z.number().int()).readonly(),
49031
+ /** Used when no camera is held out: the last N days of every camera. */
49032
+ holdoutLastDays: z.number().int().nonnegative(),
49033
+ /** Holdout subjects per class (camera holdout only; `null` = known only after the split). */
49034
+ holdoutSupport: z.record(z.string(), z.number().int()).nullable(),
49035
+ trainSubjects: z.number().int()
49036
+ });
49037
+ var TrainingRefusalReasonSchema = z.enum([
49038
+ "unknown-dataset",
49039
+ "unknown-provider",
49040
+ "provider-not-configured",
49041
+ "unknown-base-model",
49042
+ "unknown-gpu",
49043
+ "dataset-too-large",
49044
+ "no-training-subjects",
49045
+ "job-already-running"
49046
+ ]);
49047
+ var TrainingJobPlanSchema = z.object({
49048
+ refusal: z.object({
49049
+ reason: TrainingRefusalReasonSchema,
49050
+ detail: z.string()
49051
+ }).nullable(),
49052
+ warnings: z.array(z.string()).readonly(),
49053
+ split: TrainingSplitPlanSchema.nullable(),
49054
+ estimate: z.object({
49055
+ datasetBytes: z.number().int(),
49056
+ uploadMinutesAt20Mbps: z.number(),
49057
+ gpuSecondsLow: z.number(),
49058
+ gpuSecondsHigh: z.number(),
49059
+ usdLow: z.number(),
49060
+ usdHigh: z.number(),
49061
+ notes: z.array(z.string()).readonly()
49062
+ }).nullable(),
49063
+ /** Pre-filled from the provider's settings; the operator may lower or raise them. */
49064
+ defaults: z.object({
49065
+ maxCostUsd: z.number(),
49066
+ maxRuntimeSec: z.number().int()
49067
+ }).nullable(),
49068
+ /** The sentence the confirm dialog repeats: where the frames go and when they are deleted. */
49069
+ dataNotice: z.string()
49070
+ });
49071
+ /**
49072
+ * Orchestrator-owned states, persisted. `uploading` covers exporting the
49073
+ * archive (it is streamed, never staged). Terminal: `failed`, `cancelled`,
49074
+ * `lost`, `expired`, `discarded`. `registered` and `canary` are where a
49075
+ * result lives after the operator acted on it.
49076
+ */
49077
+ var TrainingJobStateSchema = z.enum([
49078
+ "uploading",
49079
+ "submitted",
49080
+ "starting",
49081
+ "running",
49082
+ "collecting",
49083
+ "ready",
49084
+ "registered",
49085
+ "canary",
49086
+ "failed",
49087
+ "cancelling",
49088
+ "cancelled",
49089
+ "lost",
49090
+ "expired",
49091
+ "discarded"
49092
+ ]);
49093
+ var TrainingFailureReasonSchema = z.enum([
49094
+ "cost-cap",
49095
+ "runtime-cap",
49096
+ "upload-failed",
49097
+ "submit-failed",
49098
+ "trainer-failed",
49099
+ "result-invalid",
49100
+ "checksum-mismatch",
49101
+ "fetch-failed",
49102
+ "provider-error",
49103
+ "hub-restart-during-upload"
49104
+ ]);
49105
+ var TrainingProgressPointSchema = z.object({
49106
+ epoch: z.number().int(),
49107
+ map50: z.number().nullable(),
49108
+ map50_95: z.number().nullable()
49109
+ });
49110
+ var TrainingCanarySchema = z.object({
49111
+ deviceIds: z.array(z.number().int()).readonly(),
49112
+ since: z.number(),
49113
+ /** Every override the canary replaced, exactly, so rollback restores it. */
49114
+ previous: z.array(z.object({
49115
+ deviceId: z.number().int(),
49116
+ nodeId: z.string(),
49117
+ deviceKey: z.string(),
49118
+ patch: z.object({
49119
+ modelId: z.string().optional(),
49120
+ settings: z.record(z.string(), z.unknown()).readonly().optional()
49121
+ }).nullable()
49122
+ })).readonly()
49123
+ });
49124
+ var TrainingJobSchema = z.object({
49125
+ id: z.string(),
49126
+ idempotencyKey: z.string(),
49127
+ name: z.string(),
49128
+ createdAt: z.number(),
49129
+ updatedAt: z.number(),
49130
+ state: TrainingJobStateSchema,
49131
+ stage: TrainingStageSchema.nullable(),
49132
+ failure: z.object({
49133
+ reason: TrainingFailureReasonSchema,
49134
+ stage: z.string().nullable(),
49135
+ detail: z.string()
49136
+ }).nullable(),
49137
+ request: TrainingJobRequestSchema,
49138
+ maxCostUsd: z.number().nonnegative(),
49139
+ maxRuntimeSec: z.number().int().positive(),
49140
+ trainer: TrainerSourceRefSchema,
49141
+ datasetLayout: RetrainExportLayoutSchema,
49142
+ dataset: z.object({
49143
+ bytes: z.number().int(),
49144
+ sha256: z.string(),
49145
+ uploadedAt: z.number()
49146
+ }).nullable(),
49147
+ split: TrainingSplitPlanSchema,
49148
+ provider: z.object({
49149
+ state: ProviderJobStateSchema.nullable(),
49150
+ providerJobId: z.string().nullable(),
49151
+ startedAt: z.number().nullable(),
49152
+ endedAt: z.number().nullable(),
49153
+ billedSeconds: z.number(),
49154
+ costUsd: z.number(),
49155
+ costIsEstimate: z.boolean(),
49156
+ lastPolledAt: z.number().nullable()
49157
+ }),
49158
+ progress: z.object({
49159
+ epoch: z.number().int().nullable(),
49160
+ epochs: z.number().int().nullable(),
49161
+ etaSec: z.number().nullable(),
49162
+ series: z.array(TrainingProgressPointSchema).readonly()
49163
+ }),
49164
+ /** The last lines of the job's output (≤ 200), newest last. Never frame paths. */
49165
+ logTail: z.array(z.string()).readonly(),
49166
+ logCursor: z.string().nullable(),
49167
+ artifacts: z.object({
49168
+ bundleBytes: z.number().int(),
49169
+ bundleSha256: z.string(),
49170
+ fetchedAt: z.number(),
49171
+ /** When the staged artefacts are deleted from the hub. */
49172
+ expiresAt: z.number()
49173
+ }).nullable(),
49174
+ /** When the provider-side copy of the frames was deleted; null while it exists. */
49175
+ cloudDatasetDeletedAt: z.number().nullable(),
49176
+ /** A failed job's dataset is kept until then (the provider's grace), then deleted. */
49177
+ cloudCleanupDueAt: z.number().nullable(),
49178
+ /** Model ids registered from this job (base, and int8 when kept). */
49179
+ registeredModelIds: z.array(z.string()).readonly(),
49180
+ canary: TrainingCanarySchema.nullable()
49181
+ });
49182
+ var IdInput = z.object({ id: z.string().min(1) });
49183
+ var trainingCapability = {
49184
+ name: "training",
49185
+ scope: "system",
49186
+ mode: "singleton",
49187
+ mount: { kind: "hub-only" },
49188
+ methods: {
49189
+ /** Store a version once. `stored: false` = that id already exists (immutable). */
49190
+ putDatasetVersion: method(TrainingDatasetVersionRowSchema, z.object({ stored: z.boolean() }), {
49191
+ kind: "mutation",
49192
+ auth: "admin"
49193
+ }),
49194
+ getDatasetVersion: method(IdInput, TrainingDatasetVersionRowSchema.nullable(), { auth: "admin" }),
49195
+ /** Every version without its frame list, newest first. */
49196
+ listDatasetVersions: method(z.object({}), z.array(RetrainDatasetVersionSchema).readonly(), { auth: "admin" }),
49197
+ findDatasetVersionIdByName: method(z.object({ name: z.string() }), z.object({ id: z.string().nullable() }), { auth: "admin" }),
49198
+ removeDatasetVersion: method(IdInput, z.object({ removed: z.boolean() }), {
49199
+ kind: "mutation",
49200
+ auth: "admin"
49201
+ }),
49202
+ listBaseModels: method(z.object({}), z.array(TrainingBaseModelSchema).readonly(), { auth: "admin" }),
49203
+ /** Split, estimate, defaults and any named refusal — nothing is started. */
49204
+ planJob: method(TrainingJobRequestSchema, TrainingJobPlanSchema, {
49205
+ auth: "admin",
49206
+ timeoutMs: 6e4
49207
+ }),
49208
+ /**
49209
+ * Start a job. The same `idempotencyKey` returns the job it already
49210
+ * started. Answers at once (`uploading`); the upload and the submit run in
49211
+ * the background and the job record says how far they got.
49212
+ */
49213
+ startJob: method(TrainingJobRequestSchema.extend({
49214
+ name: z.string().min(1).max(120),
49215
+ maxCostUsd: z.number().positive(),
49216
+ maxRuntimeSec: z.number().int().positive(),
49217
+ idempotencyKey: z.string().min(8).max(80)
49218
+ }), z.discriminatedUnion("kind", [z.object({
49219
+ kind: z.literal("started"),
49220
+ job: TrainingJobSchema
49221
+ }), z.object({
49222
+ kind: z.literal("refused"),
49223
+ reason: TrainingRefusalReasonSchema,
49224
+ detail: z.string()
49225
+ })]), {
49226
+ kind: "mutation",
49227
+ auth: "admin",
49228
+ timeoutMs: 12e4
49229
+ }),
49230
+ listJobs: method(z.object({}), z.array(TrainingJobSchema).readonly(), { auth: "admin" }),
49231
+ getJob: method(IdInput, TrainingJobSchema.nullable(), { auth: "admin" }),
49232
+ cancelJob: method(IdInput, TrainingJobSchema, {
49233
+ kind: "mutation",
49234
+ auth: "admin",
49235
+ timeoutMs: 12e4
49236
+ }),
49237
+ /** The verified `result.json` of a job in `ready` or later; null before. */
49238
+ getJobResult: method(IdInput, TrainingResultSchema.nullable(), { auth: "admin" }),
49239
+ /** Hand the verified bundle to Model Studio; optionally canary right after. */
49240
+ registerResult: method(IdInput.extend({ canaryDeviceIds: z.array(z.number().int()).readonly() }), TrainingJobSchema, {
49241
+ kind: "mutation",
49242
+ auth: "admin",
49243
+ timeoutMs: 30 * 6e4
49244
+ }),
49245
+ /** Pin the registered model on these cameras, remembering what it replaced. */
49246
+ canaryResult: method(IdInput.extend({ deviceIds: z.array(z.number().int()).min(1).readonly() }), TrainingJobSchema, {
49247
+ kind: "mutation",
49248
+ auth: "admin",
49249
+ timeoutMs: 12e4
49250
+ }),
49251
+ /** Put every canary camera back exactly as it was. */
49252
+ rollbackCanary: method(IdInput, TrainingJobSchema, {
49253
+ kind: "mutation",
49254
+ auth: "admin",
49255
+ timeoutMs: 12e4
49256
+ }),
49257
+ /** Delete the staged artefacts of an unregistered result. */
49258
+ discardResult: method(IdInput, TrainingJobSchema, {
49259
+ kind: "mutation",
49260
+ auth: "admin"
49261
+ })
49262
+ }
49263
+ };
47926
49264
  function isValueOf(slot, value) {
47927
49265
  return value.slot === slot;
47928
49266
  }
@@ -50371,6 +51709,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50371
51709
  addonId: null,
50372
51710
  access: "create"
50373
51711
  },
51712
+ "backup.checkBackupKey": {
51713
+ capName: "backup",
51714
+ capScope: "system",
51715
+ addonId: null,
51716
+ access: "create"
51717
+ },
50374
51718
  "backup.delete": {
50375
51719
  capName: "backup",
50376
51720
  capScope: "system",
@@ -50383,6 +51727,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
50383
51727
  addonId: null,
50384
51728
  access: "delete"
50385
51729
  },
51730
+ "backup.disableEncryption": {
51731
+ capName: "backup",
51732
+ capScope: "system",
51733
+ addonId: null,
51734
+ access: "create"
51735
+ },
51736
+ "backup.getEncryption": {
51737
+ capName: "backup",
51738
+ capScope: "system",
51739
+ addonId: null,
51740
+ access: "view"
51741
+ },
50386
51742
  "backup.getEntries": {
50387
51743
  capName: "backup",
50388
51744
  capScope: "system",
@@ -50437,6 +51793,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50437
51793
  addonId: null,
50438
51794
  access: "create"
50439
51795
  },
51796
+ "backup.setupEncryption": {
51797
+ capName: "backup",
51798
+ capScope: "system",
51799
+ addonId: null,
51800
+ access: "create"
51801
+ },
50440
51802
  "backup.trigger": {
50441
51803
  capName: "backup",
50442
51804
  capScope: "system",
@@ -50989,6 +52351,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50989
52351
  addonId: null,
50990
52352
  access: "create"
50991
52353
  },
52354
+ "customModelRegistry.importModelBundle": {
52355
+ capName: "custom-model-registry",
52356
+ capScope: "system",
52357
+ addonId: null,
52358
+ access: "create"
52359
+ },
50992
52360
  "customModelRegistry.listModels": {
50993
52361
  capName: "custom-model-registry",
50994
52362
  capScope: "system",
@@ -52429,6 +53797,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52429
53797
  addonId: null,
52430
53798
  access: "view"
52431
53799
  },
53800
+ "keychainAdmin.acknowledgeReset": {
53801
+ capName: "keychain-admin",
53802
+ capScope: "system",
53803
+ addonId: null,
53804
+ access: "create"
53805
+ },
52432
53806
  "keychainAdmin.deleteOrphans": {
52433
53807
  capName: "keychain-admin",
52434
53808
  capScope: "system",
@@ -52465,6 +53839,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52465
53839
  addonId: null,
52466
53840
  access: "delete"
52467
53841
  },
53842
+ "keychainAdmin.resetReport": {
53843
+ capName: "keychain-admin",
53844
+ capScope: "system",
53845
+ addonId: null,
53846
+ access: "delete"
53847
+ },
52468
53848
  "keychainAdmin.reveal": {
52469
53849
  capName: "keychain-admin",
52470
53850
  capScope: "system",
@@ -54211,6 +55591,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
54211
55591
  addonId: null,
54212
55592
  access: "view"
54213
55593
  },
55594
+ "pipelineAnalytics.offerRetrainArchive": {
55595
+ capName: "pipeline-analytics",
55596
+ capScope: "device",
55597
+ addonId: null,
55598
+ access: "create"
55599
+ },
54214
55600
  "pipelineAnalytics.ownersWithMedia": {
54215
55601
  capName: "pipeline-analytics",
54216
55602
  capScope: "device",
@@ -56749,6 +58135,174 @@ var METHOD_ACCESS_MAP = Object.freeze({
56749
58135
  addonId: null,
56750
58136
  access: "view"
56751
58137
  },
58138
+ "training.canaryResult": {
58139
+ capName: "training",
58140
+ capScope: "system",
58141
+ addonId: null,
58142
+ access: "create"
58143
+ },
58144
+ "training.cancelJob": {
58145
+ capName: "training",
58146
+ capScope: "system",
58147
+ addonId: null,
58148
+ access: "create"
58149
+ },
58150
+ "training.discardResult": {
58151
+ capName: "training",
58152
+ capScope: "system",
58153
+ addonId: null,
58154
+ access: "create"
58155
+ },
58156
+ "training.findDatasetVersionIdByName": {
58157
+ capName: "training",
58158
+ capScope: "system",
58159
+ addonId: null,
58160
+ access: "view"
58161
+ },
58162
+ "training.getDatasetVersion": {
58163
+ capName: "training",
58164
+ capScope: "system",
58165
+ addonId: null,
58166
+ access: "view"
58167
+ },
58168
+ "training.getJob": {
58169
+ capName: "training",
58170
+ capScope: "system",
58171
+ addonId: null,
58172
+ access: "view"
58173
+ },
58174
+ "training.getJobResult": {
58175
+ capName: "training",
58176
+ capScope: "system",
58177
+ addonId: null,
58178
+ access: "view"
58179
+ },
58180
+ "training.listBaseModels": {
58181
+ capName: "training",
58182
+ capScope: "system",
58183
+ addonId: null,
58184
+ access: "view"
58185
+ },
58186
+ "training.listDatasetVersions": {
58187
+ capName: "training",
58188
+ capScope: "system",
58189
+ addonId: null,
58190
+ access: "view"
58191
+ },
58192
+ "training.listJobs": {
58193
+ capName: "training",
58194
+ capScope: "system",
58195
+ addonId: null,
58196
+ access: "view"
58197
+ },
58198
+ "training.planJob": {
58199
+ capName: "training",
58200
+ capScope: "system",
58201
+ addonId: null,
58202
+ access: "view"
58203
+ },
58204
+ "training.putDatasetVersion": {
58205
+ capName: "training",
58206
+ capScope: "system",
58207
+ addonId: null,
58208
+ access: "create"
58209
+ },
58210
+ "training.registerResult": {
58211
+ capName: "training",
58212
+ capScope: "system",
58213
+ addonId: null,
58214
+ access: "create"
58215
+ },
58216
+ "training.removeDatasetVersion": {
58217
+ capName: "training",
58218
+ capScope: "system",
58219
+ addonId: null,
58220
+ access: "delete"
58221
+ },
58222
+ "training.rollbackCanary": {
58223
+ capName: "training",
58224
+ capScope: "system",
58225
+ addonId: null,
58226
+ access: "create"
58227
+ },
58228
+ "training.startJob": {
58229
+ capName: "training",
58230
+ capScope: "system",
58231
+ addonId: null,
58232
+ access: "create"
58233
+ },
58234
+ "trainingProvider.cancelJob": {
58235
+ capName: "training-provider",
58236
+ capScope: "system",
58237
+ addonId: null,
58238
+ access: "create"
58239
+ },
58240
+ "trainingProvider.cleanupJob": {
58241
+ capName: "training-provider",
58242
+ capScope: "system",
58243
+ addonId: null,
58244
+ access: "create"
58245
+ },
58246
+ "trainingProvider.describe": {
58247
+ capName: "training-provider",
58248
+ capScope: "system",
58249
+ addonId: null,
58250
+ access: "view"
58251
+ },
58252
+ "trainingProvider.estimateJob": {
58253
+ capName: "training-provider",
58254
+ capScope: "system",
58255
+ addonId: null,
58256
+ access: "view"
58257
+ },
58258
+ "trainingProvider.getJob": {
58259
+ capName: "training-provider",
58260
+ capScope: "system",
58261
+ addonId: null,
58262
+ access: "view"
58263
+ },
58264
+ "trainingProvider.listJobEvents": {
58265
+ capName: "training-provider",
58266
+ capScope: "system",
58267
+ addonId: null,
58268
+ access: "view"
58269
+ },
58270
+ "trainingProvider.listJobs": {
58271
+ capName: "training-provider",
58272
+ capScope: "system",
58273
+ addonId: null,
58274
+ access: "view"
58275
+ },
58276
+ "trainingProvider.offerArtifact": {
58277
+ capName: "training-provider",
58278
+ capScope: "system",
58279
+ addonId: null,
58280
+ access: "create"
58281
+ },
58282
+ "trainingProvider.readResult": {
58283
+ capName: "training-provider",
58284
+ capScope: "system",
58285
+ addonId: null,
58286
+ access: "create"
58287
+ },
58288
+ "trainingProvider.submitJob": {
58289
+ capName: "training-provider",
58290
+ capScope: "system",
58291
+ addonId: null,
58292
+ access: "create"
58293
+ },
58294
+ "trainingProvider.uploadDataset": {
58295
+ capName: "training-provider",
58296
+ capScope: "system",
58297
+ addonId: null,
58298
+ access: "create"
58299
+ },
58300
+ "trainingProvider.validateCredentials": {
58301
+ capName: "training-provider",
58302
+ capScope: "system",
58303
+ addonId: null,
58304
+ access: "create"
58305
+ },
56752
58306
  "turnProvider.getTurnServers": {
56753
58307
  capName: "turn-provider",
56754
58308
  capScope: "system",
@@ -56773,12 +58327,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56773
58327
  addonId: null,
56774
58328
  access: "create"
56775
58329
  },
56776
- "userManagement.createApiKey": {
56777
- capName: "user-management",
56778
- capScope: "system",
56779
- addonId: null,
56780
- access: "create"
56781
- },
56782
58330
  "userManagement.createScopedToken": {
56783
58331
  capName: "user-management",
56784
58332
  capScope: "system",
@@ -56815,12 +58363,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56815
58363
  addonId: null,
56816
58364
  access: "view"
56817
58365
  },
56818
- "userManagement.linkExternalIdentity": {
56819
- capName: "user-management",
56820
- capScope: "system",
56821
- addonId: null,
56822
- access: "create"
56823
- },
56824
58366
  "userManagement.listApiKeys": {
56825
58367
  capName: "user-management",
56826
58368
  capScope: "system",
@@ -56923,12 +58465,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56923
58465
  addonId: null,
56924
58466
  access: "create"
56925
58467
  },
56926
- "userManagement.validateApiKey": {
56927
- capName: "user-management",
56928
- capScope: "system",
56929
- addonId: null,
56930
- access: "view"
56931
- },
56932
58468
  "userManagement.validateCredentials": {
56933
58469
  capName: "user-management",
56934
58470
  capScope: "system",
@@ -58921,6 +60457,8 @@ var ALL_CAPABILITY_DEFINITIONS = [
58921
60457
  temperatureSensorCapability,
58922
60458
  terminalSessionCapability,
58923
60459
  toastCapability,
60460
+ trainingCapability,
60461
+ trainingProviderCapability,
58924
60462
  turnProviderCapability,
58925
60463
  updateCapability,
58926
60464
  userManagementCapability,
@@ -61299,6 +62837,11 @@ Object.freeze({
61299
62837
  form: "single",
61300
62838
  optional: false
61301
62839
  }],
62840
+ "training.canaryResult": [{
62841
+ name: "deviceIds",
62842
+ form: "array",
62843
+ optional: false
62844
+ }],
61302
62845
  "update.getStatus": [{
61303
62846
  name: "deviceId",
61304
62847
  form: "single",
@@ -62936,7 +64479,12 @@ var CREDENTIAL_PARAM_PATTERNS = [
62936
64479
  /^bearer$/i,
62937
64480
  /^api[_-]?key$/i,
62938
64481
  /^access[_-]?token$/i,
62939
- /^refresh[_-]?token$/i
64482
+ /^refresh[_-]?token$/i,
64483
+ /^bridge$/i,
64484
+ /^code$/i,
64485
+ /^state$/i,
64486
+ /^challenge$/i,
64487
+ /^link$/i
62940
64488
  ];
62941
64489
  function isCredentialParam(name) {
62942
64490
  return CREDENTIAL_PARAM_PATTERNS.some((re) => re.test(name));
@@ -62958,6 +64506,10 @@ function isCredentialParam(name) {
62958
64506
  * are NOT masked (those are entity references, not credentials).
62959
64507
  */
62960
64508
  function maskUrlCredentials(rawUrl) {
64509
+ if (rawUrl.startsWith("/") && !rawUrl.startsWith("//")) {
64510
+ const masked = maskUrlCredentials(`http://relative.invalid${rawUrl}`);
64511
+ return masked.startsWith("http://relative.invalid") ? masked.slice(23) : rawUrl;
64512
+ }
62961
64513
  try {
62962
64514
  const u = new URL(rawUrl);
62963
64515
  let touched = false;
@@ -63109,4 +64661,4 @@ function enumerateInferenceDevices(hw) {
63109
64661
  return out;
63110
64662
  }
63111
64663
  //#endregion
63112
- export { buildStreamParamsConfigSchema as $, compositionItemFieldPath as $n, looseSchema as $t, MINUTES_PER_DAY as A, scoreRuntimes as An, isDeviceConfigCap as Ar, extractNestedAddonId as At, StorageLocationModeSchema as B, userManagementCapability as Bn, parseSecretRef as Br, isLocationEnabled as Bt, FieldClaimModeSchema as C, resolveHubAddressRoles as Cn, collectZodSecretPaths as Cr, enumerateInferenceDevices as Ct, LegacyStorageLocationDefaultSchema as D, runtimeStatePolicyFor as Dn, expandCapMethods as Dr, evictionPolicyOfLocation as Dt, LOG_LEVEL_RANK as E, resolveNotificationTapAddress as En, emitReadiness as Er, evaluateCompositionField as Et, RecordingStatusSchema as F, storageMigrationCapability as Fn, isSecretMarkerLike as Fr, hfModelUrl as Ft, addonSettingsCapability as G, vectorStoreCapability as Gn, scrubKnownSecrets as Gr, keychainCapability as Gt, StorageMigrationJobSchema as H, validateCustomAddress as Hn, resolveCapMount as Hr, isVoidInput as Ht, STORAGE_ACCESS_FALLBACK as I, storageOccupancyCapability as In, nodePin as Ir, isAppLinkBase as It, authProviderCapability as J, buildFfmpegArgs as Jn, sleep as Jr, lifecycleJobSchema as Jt, addonWidgetsCapability as K, withLocationMode as Kn, secretTreeFromPaths as Kr, legacyAutoLinkIds as Kt, STREAM_PROFILE_META as L, storageProviderCapability as Ln, normalizeSecretOwner as Lr, isArrayOutputSchema as Lt, MOTION_CLOSE_AFTER_MS as M, siteZoneKey as Mn, isKeychainStoredField as Mr, foldSnapshotByFunction as Mt, RECORDING_SOURCE_CAMSTACK_ADDON as N, snapshotCapability as Nn, isSecretClear as Nr, formatMinutes as Nt, LocationIconIdSchema as O, scopesAllowAddon as On, formatSecretScope as Or, expandContainer as Ot, RUNTIME_DEFAULTS as P, storageCapability as Pn, isSecretMarker as Pr, gbToBytes as Pt, buildCapabilityLookup as Q, compositionFeatureEnabled as Qn, EventCategory as Qr, logLevelAtMost as Qt, ScopedTokenSchema as R, streamCatalogCapability as Rn, parseJsonObject as Rr, isCollectionArrayMethod as Rt, FIRST_LEVEL_MACRO_CLASSES as S, resolveCoreBlockFiles as Sn, collectSecretConfigKeys as Sr, deviceStatusCapability as St, HF_REPO as T, resolveMethodAuth as Tn, emitDownForOwnedCaps as Tr, enumerateSchemaFields as Tt, UserRecordSchema as U, vectorDimFromBase64 as Un, resolveSecretMarkers as Ur, kebabToCamel as Ut, StorageLocationTypeSchema as V, validateComposition as Vn, readinessKey as Vr, isObjectInput as Vt, addonPagesCapability as W, vectorIndexDimConflictError as Wn, scopeKey as Wr, keychainAdminCapability as Wt, bareAddonId as X, composedDeviceRef as Xn, toSecretFieldView as Xr, locationIconKey as Xt, backupCapability as Y, cloneStatefulExpressionMemory as Yn, toNodeId as Yr, localNetworkCapability as Yt, batteryCapability as Z, composedDeviceStableId as Zn, transformSecretWrite as Zr, logDestinationCapability as Zt, DATABASE_SNAPSHOT_TIMEOUT_MS as _, reducePoints as _n, WELL_KNOWN_TAB_MAP as _r, deriveBatteryPresence as _t, BatteryStatusSchema as a, modeMayWrite as an, DeviceFeature as ar, compositionSourceRefs as at, DeclaredDevices as b, resolveBucketMs as bn, asString$1 as br, deviceManagerCapability as bt, CAP_NAMES_WITH_STATUS as c, normalizeUnit as cn, errMsg as cr, coreBlockIdFromAddonId as ct, CORE_BLOCKS_ADDON_ID as d, parseStreamParamsFormPatch as dn, DEVICE_SETTINGS_CONTRIBUTION_METHODS as dr, createDeviceExtensionProvider as dt, maskUrlCredentials as en, compositionSliceKey as er, calendarsCapability as et, CORE_BLOCK_ADDON_PREFIX as f, pickAppLinkHub as fn, DEVICE_STATUS_METHOD as fr, dataStoreProviderCapability as ft, DATABASE_SNAPSHOT_SUFFIX as g, ptzAutotrackCapability as gn, SECRET_CLEAR as gr, defineExtensionSlotProvider as gt, DATABASE_SNAPSHOT_PARTIAL_SUFFIX as h, procedureAuthKey as hn, ReadinessTimeoutError as hr, decodeVectorBase64 as ht, BaseDevice as i, metricsProviderCapability as in, toExpressionValue as ir, compositionFieldKey as it, MODEL_FORMATS as j, settingsStoreCapability as jn, isEvent as jr, filesystemBrowseCapability as jt, METHOD_ACCESS_MAP as k, scopesAllowDeviceCap as kn, hydrateSchema as kr, expressionContainsBuiltinCall as kt, COMPOSED_ITEM_FIXED_FIELDS as l, objectInputDeclaresAddonId as ln, BaseAddon as lr, coreBlockSourceKind as lt, CoreBlockSchema as m, platformProbeCapability as mn, ReadinessRegistry as mr, declarationOwnerNodeId as mt, AlertSchema as n, mayWriteToLocation as nn, createStatefulExpressionMemory as nr, checkRoleAddress as nt, CAMERA_SWITCH_ORDER as o, motionCapability as on, DeviceRole as or, coreBlockAddonId as ot, CalendarPreferencesSchema as p, planComposition as pn, REDACTED_SECRET as pr, databaseSnapshotCapability as pt, alertsCapability as q, COMPOSER_ADDON_ID as qn, secretTreeFromSchema as qr, legacyModeOf as qt, ApiKeyRecordSchema as r, memoizePinnedFfmpeg as rn, disarmRoseMemory as rr, composerCapability as rt, CAMSTACK_APP_LINK_ORIGIN as s, normalizeCoreBlockWrite as sn, DeviceType as sr, coreBlockComposition as st, ALL_CAPABILITY_DEFINITIONS as t, mayReadLocation as tn, compositionSourceKey as tr, cameraGridLayoutCapability as tt, COMPOSER_RUNNER_ID as u, objectInputDeclaresRequiredDeviceId as un, DATAPLANE_SECRET_HEADER as ur, coreBlocksCapability as ut, DAY_NAMES as v, replacedFieldKeys as vn, asJsonObject as vr, describeComposableCaps as vt, FieldClaimSchema as w, resolveLocationMode as wn, createEvent as wr, enumerateItemArrayFields as wt, DeviceStatusSchema as x, resolveCoreBlockEntry as xn, buildSecretRef as xr, deviceStateCapability as xt, DERIVED_CAMERA_SILENCED_CAP_NAMES as y, resolveAppConnections as yn, asNumber as yr, deviceExtensionCapability as yt, StorageCleanupJobSchema as z, streamQualityLabel as zn, parseJsonUnknown as zr, isDefaultAppConnectionEndpoint as zt };
64664
+ export { buildCapabilityLookup as $, compositionFeatureEnabled as $n, transformSecretWrite as $r, logLevelAtMost as $t, METHOD_ACCESS_MAP as A, scopesAllowDeviceCap as An, hydrateSchema as Ar, expressionContainsBuiltinCall as At, StorageCleanupJobSchema as B, streamQualityLabel as Bn, parseJsonObject as Br, isDefaultAppConnectionEndpoint as Bt, FieldClaimModeSchema as C, resolveCoreBlockFiles as Cn, collectSecretConfigKeys as Cr, deviceStatusCapability as Ct, LOG_LEVEL_RANK as D, resolveNotificationTapAddress as Dn, emitReadiness as Dr, evaluateCompositionField as Dt, KEYCHAIN_RESET_TRIGGER_ACTOR as E, resolveMethodAuth as En, emitDownForOwnedCaps as Er, enumerateSchemaFields as Et, RUNTIME_DEFAULTS as F, storageCapability as Fn, isSecretMarker as Fr, gbToBytes as Ft, addonPagesCapability as G, vectorIndexDimConflictError as Gn, resolveSecretMarkers as Gr, keychainAdminCapability as Gt, StorageLocationTypeSchema as H, validateComposition as Hn, parseSecretRef as Hr, isObjectInput as Ht, RecordingStatusSchema as I, storageMigrationCapability as In, isSecretMarkerLike as Ir, hfModelUrl as It, alertsCapability as J, COMPOSER_ADDON_ID as Jn, secretTreeFromPaths as Jr, legacyModeOf as Jt, addonSettingsCapability as K, vectorStoreCapability as Kn, scopeKey as Kr, keychainCapability as Kt, STORAGE_ACCESS_FALLBACK as L, storageOccupancyCapability as Ln, makeSecretMarker as Lr, isAppLinkBase as Lt, MODEL_FORMATS as M, settingsStoreCapability as Mn, isEvent as Mr, filesystemBrowseCapability as Mt, MOTION_CLOSE_AFTER_MS as N, siteZoneKey as Nn, isKeychainStoredField as Nr, foldSnapshotByFunction as Nt, LegacyStorageLocationDefaultSchema as O, runtimeStatePolicyFor as On, expandCapMethods as Or, evictionPolicyOfLocation as Ot, RECORDING_SOURCE_CAMSTACK_ADDON as P, snapshotCapability as Pn, isSecretClear as Pr, formatMinutes as Pt, batteryCapability as Q, composedDeviceStableId as Qn, toSecretFieldView as Qr, logDestinationCapability as Qt, STREAM_PROFILE_META as R, storageProviderCapability as Rn, nodePin as Rr, isArrayOutputSchema as Rt, FIRST_LEVEL_MACRO_CLASSES as S, resolveCoreBlockEntry as Sn, buildSecretRef as Sr, deviceStateCapability as St, HF_REPO as T, resolveLocationMode as Tn, createEvent as Tr, enumerateItemArrayFields as Tt, StorageMigrationJobSchema as U, validateCustomAddress as Un, readinessKey as Ur, isVoidInput as Ut, StorageLocationModeSchema as V, userManagementCapability as Vn, parseJsonUnknown as Vr, isLocationEnabled as Vt, UserRecordSchema as W, vectorDimFromBase64 as Wn, resolveCapMount as Wr, kebabToCamel as Wt, backupCapability as X, cloneStatefulExpressionMemory as Xn, sleep as Xr, localNetworkCapability as Xt, authProviderCapability as Y, buildFfmpegArgs as Yn, secretTreeFromSchema as Yr, lifecycleJobSchema as Yt, bareAddonId as Z, composedDeviceRef as Zn, toNodeId as Zr, locationIconKey as Zt, DATABASE_SNAPSHOT_TIMEOUT_MS as _, ptzAutotrackCapability as _n, SECRET_CLEAR as _r, defineExtensionSlotProvider as _t, BatteryStatusSchema as a, metricsProviderCapability as an, toExpressionValue as ar, compositionFieldKey as at, DeclaredDevices as b, resolveAppConnections as bn, asNumber as br, deviceExtensionCapability as bt, CAP_NAMES_WITH_STATUS as c, normalizeCoreBlockWrite as cn, DeviceType as cr, coreBlockComposition as ct, CORE_BLOCKS_ADDON_ID as d, objectInputDeclaresRequiredDeviceId as dn, DATAPLANE_SECRET_HEADER as dr, coreBlocksCapability as dt, EventCategory as ei, looseSchema as en, compositionItemFieldPath as er, buildStreamParamsConfigSchema as et, CORE_BLOCK_ADDON_PREFIX as f, parseStreamParamsFormPatch as fn, DEVICE_SETTINGS_CONTRIBUTION_METHODS as fr, createDeviceExtensionProvider as ft, DATABASE_SNAPSHOT_SUFFIX as g, procedureAuthKey as gn, ReadinessTimeoutError as gr, decodeVectorBase64 as gt, DATABASE_SNAPSHOT_PARTIAL_SUFFIX as h, platformProbeCapability as hn, ReadinessRegistry as hr, declarationOwnerNodeId as ht, BaseDevice as i, memoizePinnedFfmpeg as in, disarmRoseMemory as ir, composerCapability as it, MINUTES_PER_DAY as j, scoreRuntimes as jn, isDeviceConfigCap as jr, extractNestedAddonId as jt, LocationIconIdSchema as k, scopesAllowAddon as kn, formatSecretScope as kr, expandContainer as kt, COMPOSED_ITEM_FIXED_FIELDS as l, normalizeUnit as ln, errMsg as lr, coreBlockIdFromAddonId as lt, CoreBlockSchema as m, planComposition as mn, REDACTED_SECRET as mr, databaseSnapshotCapability as mt, AlertSchema as n, mayReadLocation as nn, compositionSourceKey as nr, cameraGridLayoutCapability as nt, CAMERA_SWITCH_ORDER as o, modeMayWrite as on, DeviceFeature as or, compositionSourceRefs as ot, CalendarPreferencesSchema as p, pickAppLinkHub as pn, DEVICE_STATUS_METHOD as pr, dataStoreProviderCapability as pt, addonWidgetsCapability as q, withLocationMode as qn, scrubKnownSecrets as qr, legacyAutoLinkIds as qt, ApiKeyRecordSchema as r, mayWriteToLocation as rn, createStatefulExpressionMemory as rr, checkRoleAddress as rt, CAMSTACK_APP_LINK_ORIGIN as s, motionCapability as sn, DeviceRole as sr, coreBlockAddonId as st, ALL_CAPABILITY_DEFINITIONS as t, maskUrlCredentials as tn, compositionSliceKey as tr, calendarsCapability as tt, COMPOSER_RUNNER_ID as u, objectInputDeclaresAddonId as un, BaseAddon as ur, coreBlockSourceKind as ut, DAY_NAMES as v, reducePoints as vn, WELL_KNOWN_TAB_MAP as vr, deriveBatteryPresence as vt, FieldClaimSchema as w, resolveHubAddressRoles as wn, collectZodSecretPaths as wr, enumerateInferenceDevices as wt, DeviceStatusSchema as x, resolveBucketMs as xn, asString$1 as xr, deviceManagerCapability as xt, DERIVED_CAMERA_SILENCED_CAP_NAMES as y, replacedFieldKeys as yn, asJsonObject as yr, describeComposableCaps as yt, ScopedTokenSchema as z, streamCatalogCapability as zn, normalizeSecretOwner as zr, isCollectionArrayMethod as zt };