@camstack/system 1.2.380 → 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 (117) 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-aggregation.d.ts +12 -0
  37. package/dist/builtins/device-manager/device-form-secrets.d.ts +25 -0
  38. package/dist/builtins/device-manager/device-manager.addon.d.ts +2 -0
  39. package/dist/builtins/device-manager/device-manager.addon.js +165 -26
  40. package/dist/builtins/device-manager/device-manager.addon.mjs +165 -26
  41. package/dist/builtins/hub-forwarder/index.js +1 -1
  42. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  43. package/dist/builtins/keychain/index.js +1108 -733
  44. package/dist/builtins/keychain/index.mjs +1108 -733
  45. package/dist/builtins/keychain/keychain-reset-trigger.d.ts +25 -0
  46. package/dist/builtins/keychain/keychain-service.d.ts +36 -3
  47. package/dist/builtins/keychain/keychain-store.d.ts +24 -1
  48. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  49. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  50. package/dist/builtins/local-auth/local-auth.addon.d.ts +16 -0
  51. package/dist/builtins/local-auth/local-auth.addon.js +1021 -118
  52. package/dist/builtins/local-auth/local-auth.addon.mjs +1034 -141
  53. package/dist/builtins/local-network/local-network.addon.js +3 -3
  54. package/dist/builtins/local-network/local-network.addon.mjs +3 -3
  55. package/dist/builtins/loki-logging/index.js +97 -29
  56. package/dist/builtins/loki-logging/index.mjs +97 -29
  57. package/dist/builtins/loki-logging/loki-logging.addon.d.ts +7 -23
  58. package/dist/builtins/loki-logging/retire-enabled-flag.d.ts +23 -0
  59. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  60. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  61. package/dist/builtins/platform-probe/index.js +1 -1
  62. package/dist/builtins/platform-probe/index.mjs +1 -1
  63. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  64. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  65. package/dist/builtins/snapshot/index.js +1 -1
  66. package/dist/builtins/snapshot/index.mjs +1 -1
  67. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  68. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  69. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  70. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  71. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
  72. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
  73. package/dist/builtins/system-backup/backup-encryption.d.ts +57 -0
  74. package/dist/builtins/system-backup/system-backup.service.d.ts +16 -1
  75. package/dist/builtins/system-config/cluster-secret-keeper.d.ts +7 -0
  76. package/dist/builtins/system-config/system-config.addon.d.ts +13 -3
  77. package/dist/builtins/system-config/system-config.addon.js +324 -38
  78. package/dist/builtins/system-config/system-config.addon.mjs +312 -39
  79. package/dist/builtins/winston-logging/index.js +1 -1
  80. package/dist/builtins/winston-logging/index.mjs +1 -1
  81. package/dist/{child-cap-dispatch-CZeRUgYF.js → child-cap-dispatch-BrMdKHfp.js} +6 -3
  82. package/dist/{child-cap-dispatch-CpjohToR.mjs → child-cap-dispatch-WsYPNvnK.mjs} +10 -7
  83. package/dist/{cluster-secret-Dg0OOOSr.js → cluster-secret-BEPLh3LL.js} +1 -1
  84. package/dist/{cluster-secret-BDue-uJj.mjs → cluster-secret-DhPdZ0VA.mjs} +1 -1
  85. package/dist/{composition-sources-CHvLMrFI.js → composition-sources-C6ZynCZ2.js} +1 -1
  86. package/dist/{composition-sources-DT_uJwI3.mjs → composition-sources-XqRX51db.mjs} +1 -1
  87. package/dist/{dist-B9oFw2jh.mjs → dist-CP5TTjwd.mjs} +1713 -157
  88. package/dist/{dist-Cc6KOdY-.js → dist-tnAO12ak.js} +1754 -156
  89. package/dist/{event-loop-stall-monitor-mk6WGxir.js → event-loop-stall-monitor-9kR0sAPS.js} +15 -15
  90. package/dist/{event-loop-stall-monitor-anUH2IE1.mjs → event-loop-stall-monitor-WkssIXjq.mjs} +15 -15
  91. package/dist/{hub-cap-forward-CH6_VWVO.mjs → hub-cap-forward-CDiN1p98.mjs} +18 -2
  92. package/dist/{hub-cap-forward-COCmYaup.js → hub-cap-forward-W8oEm4ME.js} +23 -1
  93. package/dist/index.d.ts +16 -1
  94. package/dist/index.js +3341 -79
  95. package/dist/index.mjs +3567 -354
  96. package/dist/{jwt-signing-key-Qhde8r82.mjs → jwt-signing-key-CAbozwvF.mjs} +1 -1
  97. package/dist/{jwt-signing-key-C2rE3wH6.js → jwt-signing-key-Dgf8sIXY.js} +1 -1
  98. package/dist/kernel/index.d.ts +12 -0
  99. package/dist/kernel/moleculer/broker-factory.d.ts +8 -1
  100. package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +1 -0
  101. package/dist/kernel/moleculer/hub-service.d.ts +9 -0
  102. package/dist/kernel/moleculer/mesh-access-middleware.d.ts +28 -0
  103. package/dist/kernel/moleculer/mesh-auth-mode.d.ts +64 -0
  104. package/dist/kernel/moleculer/mesh-authenticator.d.ts +71 -0
  105. package/dist/kernel/moleculer/mesh-auto-enforce.d.ts +26 -0
  106. package/dist/kernel/moleculer/mesh-join-client.d.ts +51 -0
  107. package/dist/kernel/moleculer/mesh-join-proof.d.ts +39 -0
  108. package/dist/kernel/system-settings-policy.d.ts +4 -3
  109. package/dist/kernel/transport/hub-cap-forward.d.ts +14 -3
  110. package/dist/kernel/transport/index.d.ts +1 -1
  111. package/dist/restore-archive-BV39nY46.mjs +4045 -0
  112. package/dist/restore-archive-CbvrfPuB.js +4139 -0
  113. package/dist/{retired-settings-keys-YOOKLA3v.js → retired-settings-keys-4bHpL-0m.js} +1 -1
  114. package/dist/{retired-settings-keys-DlZ3VV6x.mjs → retired-settings-keys-CzpIkwS-.mjs} +1 -1
  115. package/dist/{tls-CJHogwTl.mjs → tls-4cRH24Id.mjs} +3 -3
  116. package/package.json +1 -1
  117. 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,12 +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
+ /** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
891
+ function isSecretMarkerLike(value) {
892
+ return isRecord$2(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
893
+ }
890
894
  function isSecretClear(value) {
891
- return isRecord$1(value) && value["$secretClear"] === true;
895
+ return isRecord$2(value) && value["$secretClear"] === true;
892
896
  }
893
897
  /**
894
898
  * The UI view of a stored secret field value: a marker becomes "set", inline
@@ -1557,7 +1561,7 @@ var KeychainWriteRefusedError = class extends Error {
1557
1561
  }
1558
1562
  };
1559
1563
  var KEEP = { kind: "keep" };
1560
- function isRecord(value) {
1564
+ function isRecord$1(value) {
1561
1565
  return typeof value === "object" && value !== null && !Array.isArray(value);
1562
1566
  }
1563
1567
  function defaultMintId() {
@@ -1570,7 +1574,7 @@ function collectSecretMarkerRefs(value, out = []) {
1570
1574
  return out;
1571
1575
  }
1572
1576
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
1573
- 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);
1574
1578
  return out;
1575
1579
  }
1576
1580
  async function storedValueEquals(secrets, marker, candidate) {
@@ -1625,7 +1629,7 @@ function rowId(row, idKey) {
1625
1629
  return null;
1626
1630
  }
1627
1631
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
1628
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord) : [];
1632
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$1) : [];
1629
1633
  if (!Array.isArray(incoming)) {
1630
1634
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
1631
1635
  return {
@@ -1641,7 +1645,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
1641
1645
  const kept = /* @__PURE__ */ new Set();
1642
1646
  const rows = [];
1643
1647
  for (const row of incoming) {
1644
- if (!isRecord(row)) {
1648
+ if (!isRecord$1(row)) {
1645
1649
  rows.push(row);
1646
1650
  continue;
1647
1651
  }
@@ -1666,7 +1670,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
1666
1670
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
1667
1671
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
1668
1672
  case "object": {
1669
- if (!isRecord(incoming)) {
1673
+ if (!isRecord$1(incoming)) {
1670
1674
  if (incoming === void 0) return KEEP;
1671
1675
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
1672
1676
  return {
@@ -1674,7 +1678,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
1674
1678
  value: incoming
1675
1679
  };
1676
1680
  }
1677
- const storedObj = isRecord(stored) ? stored : {};
1681
+ const storedObj = isRecord$1(stored) ? stored : {};
1678
1682
  return {
1679
1683
  kind: "value",
1680
1684
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -1733,12 +1737,19 @@ async function transformSecretWrite(input) {
1733
1737
  }
1734
1738
  };
1735
1739
  }
1740
+ /** Is `ref` one of `owner`'s refs? */
1741
+ function isSecretRefOwnedBy(ref, owner) {
1742
+ return parseSecretRef(ref)?.owner === owner;
1743
+ }
1736
1744
  /**
1737
1745
  * Replace every marker in `value` with its keychain value. Returns the SAME
1738
1746
  * reference when there is no marker (no allocation, no keychain call).
1739
1747
  */
1740
- async function resolveSecretMarkers(value, secrets) {
1741
- 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);
1742
1753
  if (refs.length === 0) return {
1743
1754
  value,
1744
1755
  unresolved: []
@@ -1762,10 +1773,18 @@ async function resolveSecretMarkers(value, secrets) {
1762
1773
  }
1763
1774
  const substitute = (v) => {
1764
1775
  if (isSecretMarker(v)) {
1776
+ if (!inScope(v.$secret)) return {
1777
+ keep: true,
1778
+ value: v
1779
+ };
1765
1780
  const answer = answers[v.$secret];
1766
- return answer?.kind === "value" ? {
1781
+ if (answer?.kind === "value") return {
1767
1782
  keep: true,
1768
1783
  value: answer.value
1784
+ };
1785
+ return keepMarker ? {
1786
+ keep: true,
1787
+ value: v
1769
1788
  } : {
1770
1789
  keep: false,
1771
1790
  value: void 0
@@ -1782,7 +1801,7 @@ async function resolveSecretMarkers(value, secrets) {
1782
1801
  value: items
1783
1802
  };
1784
1803
  }
1785
- if (isRecord(v)) {
1804
+ if (isRecord$1(v)) {
1786
1805
  const out = {};
1787
1806
  for (const [k, child] of Object.entries(v)) {
1788
1807
  const s = substitute(child);
@@ -1803,14 +1822,152 @@ async function resolveSecretMarkers(value, secrets) {
1803
1822
  unresolved
1804
1823
  };
1805
1824
  }
1806
- function touchesTree(tree, patch) {
1807
- if (tree.size === 0) return false;
1808
- for (const key of Object.keys(patch)) {
1809
- const at = key.indexOf("@");
1810
- 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]));
1811
1919
  }
1812
1920
  return false;
1813
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
+ }
1814
1971
  /** The blob as stored now; a failed read REFUSES the write instead of guessing. */
1815
1972
  async function readStoredAddonBlob(raw) {
1816
1973
  const threeWay = raw.readAddonStoreResult;
@@ -1819,7 +1976,41 @@ async function readStoredAddonBlob(raw) {
1819
1976
  if (result.kind === "failed") throw new Error(`settings write refused: the stored blob could not be read (${result.error})`);
1820
1977
  return result.kind === "missing" ? {} : result.value;
1821
1978
  }
1822
- 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) {
1823
2014
  const chains = /* @__PURE__ */ new Map();
1824
2015
  const serialize = (key, fn) => {
1825
2016
  const next = (chains.get(key) ?? Promise.resolve()).then(fn, fn);
@@ -1830,46 +2021,130 @@ function createSecretAwareSettings(raw, opts) {
1830
2021
  });
1831
2022
  return next;
1832
2023
  };
1833
- const write = async (chainKey, tree, scope, patch, readStored, rawWrite) => {
1834
- if (!touchesTree(tree, patch)) return rawWrite(patch);
1835
- 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 () => {
1836
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
+ });
1837
2050
  const result = await transformSecretWrite({
1838
- tree,
2051
+ tree: widenTreeWithStoredMarkers(declared, stored, Object.keys(restored.patch), opts.secrets.owner),
1839
2052
  secrets: opts.secrets,
1840
- patch,
2053
+ patch: restored.patch,
1841
2054
  stored,
1842
2055
  ...scope !== void 0 ? { scope } : {},
1843
- operatorWrite: opts.isOperatorWrite?.() === true
2056
+ operatorWrite
1844
2057
  });
1845
- 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
+ }
1846
2064
  const { orphaned } = await result.afterWrite();
1847
2065
  if (orphaned.length > 0) opts.onOrphaned(orphaned);
2066
+ return { changed: wroteBlob || restored.changedRefs.length > 0 };
1848
2067
  });
1849
2068
  };
1850
- const wrapped = {
1851
- writeAddonStore: (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p)),
1852
- writeDeviceStore: (deviceId, patch) => write(`device:${deviceId}`, opts.deviceTree(), {
1853
- kind: "device",
1854
- deviceId
1855
- }, 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
1856
2131
  };
1857
- return new Proxy(raw, { get(target, prop, receiver) {
1858
- if (prop === "writeAddonStore") return wrapped.writeAddonStore;
1859
- if (prop === "writeDeviceStore") return wrapped.writeDeviceStore;
1860
- const value = Reflect.get(target, prop, receiver);
1861
- return typeof value === "function" ? value.bind(target) : value;
1862
- } });
1863
- }
1864
- /** The context the addon sees: identical, except that its settings writes are secret-aware. */
1865
- function withSecretAwareSettings(context, opts) {
2132
+ }
2133
+ /** The context the addon sees, plus the private raw view and write outcome. */
2134
+ function createSecretAwareContext(context, opts) {
1866
2135
  const raw = context.settings;
1867
- if (raw === void 0) return context;
1868
- const settings = createSecretAwareSettings(raw, opts);
1869
- return new Proxy(context, { get(target, prop, receiver) {
1870
- if (prop === "settings") return settings;
1871
- return Reflect.get(target, prop, receiver);
1872
- } });
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
+ };
1873
2148
  }
1874
2149
  var LEAF = { kind: "leaf" };
1875
2150
  var DEFAULT_ITEM_ID_KEY = "id";
@@ -2057,6 +2332,14 @@ var BaseAddon = class {
2057
2332
  _secretChangeUnsub = null;
2058
2333
  /** >0 while an OPERATOR's settings write is being applied. */
2059
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();
2060
2343
  constructor(defaults) {
2061
2344
  this.defaults = defaults;
2062
2345
  this._config = { ...defaults };
@@ -2093,13 +2376,21 @@ var BaseAddon = class {
2093
2376
  }
2094
2377
  async initialize(context) {
2095
2378
  const secrets = context.secrets;
2096
- this._ctx = secrets !== void 0 ? withSecretAwareSettings(context, {
2097
- secrets,
2098
- addonTree: () => this.addonSecretTree(),
2099
- deviceTree: () => this.deviceSecretTree(),
2100
- onOrphaned: (refs) => context.logger.warn("keychain refs dropped by a settings write could not be deleted", { meta: { refs: [...refs] } }),
2101
- isOperatorWrite: () => this._operatorWriteDepth > 0
2102
- }) : 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
+ }
2103
2394
  await this.resolveConfig();
2104
2395
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
2105
2396
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -2216,11 +2507,8 @@ var BaseAddon = class {
2216
2507
  async getGlobalSettings(overlay, cap, nodeId) {
2217
2508
  const schema = this.globalSettingsSchema(cap);
2218
2509
  if (!schema) return { sections: [] };
2219
- const projected = await this.resolveGlobalStore(nodeId, cap);
2220
- return hydrateSchema(schema, overlay ? {
2221
- ...projected,
2222
- ...overlay
2223
- } : projected, { inlineSecretsAsViews: true });
2510
+ const projected = await this.readRawProjectedStore(nodeId, cap);
2511
+ return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
2224
2512
  }
2225
2513
  /**
2226
2514
  * The integration-level view of this addon's settings: exactly the sections
@@ -2265,7 +2553,7 @@ var BaseAddon = class {
2265
2553
  });
2266
2554
  }
2267
2555
  if (sections.length === 0) return null;
2268
- const projected = await this.resolveGlobalStore(nodeId);
2556
+ const projected = await this.readRawProjectedStore(nodeId);
2269
2557
  return hydrateSchema({
2270
2558
  ...schema,
2271
2559
  sections
@@ -2282,12 +2570,50 @@ var BaseAddon = class {
2282
2570
  * the store for custom option logic (option narrowing, value snapping) to
2283
2571
  * read it per-node — never `ctx.settings.readAddonStore()` directly.
2284
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.
2285
2580
  */
2286
2581
  async resolveGlobalStore(nodeId, cap) {
2287
2582
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
2583
+ return this.projectForNode(raw, nodeId, cap);
2584
+ }
2585
+ projectForNode(store, nodeId, cap) {
2288
2586
  const keys = this.perNodeKeys(cap);
2289
2587
  const node = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
2290
- 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;
2291
2617
  }
2292
2618
  /**
2293
2619
  * Whether ONE global key of this addon's store was written, read three-way
@@ -2352,8 +2678,12 @@ var BaseAddon = class {
2352
2678
  async clearSecrets(keys, nodeId) {
2353
2679
  const tree = this.addonSecretTree();
2354
2680
  const keychain = this._ctx?.secrets !== void 0;
2681
+ const stored = keychain ? await this.readAddonStoreWithRetry() : {};
2355
2682
  const patch = {};
2356
- 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
+ }
2357
2687
  await this.applyGlobalPatch(patch, nodeId);
2358
2688
  }
2359
2689
  async applyGlobalPatch(barePatch, nodeId) {
@@ -2361,7 +2691,11 @@ var BaseAddon = class {
2361
2691
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
2362
2692
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
2363
2693
  const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
2364
- 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);
2365
2699
  if (target !== localNode) return;
2366
2700
  await this.resolveConfig();
2367
2701
  await this.onConfigChanged();
@@ -2414,7 +2748,7 @@ var BaseAddon = class {
2414
2748
  async getDeviceSettings(deviceId) {
2415
2749
  const schema = this.deviceSettingsSchema();
2416
2750
  if (!schema) return { sections: [] };
2417
- return hydrateSchema(schema, await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
2751
+ return hydrateSchema(schema, await this.rawSettings()?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
2418
2752
  }
2419
2753
  async updateDeviceSettings(deviceId, patch) {
2420
2754
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
@@ -2586,7 +2920,7 @@ var BaseAddon = class {
2586
2920
  key,
2587
2921
  schema,
2588
2922
  fallback,
2589
- read: async () => this.resolveStoredSecrets(await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, false),
2923
+ read: async () => this.resolveStoredSecrets(await this.rawSettings()?.readDeviceStore(deviceId) ?? {}, false),
2590
2924
  write: async (patch) => {
2591
2925
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
2592
2926
  },
@@ -2648,6 +2982,23 @@ var BaseAddon = class {
2648
2982
  }
2649
2983
  return value;
2650
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
+ }
2651
3002
  /** Re-resolve the config (a keychain-only change, or a retry) and apply it. */
2652
3003
  scheduleSecretReresolve(delayMs) {
2653
3004
  const timerOf = delayMs === SECRET_CHANGE_DEBOUNCE_MS ? "_secretChangeTimer" : "_secretRetryTimer";
@@ -2674,7 +3025,7 @@ var BaseAddon = class {
2674
3025
  * `{}` (defaults) — the addon's first successful patch will rehydrate.
2675
3026
  */
2676
3027
  async readAddonStoreWithRetry() {
2677
- const settings = this._ctx?.settings;
3028
+ const settings = this.rawSettings();
2678
3029
  if (!settings) return {};
2679
3030
  const delaysMs = [
2680
3031
  150,
@@ -6648,6 +6999,278 @@ var DERIVED_CAMERA_SILENCED_CAP_NAMES = [
6648
6999
  "scene-monitor"
6649
7000
  ];
6650
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 = zod.z.enum([
7020
+ "onnx",
7021
+ "openvino-fp16",
7022
+ "openvino-int8",
7023
+ "coreml-fp16"
7024
+ ]);
7025
+ var FloorsSchema = zod.z.record(zod.z.string(), zod.z.number().min(0).max(1));
7026
+ zod.z.object({
7027
+ schema: zod.z.literal(TRAINING_JOB_SCHEMA),
7028
+ jobKey: zod.z.string().uuid(),
7029
+ target: zod.z.object({
7030
+ kind: zod.z.literal("detector"),
7031
+ step: zod.z.literal("object-detection")
7032
+ }),
7033
+ base: zod.z.object({
7034
+ modelId: zod.z.string().min(1),
7035
+ /** Ultralytics weights id, e.g. `yolov9m.pt`. */
7036
+ weights: zod.z.string().min(1),
7037
+ imgsz: zod.z.number().int().positive()
7038
+ }),
7039
+ baseline: zod.z.object({
7040
+ modelId: zod.z.string().min(1),
7041
+ /** The baseline ONNX the holdout comparison runs; public mirror URL. */
7042
+ onnxUrl: zod.z.string().url(),
7043
+ imgsz: zod.z.number().int().positive(),
7044
+ floors: FloorsSchema
7045
+ }),
7046
+ dataset: zod.z.object({
7047
+ versionId: zod.z.string().min(1),
7048
+ annotationHash: zod.z.string(),
7049
+ /** sha256 of the uploaded archive; the trainer refuses a mismatch (exit 65). */
7050
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
7051
+ }),
7052
+ split: zod.z.object({
7053
+ holdoutCameras: zod.z.array(zod.z.number().int()),
7054
+ /** When no camera is held out: the last N days of every camera. */
7055
+ holdoutLastDays: zod.z.number().int().nonnegative(),
7056
+ valFraction: zod.z.number().min(0).max(.5),
7057
+ seed: zod.z.number().int()
7058
+ }),
7059
+ hyper: zod.z.object({
7060
+ epochs: zod.z.number().int().positive(),
7061
+ batch: zod.z.number().int().positive(),
7062
+ lr0: zod.z.number().positive(),
7063
+ /** Layers frozen from the start of the backbone; 0 = none. */
7064
+ freeze: zod.z.number().int().nonnegative(),
7065
+ patience: zod.z.number().int().nonnegative(),
7066
+ seed: zod.z.number().int()
7067
+ }),
7068
+ replay: zod.z.object({
7069
+ source: zod.z.literal("coco2017"),
7070
+ count: zod.z.number().int().nonnegative()
7071
+ }),
7072
+ exports: zod.z.array(TrainingExportSchema).min(1),
7073
+ calibration: zod.z.object({ maxFrames: zod.z.number().int().positive() }),
7074
+ floors: zod.z.object({
7075
+ minSupport: zod.z.number().int().positive(),
7076
+ clamp: zod.z.number().min(0).max(1)
7077
+ }),
7078
+ int8Gate: zod.z.object({ maxMap50Drop: zod.z.number().min(0).max(1) }),
7079
+ catalog: zod.z.object({
7080
+ id: zod.z.string().regex(/^[a-z0-9][a-z0-9-]{2,80}$/),
7081
+ name: zod.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 = zod.z.object({
7086
+ path: zod.z.string().min(1),
7087
+ bytes: zod.z.number().int().nonnegative(),
7088
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/),
7089
+ format: zod.z.enum([
7090
+ "onnx",
7091
+ "openvino",
7092
+ "coreml",
7093
+ "pt"
7094
+ ]),
7095
+ variant: zod.z.enum(["base", "int8"])
7096
+ });
7097
+ var DraftFormatSchema = zod.z.object({
7098
+ /** RELATIVE path inside the bundle; the hub rewrites it to `camstack-local://`. */
7099
+ url: zod.z.string().min(1),
7100
+ sizeMB: zod.z.number().nonnegative(),
7101
+ isDirectory: zod.z.boolean().optional(),
7102
+ files: zod.z.array(zod.z.string()).optional(),
7103
+ runtimes: zod.z.array(zod.z.literal("python")).optional(),
7104
+ revision: zod.z.string().min(1)
7105
+ });
7106
+ /** The trainer's half of a catalog entry; the hub completes it (labels, licence, group). */
7107
+ var TrainingCatalogDraftSchema = zod.z.object({
7108
+ id: zod.z.string().min(1),
7109
+ name: zod.z.string().min(1),
7110
+ description: zod.z.string(),
7111
+ labels: zod.z.array(zod.z.unknown()),
7112
+ inputSize: zod.z.object({
7113
+ width: zod.z.number().int().positive(),
7114
+ height: zod.z.number().int().positive()
7115
+ }),
7116
+ preprocessMode: zod.z.literal("letterbox"),
7117
+ defaultClassFloors: FloorsSchema,
7118
+ formats: zod.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 = zod.z.object({
7126
+ positives: zod.z.number().int(),
7127
+ floor: zod.z.number().nullable(),
7128
+ tp: zod.z.number().int(),
7129
+ fp: zod.z.number().int(),
7130
+ fn: zod.z.number().int(),
7131
+ precision: zod.z.number().nullable(),
7132
+ recall: zod.z.number().nullable(),
7133
+ ap50: zod.z.number().nullable(),
7134
+ recallByCamera: zod.z.record(zod.z.string(), zod.z.number().nullable()).optional(),
7135
+ recallByLight: zod.z.record(zod.z.string(), zod.z.number().nullable()).optional(),
7136
+ fpByCamera: zod.z.record(zod.z.string(), zod.z.number()).optional(),
7137
+ smallFound: zod.z.number().int().optional(),
7138
+ smallTotal: zod.z.number().int().optional()
7139
+ }).passthrough();
7140
+ var TrainingEvalReportSchema = zod.z.object({
7141
+ frames: zod.z.number().int(),
7142
+ classes: zod.z.record(zod.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 = zod.z.object({
7150
+ schema: zod.z.literal(TRAINING_RESULT_SCHEMA),
7151
+ jobKey: zod.z.string(),
7152
+ status: zod.z.enum(["succeeded", "failed"]),
7153
+ error: zod.z.object({
7154
+ stage: zod.z.string(),
7155
+ code: zod.z.string(),
7156
+ message: zod.z.string()
7157
+ }).nullable(),
7158
+ trainer: zod.z.object({
7159
+ version: zod.z.string(),
7160
+ revision: zod.z.string()
7161
+ }),
7162
+ toolchain: zod.z.record(zod.z.string(), zod.z.string().nullable()),
7163
+ dataset: zod.z.object({
7164
+ versionId: zod.z.string(),
7165
+ annotationHash: zod.z.string(),
7166
+ sha256: zod.z.string(),
7167
+ frames: zod.z.object({
7168
+ train: zod.z.number().int(),
7169
+ val: zod.z.number().int(),
7170
+ holdout: zod.z.number().int()
7171
+ }),
7172
+ holdoutCameras: zod.z.array(zod.z.number().int()),
7173
+ replayImages: zod.z.number().int()
7174
+ }).optional(),
7175
+ training: zod.z.object({
7176
+ epochsRun: zod.z.number().int(),
7177
+ bestEpoch: zod.z.number().int(),
7178
+ bestMap50: zod.z.number(),
7179
+ bestMap50_95: zod.z.number(),
7180
+ seconds: zod.z.number()
7181
+ }).optional(),
7182
+ evaluation: zod.z.object({
7183
+ holdoutFrames: zod.z.number().int(),
7184
+ baseline: TrainingEvalReportSchema.nullable(),
7185
+ candidate: TrainingEvalReportSchema.nullable(),
7186
+ candidateInt8: TrainingEvalReportSchema.nullable(),
7187
+ recallDeltaByCamera: zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.number())).nullable(),
7188
+ map50: zod.z.record(zod.z.string(), zod.z.number().nullable())
7189
+ }).optional(),
7190
+ floors: zod.z.object({
7191
+ baseline: FloorsSchema,
7192
+ candidate: FloorsSchema,
7193
+ candidateInt8: FloorsSchema.nullable(),
7194
+ notes: zod.z.array(zod.z.string())
7195
+ }).optional(),
7196
+ int8: zod.z.object({
7197
+ kept: zod.z.boolean(),
7198
+ map50Fp16: zod.z.number().nullable(),
7199
+ map50Int8: zod.z.number().nullable(),
7200
+ reason: zod.z.string().nullable()
7201
+ }).optional(),
7202
+ exportsDropped: zod.z.array(zod.z.object({
7203
+ export: zod.z.string(),
7204
+ reason: zod.z.string()
7205
+ })).optional(),
7206
+ files: zod.z.array(TrainingResultFileSchema).optional(),
7207
+ bundle: zod.z.object({
7208
+ path: zod.z.literal("bundle.tar"),
7209
+ bytes: zod.z.number().int().nonnegative(),
7210
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
7211
+ }).nullable().optional(),
7212
+ catalogDraft: zod.z.object({
7213
+ base: TrainingCatalogDraftSchema,
7214
+ int8: TrainingCatalogDraftSchema.nullable()
7215
+ }).nullable().optional(),
7216
+ licence: zod.z.string()
7217
+ });
7218
+ var TrainingStageSchema = zod.z.enum([
7219
+ "prepare",
7220
+ "replay",
7221
+ "train",
7222
+ "eval",
7223
+ "export",
7224
+ "package"
7225
+ ]);
7226
+ var EventBase = {
7227
+ v: zod.z.literal(1),
7228
+ ts: zod.z.number()
7229
+ };
7230
+ zod.z.discriminatedUnion("type", [
7231
+ zod.z.object({
7232
+ ...EventBase,
7233
+ type: zod.z.literal("stage"),
7234
+ stage: TrainingStageSchema
7235
+ }),
7236
+ zod.z.object({
7237
+ ...EventBase,
7238
+ type: zod.z.literal("epoch"),
7239
+ epoch: zod.z.number().int(),
7240
+ epochs: zod.z.number().int(),
7241
+ boxLoss: zod.z.number().nullable().optional(),
7242
+ map50: zod.z.number().nullable().optional(),
7243
+ map50_95: zod.z.number().nullable().optional(),
7244
+ etaSec: zod.z.number().nullable().optional()
7245
+ }),
7246
+ zod.z.object({
7247
+ ...EventBase,
7248
+ type: zod.z.literal("metric"),
7249
+ name: zod.z.string(),
7250
+ value: zod.z.number()
7251
+ }),
7252
+ zod.z.object({
7253
+ ...EventBase,
7254
+ type: zod.z.literal("artifact"),
7255
+ path: zod.z.string(),
7256
+ bytes: zod.z.number(),
7257
+ sha256: zod.z.string()
7258
+ }),
7259
+ zod.z.object({
7260
+ ...EventBase,
7261
+ type: zod.z.literal("done"),
7262
+ status: zod.z.enum(["succeeded", "failed"]),
7263
+ resultSha256: zod.z.string().nullable().optional()
7264
+ }),
7265
+ zod.z.object({
7266
+ ...EventBase,
7267
+ type: zod.z.literal("error"),
7268
+ stage: zod.z.string(),
7269
+ code: zod.z.string(),
7270
+ message: zod.z.string()
7271
+ })
7272
+ ]);
7273
+ /**
6651
7274
  * What the gate decided about a birth, from the CALLER's point of view.
6652
7275
  *
6653
7276
  * Deliberately not `ConfirmationVerdict`: `undecided` is not a birth decision
@@ -9404,7 +10027,9 @@ var AddonHttpRouteSchema = zod.z.object({
9404
10027
  "authenticated",
9405
10028
  "admin"
9406
10029
  ]).optional(),
9407
- description: zod.z.string().optional()
10030
+ description: zod.z.string().optional(),
10031
+ /** The hub limits this route per client address before the handler runs. */
10032
+ rateLimit: zod.z.enum(["auth"]).optional()
9408
10033
  });
9409
10034
  /**
9410
10035
  * Cross-process route invocation envelope. The hub captures the
@@ -10368,6 +10993,37 @@ var BackupDestinationInfoSchema = zod.z.object({
10368
10993
  lastError: zod.z.string().optional()
10369
10994
  });
10370
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 = zod.z.object({
11002
+ /** `x25519-hkdf-aes256gcm-stream-v1`. */
11003
+ scheme: zod.z.string(),
11004
+ /** 16 hex — the backup key whose secret restores this archive. */
11005
+ keyId: zod.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 = zod.z.object({
11013
+ enabled: zod.z.boolean(),
11014
+ active: zod.z.object({
11015
+ keyId: zod.z.string(),
11016
+ publicKey: zod.z.string(),
11017
+ createdAt: zod.z.number()
11018
+ }).nullable(),
11019
+ /** Every key ever set up, newest first. */
11020
+ keys: zod.z.array(zod.z.object({
11021
+ keyId: zod.z.string(),
11022
+ createdAt: zod.z.number(),
11023
+ retiredAt: zod.z.number().nullable()
11024
+ })).readonly()
11025
+ });
11026
+ /**
10371
11027
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
10372
11028
  * Same shape the destination drill-in renders. Sourced from the per-
10373
11029
  * location `manifests.json` (no per-archive tarball read required).
@@ -10379,7 +11035,9 @@ var BackupArchiveEntrySchema = zod.z.object({
10379
11035
  sizeBytes: zod.z.number(),
10380
11036
  label: zod.z.string().optional(),
10381
11037
  /** Top-level locations included in the archive (db, addons, tls, …). */
10382
- locations: zod.z.array(zod.z.string()).readonly()
11038
+ locations: zod.z.array(zod.z.string()).readonly(),
11039
+ /** Present when the archive is encrypted; restore then needs that key. */
11040
+ encryption: BackupArchiveEncryptionSchema.optional()
10383
11041
  });
10384
11042
  var BackupEntrySchema = zod.z.object({
10385
11043
  id: zod.z.string(),
@@ -10388,7 +11046,8 @@ var BackupEntrySchema = zod.z.object({
10388
11046
  label: zod.z.string().optional(),
10389
11047
  createdAt: zod.z.number(),
10390
11048
  sizeBytes: zod.z.number(),
10391
- locations: zod.z.array(zod.z.string()).optional()
11049
+ locations: zod.z.array(zod.z.string()).optional(),
11050
+ encryption: BackupArchiveEncryptionSchema.optional()
10392
11051
  });
10393
11052
  var ArchiveEntrySchema = zod.z.object({
10394
11053
  path: zod.z.string(),
@@ -10643,11 +11302,58 @@ var backupCapability = {
10643
11302
  * this list and skips entries whose path doesn't start with
10644
11303
  * any of them.
10645
11304
  */
10646
- locations: zod.z.array(zod.z.string()).optional()
11305
+ locations: zod.z.array(zod.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: zod.z.string().optional()
10647
11313
  }), zod.z.void(), {
10648
11314
  kind: "mutation",
10649
11315
  auth: "admin"
10650
11316
  }),
11317
+ /** The encryption setting — public keys only. */
11318
+ getEncryption: method(zod.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(zod.z.object({}), zod.z.object({
11327
+ keyId: zod.z.string(),
11328
+ secretKey: zod.z.string(),
11329
+ publicKey: zod.z.string(),
11330
+ createdAt: zod.z.number(),
11331
+ replacedKeyId: zod.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(zod.z.object({}), zod.z.object({ disabledKeyId: zod.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(zod.z.object({ backupSecretKey: zod.z.string() }), zod.z.object({
11346
+ valid: zod.z.boolean(),
11347
+ keyId: zod.z.string().nullable(),
11348
+ /** The hub knows this key (active or retired). */
11349
+ known: zod.z.boolean(),
11350
+ /** It is the key new archives are encrypted to. */
11351
+ active: zod.z.boolean(),
11352
+ error: zod.z.string().optional()
11353
+ }), {
11354
+ kind: "mutation",
11355
+ auth: "admin"
11356
+ }),
10651
11357
  delete: method(zod.z.object({
10652
11358
  destinationId: zod.z.string(),
10653
11359
  backupId: zod.z.string()
@@ -14158,22 +14864,87 @@ var CustomModelDescriptorSchema = zod.z.object({
14158
14864
  stepId: zod.z.string(),
14159
14865
  entry: ModelCatalogEntrySchema
14160
14866
  });
14867
+ /** One regular file of an imported bundle, as the producer measured it. */
14868
+ var ModelBundleFileSchema = zod.z.object({
14869
+ /** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
14870
+ path: zod.z.string().min(1),
14871
+ bytes: zod.z.number().int().nonnegative(),
14872
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
14873
+ });
14874
+ var ImportModelBundleInputSchema = zod.z.object({
14875
+ /** The registry provider that keeps the model (Model Studio). */
14876
+ addonId: zod.z.string(),
14877
+ stepId: zod.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: zod.z.array(ModelCatalogEntrySchema).min(1).max(4),
14884
+ /** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
14885
+ bundle: zod.z.object({
14886
+ ticket: PeerBytesTicketSchema,
14887
+ bytes: zod.z.number().int().positive(),
14888
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
14889
+ }),
14890
+ /** Every file the tar must hold; anything missing, extra or different is refused. */
14891
+ files: zod.z.array(ModelBundleFileSchema).min(1).max(64),
14892
+ /** Convert missing engine formats and push the model to every agent afterwards. */
14893
+ distribute: zod.z.boolean()
14894
+ });
14895
+ var ImportModelBundleResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
14896
+ kind: zod.z.literal("imported"),
14897
+ modelIds: zod.z.array(zod.z.string()).readonly(),
14898
+ distributed: zod.z.array(zod.z.object({
14899
+ modelId: zod.z.string(),
14900
+ nodeId: zod.z.string(),
14901
+ format: zod.z.enum(MODEL_FORMATS),
14902
+ ok: zod.z.boolean(),
14903
+ error: zod.z.string().optional()
14904
+ })).readonly()
14905
+ }), zod.z.object({
14906
+ kind: zod.z.literal("refused"),
14907
+ reason: zod.z.enum([
14908
+ "transfer-failed",
14909
+ "checksum-mismatch",
14910
+ "bundle-mismatch",
14911
+ "invalid-entry",
14912
+ "id-taken"
14913
+ ]),
14914
+ detail: zod.z.string()
14915
+ })]);
14161
14916
  var customModelRegistryCapability = {
14162
14917
  name: "custom-model-registry",
14163
14918
  scope: "system",
14164
14919
  mode: "collection",
14165
14920
  internal: true,
14166
- methods: {
14167
- /**
14168
- * `internal: true` did not gate the mount (see the 2026-08-26 note on
14169
- * `data-store-provider`) — this was reachable on the AppRouter by ANY
14170
- * authenticated session at the default `auth: 'protected'`. The only
14171
- * consumer, the detection-pipeline's model resolution
14172
- * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
14173
- * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
14174
- * never tRPC — so `auth: 'admin'` costs it nothing.
14175
- */
14176
- listModels: method(zod.z.void(), zod.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(zod.z.void(), zod.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
+ }
14177
14948
  };
14178
14949
  /**
14179
14950
  * Query filter for settings-store collections.
@@ -14980,6 +15751,42 @@ var KeychainKeySourceSchema = zod.z.object({
14980
15751
  */
14981
15752
  sharesBackupWithDatabase: zod.z.boolean()
14982
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 = zod.z.object({
15766
+ id: zod.z.string(),
15767
+ at: zod.z.number(),
15768
+ /** `user:<id>` or {@link KEYCHAIN_RESET_TRIGGER_ACTOR}. */
15769
+ actor: zod.z.string(),
15770
+ /** Why the keychain was locked when it was reset; `null` when it was not locked. */
15771
+ lockedReason: zod.z.string().nullable(),
15772
+ /** Values moved into the archive table (ciphertext under the lost key). */
15773
+ archived: zod.z.number().int(),
15774
+ /** `keychain:secrets-orphaned-<ts>`, or `null` when nothing was stored. */
15775
+ archivedCollection: zod.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: zod.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: zod.z.array(zod.z.string()).readonly(),
15787
+ /** When an operator dismissed the notice on the Secrets page; `null` until then. */
15788
+ acknowledgedAt: zod.z.number().nullable()
15789
+ });
14983
15790
  var KeychainStatusSchema = zod.z.object({
14984
15791
  keychain: KeychainStateSchema,
14985
15792
  /** The source of the master key in use; `null` while locked. */
@@ -14997,7 +15804,9 @@ var KeychainStatusSchema = zod.z.object({
14997
15804
  /** Wrapped copies of the data key under a previous master key, kept until purged. */
14998
15805
  retiredWraps: zod.z.number().int(),
14999
15806
  recoveryKeyDownloadedAt: zod.z.number().nullable(),
15000
- secretCount: zod.z.number().int()
15807
+ secretCount: zod.z.number().int(),
15808
+ /** The most recent reset, acknowledged or not; `null` when the keychain was never reset. */
15809
+ lastReset: KeychainResetRecordSchema.nullable()
15001
15810
  });
15002
15811
  /**
15003
15812
  * Whether a stored value is still pointed at by its owner's settings.
@@ -15019,13 +15828,14 @@ var KeychainAuditActionSchema = zod.z.enum([
15019
15828
  "download-recovery-key",
15020
15829
  "delete-orphans",
15021
15830
  "reset",
15831
+ "acknowledge-reset",
15022
15832
  "initialize"
15023
15833
  ]);
15024
15834
  var KeychainAuditEntrySchema = zod.z.object({
15025
15835
  id: zod.z.string(),
15026
15836
  at: zod.z.number(),
15027
15837
  action: KeychainAuditActionSchema,
15028
- /** `addon:<id>` | `user:<id>` | `system`. */
15838
+ /** `addon:<id>` | `user:<id>` | `system` | `trigger-file`. */
15029
15839
  actor: zod.z.string(),
15030
15840
  ref: zod.z.string().nullable(),
15031
15841
  detail: zod.z.string().nullable()
@@ -15135,6 +15945,21 @@ var keychainAdminCapability = {
15135
15945
  kind: "mutation",
15136
15946
  auth: "admin",
15137
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(zod.z.object({}), zod.z.object({
15955
+ reset: KeychainResetRecordSchema.nullable(),
15956
+ pendingReentry: zod.z.array(KeychainSecretMetaSchema).readonly()
15957
+ }), { auth: "admin" }),
15958
+ /** Dismiss the "keychain was reset" notice. Audited. */
15959
+ acknowledgeReset: method(zod.z.object({}), zod.z.object({ acknowledged: zod.z.boolean() }), {
15960
+ kind: "mutation",
15961
+ auth: "admin",
15962
+ caller: "required"
15138
15963
  })
15139
15964
  }
15140
15965
  };
@@ -18391,8 +19216,8 @@ var PUBLIC_ENDPOINT_HTTP_REASON = "The public endpoint needs an https:// address
18391
19216
  * May `baseUrl` carry `role`? The address check above, plus two role rules:
18392
19217
  * the app link carries `notificationTaps` only (it is not a hub address), and
18393
19218
  * a plain-http address carries `publicEndpoint` only when the operator
18394
- * explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password or
18395
- * 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.
18396
19221
  */
18397
19222
  function checkRoleAddress(role, baseUrl, options = {}) {
18398
19223
  if (isAppLinkBase(baseUrl)) return role === "notificationTaps" ? {
@@ -19422,7 +20247,7 @@ var logDestinationCapability = {
19422
20247
  * `login-method` — collection cap through which auth addons contribute
19423
20248
  * their pre-auth login surfaces to the login page. This is the SINGLE,
19424
20249
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
19425
- * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
20250
+ * every auth addon (OIDC, WebAuthn/passkey) registers a
19426
20251
  * `login-method` provider and the PUBLIC `auth.listLoginMethods`
19427
20252
  * procedure aggregates them for the unauthenticated login page.
19428
20253
  *
@@ -19430,8 +20255,7 @@ var logDestinationCapability = {
19430
20255
  *
19431
20256
  * - `redirect` — a declarative button. The login page renders a generic
19432
20257
  * button that navigates to `startUrl` (an addon-owned HTTP route).
19433
- * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
19434
- * 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
19435
20259
  * login page needs NO change.
19436
20260
  *
19437
20261
  * - `widget` — a Module-Federation widget the login page mounts (via
@@ -19450,8 +20274,8 @@ var logDestinationCapability = {
19450
20274
  * decision.
19451
20275
  *
19452
20276
  * Every contribution carries a `stage`:
19453
- * - `primary` — shown on the first credentials screen (OIDC /
19454
- * magic-link buttons; a future usernameless passkey).
20277
+ * - `primary` — shown on the first credentials screen (OIDC
20278
+ * buttons; a usernameless passkey).
19455
20279
  * - `second-factor` — shown AFTER the password leg, gated on the
19456
20280
  * returned `factors` (passkey-as-2FA today).
19457
20281
  *
@@ -19463,7 +20287,7 @@ var logDestinationCapability = {
19463
20287
  var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
19464
20288
  /**
19465
20289
  * A declarative redirect button — the login page navigates to `startUrl`.
19466
- * OIDC and magic-link contribute this; a future SSO addon does too.
20290
+ * OIDC contributes this; a future SSO addon does too.
19467
20291
  */
19468
20292
  var RedirectLoginMethodSchema = zod.z.object({
19469
20293
  kind: zod.z.literal("redirect"),
@@ -19480,7 +20304,7 @@ var RedirectLoginMethodSchema = zod.z.object({
19480
20304
  * The provider names an external identity a signed-in user can link to
19481
20305
  * their own account (D782): `auth.listLinkableProviders` /
19482
20306
  * `beginExternalLink` offer exactly the redirect methods that set this,
19483
- * whatever their id. Absent / false — a login button only (magic-link).
20307
+ * whatever their id. Absent / false — a login button only.
19484
20308
  */
19485
20309
  linkable: zod.z.boolean().optional()
19486
20310
  });
@@ -23751,8 +24575,7 @@ var notificationRulesCapability = {
23751
24575
  * `super_admin` — collapsed onto the single boolean because every
23752
24576
  * non-admin caller is governed by the same scope-check rules; the
23753
24577
  * distinction was only ever used to route role-specific procedures
23754
- * which we no longer have. Agents are admin sessions issued via
23755
- * `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
24578
+ * which we no longer have. CLI / webhook integrations are `cst_*` tokens
23756
24579
  * with `isAdmin: false` + an explicit scope list.
23757
24580
  */
23758
24581
  /** Per-method access flavour. Materialised on every cap method via codegen. */
@@ -27033,6 +27856,26 @@ var pipelineAnalyticsCapability = {
27033
27856
  auth: "admin",
27034
27857
  timeoutMs: 60 * 6e4
27035
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(zod.z.object({
27868
+ filter: RetrainExportFilterSchema,
27869
+ layout: RetrainExportLayoutSchema
27870
+ }), zod.z.object({
27871
+ ticket: PeerBytesTicketSchema,
27872
+ frames: zod.z.number().int(),
27873
+ annotations: zod.z.number().int()
27874
+ }), {
27875
+ kind: "mutation",
27876
+ auth: "admin",
27877
+ timeoutMs: 12e4
27878
+ }),
27036
27879
  /** The counts a selection would freeze — class × camera × day/night,
27037
27880
  * completeness — without freezing anything. Index reads only. */
27038
27881
  previewRetrainDataset: method(zod.z.object({
@@ -31779,8 +32622,8 @@ version: zod.z.string().optional() }), ServerUpdateActionResultSchema, {
31779
32622
  * `smtp-provider` — pluggable email delivery surface.
31780
32623
  *
31781
32624
  * Collection cap: a deployment may install multiple SMTP relays (e.g.
31782
- * one for transactional / one for notifications). Consumers (magic-link,
31783
- * 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
31784
32627
  * ready provider OR target a specific addonId.
31785
32628
  *
31786
32629
  * The shape stays small on purpose — `sendEmail` is enough for the
@@ -32076,7 +32919,7 @@ targets: zod.z.array(zod.z.object({
32076
32919
  };
32077
32920
  /**
32078
32921
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
32079
- * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
32922
+ * providers (OIDC, SAML, …) mint an HMAC-signed token
32080
32923
  * carrying the post-callback claims before redirecting back to
32081
32924
  * `/api/auth/sso/finish`.
32082
32925
  *
@@ -32139,7 +32982,14 @@ var SsoBridgeClaimsSchema = zod.z.object({
32139
32982
  * Verified `iss` of the id_token behind an OIDC login bridge (D782). A
32140
32983
  * subject is unique only per issuer, so a link is bound to it.
32141
32984
  */
32142
- issuer: zod.z.string().optional()
32985
+ issuer: zod.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: zod.z.string().optional()
32143
32993
  });
32144
32994
  var ssoBridgeCapability = {
32145
32995
  name: "sso-bridge",
@@ -32157,7 +33007,7 @@ var ssoBridgeCapability = {
32157
33007
  * `/api/auth/sso/finish` to become admin — a full privilege escalation
32158
33008
  * through the exact surface this cap exists to close (see the module
32159
33009
  * docblock). Every real caller (`oauth-grants.ts`, `main.ts`
32160
- * registration, the OIDC/magic-link addons) reaches this through
33010
+ * registration, the OIDC addon) reaches this through
32161
33011
  * `ctx.api` / the in-process registry — never through tRPC — so
32162
33012
  * `auth: 'admin'` costs those callers nothing.
32163
33013
  */
@@ -46953,16 +47803,6 @@ var ApiKeySummarySchema = zod.z.object({
46953
47803
  createdAt: zod.z.number(),
46954
47804
  lastUsedAt: zod.z.number().optional()
46955
47805
  });
46956
- var CreateApiKeyInputSchema = zod.z.object({
46957
- label: zod.z.string(),
46958
- isAdmin: zod.z.boolean().default(false),
46959
- allowedProviders: zod.z.union([zod.z.array(zod.z.string()), zod.z.literal("*")]).optional(),
46960
- allowedDevices: zod.z.record(zod.z.string(), zod.z.union([zod.z.array(zod.z.string()), zod.z.literal("*")])).optional()
46961
- });
46962
- var CreateApiKeyResultSchema = zod.z.object({
46963
- token: zod.z.string(),
46964
- record: ApiKeySummarySchema
46965
- });
46966
47806
  var ScopedTokenSummarySchema = zod.z.object({
46967
47807
  id: zod.z.string(),
46968
47808
  userId: zod.z.string(),
@@ -47074,20 +47914,11 @@ var userManagementCapability = {
47074
47914
  access: "view"
47075
47915
  }),
47076
47916
  listApiKeys: method(zod.z.void(), zod.z.array(ApiKeySummarySchema), { auth: "admin" }),
47077
- createApiKey: method(CreateApiKeyInputSchema, CreateApiKeyResultSchema, {
47078
- kind: "mutation",
47079
- auth: "admin",
47080
- access: "create"
47081
- }),
47082
47917
  revokeApiKey: method(zod.z.object({ id: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
47083
47918
  kind: "mutation",
47084
47919
  auth: "admin",
47085
47920
  access: "delete"
47086
47921
  }),
47087
- validateApiKey: method(zod.z.object({ token: zod.z.string() }), ApiKeySummarySchema.nullable(), {
47088
- kind: "mutation",
47089
- access: "view"
47090
- }),
47091
47922
  createScopedToken: method(CreateScopedTokenInputSchema, CreateScopedTokenResultSchema, {
47092
47923
  kind: "mutation",
47093
47924
  auth: "admin",
@@ -47177,20 +48008,6 @@ var userManagementCapability = {
47177
48008
  userId: zod.z.string(),
47178
48009
  issuer: zod.z.string()
47179
48010
  }).nullable(), { auth: "admin" }),
47180
- /**
47181
- * Link `(provider, subject)` to `userId`. Idempotent for the same user;
47182
- * REFUSES (throws) an identity already linked to another user, and a
47183
- * user that does not exist.
47184
- */
47185
- linkExternalIdentity: method(ExternalIdentityKeySchema.extend({
47186
- userId: zod.z.string(),
47187
- issuer: zod.z.string().min(1),
47188
- label: zod.z.string().optional()
47189
- }), zod.z.object({ success: zod.z.literal(true) }), {
47190
- kind: "mutation",
47191
- auth: "admin",
47192
- access: "create"
47193
- }),
47194
48011
  /** Remove one link of `userId`. `success: false` when it was not linked to that user. */
47195
48012
  unlinkExternalIdentity: method(ExternalIdentityKeySchema.extend({ userId: zod.z.string() }), zod.z.object({ success: zod.z.boolean() }), {
47196
48013
  kind: "mutation",
@@ -47919,6 +48736,531 @@ var zoneRulesCapability = {
47919
48736
  */
47920
48737
  durability: "restored"
47921
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 = zod.z.object({
48762
+ id: zod.z.string(),
48763
+ label: zod.z.string(),
48764
+ /** List price, USD per GPU-hour, before any region multiplier. */
48765
+ usdPerHour: zod.z.number().nonnegative(),
48766
+ vramGb: zod.z.number().nonnegative()
48767
+ });
48768
+ var TrainingRegionOptionSchema = zod.z.object({
48769
+ /** `''` = any region (the provider's choice). */
48770
+ id: zod.z.string(),
48771
+ label: zod.z.string(),
48772
+ priceMultiplier: zod.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 = zod.z.object({
48780
+ gpuType: zod.z.string(),
48781
+ region: zod.z.string(),
48782
+ costCapUsd: zod.z.number().nonnegative(),
48783
+ maxRuntimeSec: zod.z.number().int().positive(),
48784
+ /** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
48785
+ failedDatasetGraceHours: zod.z.number().nonnegative()
48786
+ });
48787
+ var TrainingProviderDescriptorSchema = zod.z.object({
48788
+ addonId: zod.z.string(),
48789
+ /** `modal`, `runpod`, `fake`, … */
48790
+ kind: zod.z.string(),
48791
+ label: zod.z.string(),
48792
+ gpuTypes: zod.z.array(TrainingGpuOptionSchema).readonly(),
48793
+ regions: zod.z.array(TrainingRegionOptionSchema).readonly(),
48794
+ maxDatasetBytes: zod.z.number().int().positive(),
48795
+ maxRuntimeSec: zod.z.number().int().positive(),
48796
+ freeTierNote: zod.z.string().optional(),
48797
+ /** Where the data goes, in one sentence the confirm dialog repeats. */
48798
+ dataResidencyNote: zod.z.string(),
48799
+ /** Credentials are present — never the values. */
48800
+ configured: zod.z.boolean(),
48801
+ defaults: TrainingProviderDefaultsSchema
48802
+ });
48803
+ var TrainingCredentialCheckSchema = zod.z.object({
48804
+ ok: zod.z.boolean(),
48805
+ /** Workspace / account name, when the provider says. */
48806
+ account: zod.z.string().optional(),
48807
+ reason: zod.z.enum([
48808
+ "missing",
48809
+ "invalid",
48810
+ "forbidden",
48811
+ "network",
48812
+ "quota"
48813
+ ]).optional(),
48814
+ detail: zod.z.string().optional()
48815
+ });
48816
+ var ProviderJobStateSchema = zod.z.enum([
48817
+ "pending",
48818
+ "starting",
48819
+ "running",
48820
+ "succeeded",
48821
+ "failed",
48822
+ "cancelled",
48823
+ "lost"
48824
+ ]);
48825
+ var ProviderJobStatusSchema = zod.z.object({
48826
+ jobKey: zod.z.string(),
48827
+ providerJobId: zod.z.string().optional(),
48828
+ state: ProviderJobStateSchema,
48829
+ startedAt: zod.z.number().optional(),
48830
+ endedAt: zod.z.number().optional(),
48831
+ exitCode: zod.z.number().int().optional(),
48832
+ failureReason: zod.z.string().optional(),
48833
+ gpuType: zod.z.string(),
48834
+ region: zod.z.string().optional(),
48835
+ billedSeconds: zod.z.number().nonnegative(),
48836
+ costUsd: zod.z.number().nonnegative(),
48837
+ /** True when `costUsd` is runtime × list price, not a billing answer. */
48838
+ costIsEstimate: zod.z.boolean(),
48839
+ lastFetchedAt: zod.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 = zod.z.object({
48848
+ /** Hugging Face repo id, e.g. `camstack/camstack-trainer`. */
48849
+ repo: zod.z.string().min(1),
48850
+ /** A commit sha (never a branch) — the job must be reproducible. */
48851
+ revision: zod.z.string().regex(/^[0-9a-f]{7,64}$/)
48852
+ });
48853
+ var JobRef = {
48854
+ addonId: zod.z.string(),
48855
+ jobKey: zod.z.string().uuid()
48856
+ };
48857
+ var TrainingLogLineSchema = zod.z.object({
48858
+ ts: zod.z.number(),
48859
+ text: zod.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(zod.z.object({}), zod.z.array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }),
48869
+ /** Call the provider with the stored credentials. Never returns them. */
48870
+ validateCredentials: method(zod.z.object({ addonId: zod.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(zod.z.object({
48877
+ addonId: zod.z.string(),
48878
+ gpuType: zod.z.string(),
48879
+ region: zod.z.string(),
48880
+ datasetBytes: zod.z.number().int().nonnegative(),
48881
+ gpuSecondsLow: zod.z.number().nonnegative(),
48882
+ gpuSecondsHigh: zod.z.number().nonnegative()
48883
+ }), zod.z.object({
48884
+ usdLow: zod.z.number().nonnegative(),
48885
+ usdHigh: zod.z.number().nonnegative(),
48886
+ notes: zod.z.array(zod.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(zod.z.object({
48895
+ ...JobRef,
48896
+ ticket: PeerBytesTicketSchema
48897
+ }), zod.z.object({
48898
+ bytes: zod.z.number().int().nonnegative(),
48899
+ sha256: zod.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(zod.z.object({
48912
+ ...JobRef,
48913
+ trainer: TrainerSourceRefSchema,
48914
+ jobSpecJson: zod.z.string().min(2).max(64e3),
48915
+ gpuType: zod.z.string(),
48916
+ region: zod.z.string(),
48917
+ maxRuntimeSec: zod.z.number().int().positive()
48918
+ }), ProviderJobStatusSchema, {
48919
+ kind: "mutation",
48920
+ auth: "admin",
48921
+ timeoutMs: 30 * 6e4
48922
+ }),
48923
+ getJob: method(zod.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(zod.z.object({
48929
+ ...JobRef,
48930
+ cursor: zod.z.string().optional(),
48931
+ max: zod.z.number().int().min(1).max(500)
48932
+ }), zod.z.object({
48933
+ lines: zod.z.array(TrainingLogLineSchema).readonly(),
48934
+ cursor: zod.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(zod.z.object(JobRef), zod.z.object({ ok: zod.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(zod.z.object(JobRef), zod.z.object({
48947
+ found: zod.z.boolean(),
48948
+ text: zod.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(zod.z.object({
48960
+ ...JobRef,
48961
+ path: zod.z.enum(["bundle.tar"])
48962
+ }), zod.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(zod.z.object(JobRef), zod.z.object({ deleted: zod.z.array(zod.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(zod.z.object({ addonId: zod.z.string() }), zod.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: zod.z.array(zod.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 = zod.z.object({
49002
+ modelId: zod.z.string(),
49003
+ label: zod.z.string(),
49004
+ weights: zod.z.string(),
49005
+ imgsz: zod.z.number().int(),
49006
+ baselineOnnxUrl: zod.z.string(),
49007
+ baselineFloors: zod.z.record(zod.z.string(), zod.z.number()),
49008
+ /** The base the fleet runs most — the UI's default. */
49009
+ recommended: zod.z.boolean()
49010
+ });
49011
+ var TrainingPresetSchema = zod.z.enum([
49012
+ "quick",
49013
+ "standard",
49014
+ "thorough"
49015
+ ]);
49016
+ var TrainingJobRequestSchema = zod.z.object({
49017
+ datasetVersionId: zod.z.string().min(1),
49018
+ /** The `training-provider` addon that runs it. */
49019
+ addonId: zod.z.string().min(1),
49020
+ gpuType: zod.z.string().min(1),
49021
+ region: zod.z.string(),
49022
+ baseModelId: zod.z.string().min(1),
49023
+ preset: TrainingPresetSchema,
49024
+ /** Freeze the backbone's first N layers; `null` = the preset's choice. */
49025
+ freeze: zod.z.number().int().nonnegative().nullable(),
49026
+ /** Cameras held out whole for evaluation; `null` = the orchestrator's default. */
49027
+ holdoutCameras: zod.z.array(zod.z.number().int()).nullable()
49028
+ });
49029
+ var TrainingSplitPlanSchema = zod.z.object({
49030
+ holdoutCameras: zod.z.array(zod.z.number().int()).readonly(),
49031
+ /** Used when no camera is held out: the last N days of every camera. */
49032
+ holdoutLastDays: zod.z.number().int().nonnegative(),
49033
+ /** Holdout subjects per class (camera holdout only; `null` = known only after the split). */
49034
+ holdoutSupport: zod.z.record(zod.z.string(), zod.z.number().int()).nullable(),
49035
+ trainSubjects: zod.z.number().int()
49036
+ });
49037
+ var TrainingRefusalReasonSchema = zod.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 = zod.z.object({
49048
+ refusal: zod.z.object({
49049
+ reason: TrainingRefusalReasonSchema,
49050
+ detail: zod.z.string()
49051
+ }).nullable(),
49052
+ warnings: zod.z.array(zod.z.string()).readonly(),
49053
+ split: TrainingSplitPlanSchema.nullable(),
49054
+ estimate: zod.z.object({
49055
+ datasetBytes: zod.z.number().int(),
49056
+ uploadMinutesAt20Mbps: zod.z.number(),
49057
+ gpuSecondsLow: zod.z.number(),
49058
+ gpuSecondsHigh: zod.z.number(),
49059
+ usdLow: zod.z.number(),
49060
+ usdHigh: zod.z.number(),
49061
+ notes: zod.z.array(zod.z.string()).readonly()
49062
+ }).nullable(),
49063
+ /** Pre-filled from the provider's settings; the operator may lower or raise them. */
49064
+ defaults: zod.z.object({
49065
+ maxCostUsd: zod.z.number(),
49066
+ maxRuntimeSec: zod.z.number().int()
49067
+ }).nullable(),
49068
+ /** The sentence the confirm dialog repeats: where the frames go and when they are deleted. */
49069
+ dataNotice: zod.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 = zod.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 = zod.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 = zod.z.object({
49106
+ epoch: zod.z.number().int(),
49107
+ map50: zod.z.number().nullable(),
49108
+ map50_95: zod.z.number().nullable()
49109
+ });
49110
+ var TrainingCanarySchema = zod.z.object({
49111
+ deviceIds: zod.z.array(zod.z.number().int()).readonly(),
49112
+ since: zod.z.number(),
49113
+ /** Every override the canary replaced, exactly, so rollback restores it. */
49114
+ previous: zod.z.array(zod.z.object({
49115
+ deviceId: zod.z.number().int(),
49116
+ nodeId: zod.z.string(),
49117
+ deviceKey: zod.z.string(),
49118
+ patch: zod.z.object({
49119
+ modelId: zod.z.string().optional(),
49120
+ settings: zod.z.record(zod.z.string(), zod.z.unknown()).readonly().optional()
49121
+ }).nullable()
49122
+ })).readonly()
49123
+ });
49124
+ var TrainingJobSchema = zod.z.object({
49125
+ id: zod.z.string(),
49126
+ idempotencyKey: zod.z.string(),
49127
+ name: zod.z.string(),
49128
+ createdAt: zod.z.number(),
49129
+ updatedAt: zod.z.number(),
49130
+ state: TrainingJobStateSchema,
49131
+ stage: TrainingStageSchema.nullable(),
49132
+ failure: zod.z.object({
49133
+ reason: TrainingFailureReasonSchema,
49134
+ stage: zod.z.string().nullable(),
49135
+ detail: zod.z.string()
49136
+ }).nullable(),
49137
+ request: TrainingJobRequestSchema,
49138
+ maxCostUsd: zod.z.number().nonnegative(),
49139
+ maxRuntimeSec: zod.z.number().int().positive(),
49140
+ trainer: TrainerSourceRefSchema,
49141
+ datasetLayout: RetrainExportLayoutSchema,
49142
+ dataset: zod.z.object({
49143
+ bytes: zod.z.number().int(),
49144
+ sha256: zod.z.string(),
49145
+ uploadedAt: zod.z.number()
49146
+ }).nullable(),
49147
+ split: TrainingSplitPlanSchema,
49148
+ provider: zod.z.object({
49149
+ state: ProviderJobStateSchema.nullable(),
49150
+ providerJobId: zod.z.string().nullable(),
49151
+ startedAt: zod.z.number().nullable(),
49152
+ endedAt: zod.z.number().nullable(),
49153
+ billedSeconds: zod.z.number(),
49154
+ costUsd: zod.z.number(),
49155
+ costIsEstimate: zod.z.boolean(),
49156
+ lastPolledAt: zod.z.number().nullable()
49157
+ }),
49158
+ progress: zod.z.object({
49159
+ epoch: zod.z.number().int().nullable(),
49160
+ epochs: zod.z.number().int().nullable(),
49161
+ etaSec: zod.z.number().nullable(),
49162
+ series: zod.z.array(TrainingProgressPointSchema).readonly()
49163
+ }),
49164
+ /** The last lines of the job's output (≤ 200), newest last. Never frame paths. */
49165
+ logTail: zod.z.array(zod.z.string()).readonly(),
49166
+ logCursor: zod.z.string().nullable(),
49167
+ artifacts: zod.z.object({
49168
+ bundleBytes: zod.z.number().int(),
49169
+ bundleSha256: zod.z.string(),
49170
+ fetchedAt: zod.z.number(),
49171
+ /** When the staged artefacts are deleted from the hub. */
49172
+ expiresAt: zod.z.number()
49173
+ }).nullable(),
49174
+ /** When the provider-side copy of the frames was deleted; null while it exists. */
49175
+ cloudDatasetDeletedAt: zod.z.number().nullable(),
49176
+ /** A failed job's dataset is kept until then (the provider's grace), then deleted. */
49177
+ cloudCleanupDueAt: zod.z.number().nullable(),
49178
+ /** Model ids registered from this job (base, and int8 when kept). */
49179
+ registeredModelIds: zod.z.array(zod.z.string()).readonly(),
49180
+ canary: TrainingCanarySchema.nullable()
49181
+ });
49182
+ var IdInput = zod.z.object({ id: zod.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, zod.z.object({ stored: zod.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(zod.z.object({}), zod.z.array(RetrainDatasetVersionSchema).readonly(), { auth: "admin" }),
49197
+ findDatasetVersionIdByName: method(zod.z.object({ name: zod.z.string() }), zod.z.object({ id: zod.z.string().nullable() }), { auth: "admin" }),
49198
+ removeDatasetVersion: method(IdInput, zod.z.object({ removed: zod.z.boolean() }), {
49199
+ kind: "mutation",
49200
+ auth: "admin"
49201
+ }),
49202
+ listBaseModels: method(zod.z.object({}), zod.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: zod.z.string().min(1).max(120),
49215
+ maxCostUsd: zod.z.number().positive(),
49216
+ maxRuntimeSec: zod.z.number().int().positive(),
49217
+ idempotencyKey: zod.z.string().min(8).max(80)
49218
+ }), zod.z.discriminatedUnion("kind", [zod.z.object({
49219
+ kind: zod.z.literal("started"),
49220
+ job: TrainingJobSchema
49221
+ }), zod.z.object({
49222
+ kind: zod.z.literal("refused"),
49223
+ reason: TrainingRefusalReasonSchema,
49224
+ detail: zod.z.string()
49225
+ })]), {
49226
+ kind: "mutation",
49227
+ auth: "admin",
49228
+ timeoutMs: 12e4
49229
+ }),
49230
+ listJobs: method(zod.z.object({}), zod.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: zod.z.array(zod.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: zod.z.array(zod.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
+ };
47922
49264
  function isValueOf(slot, value) {
47923
49265
  return value.slot === slot;
47924
49266
  }
@@ -50367,6 +51709,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50367
51709
  addonId: null,
50368
51710
  access: "create"
50369
51711
  },
51712
+ "backup.checkBackupKey": {
51713
+ capName: "backup",
51714
+ capScope: "system",
51715
+ addonId: null,
51716
+ access: "create"
51717
+ },
50370
51718
  "backup.delete": {
50371
51719
  capName: "backup",
50372
51720
  capScope: "system",
@@ -50379,6 +51727,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
50379
51727
  addonId: null,
50380
51728
  access: "delete"
50381
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
+ },
50382
51742
  "backup.getEntries": {
50383
51743
  capName: "backup",
50384
51744
  capScope: "system",
@@ -50433,6 +51793,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50433
51793
  addonId: null,
50434
51794
  access: "create"
50435
51795
  },
51796
+ "backup.setupEncryption": {
51797
+ capName: "backup",
51798
+ capScope: "system",
51799
+ addonId: null,
51800
+ access: "create"
51801
+ },
50436
51802
  "backup.trigger": {
50437
51803
  capName: "backup",
50438
51804
  capScope: "system",
@@ -50985,6 +52351,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50985
52351
  addonId: null,
50986
52352
  access: "create"
50987
52353
  },
52354
+ "customModelRegistry.importModelBundle": {
52355
+ capName: "custom-model-registry",
52356
+ capScope: "system",
52357
+ addonId: null,
52358
+ access: "create"
52359
+ },
50988
52360
  "customModelRegistry.listModels": {
50989
52361
  capName: "custom-model-registry",
50990
52362
  capScope: "system",
@@ -52425,6 +53797,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52425
53797
  addonId: null,
52426
53798
  access: "view"
52427
53799
  },
53800
+ "keychainAdmin.acknowledgeReset": {
53801
+ capName: "keychain-admin",
53802
+ capScope: "system",
53803
+ addonId: null,
53804
+ access: "create"
53805
+ },
52428
53806
  "keychainAdmin.deleteOrphans": {
52429
53807
  capName: "keychain-admin",
52430
53808
  capScope: "system",
@@ -52461,6 +53839,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52461
53839
  addonId: null,
52462
53840
  access: "delete"
52463
53841
  },
53842
+ "keychainAdmin.resetReport": {
53843
+ capName: "keychain-admin",
53844
+ capScope: "system",
53845
+ addonId: null,
53846
+ access: "delete"
53847
+ },
52464
53848
  "keychainAdmin.reveal": {
52465
53849
  capName: "keychain-admin",
52466
53850
  capScope: "system",
@@ -54207,6 +55591,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
54207
55591
  addonId: null,
54208
55592
  access: "view"
54209
55593
  },
55594
+ "pipelineAnalytics.offerRetrainArchive": {
55595
+ capName: "pipeline-analytics",
55596
+ capScope: "device",
55597
+ addonId: null,
55598
+ access: "create"
55599
+ },
54210
55600
  "pipelineAnalytics.ownersWithMedia": {
54211
55601
  capName: "pipeline-analytics",
54212
55602
  capScope: "device",
@@ -56745,6 +58135,174 @@ var METHOD_ACCESS_MAP = Object.freeze({
56745
58135
  addonId: null,
56746
58136
  access: "view"
56747
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
+ },
56748
58306
  "turnProvider.getTurnServers": {
56749
58307
  capName: "turn-provider",
56750
58308
  capScope: "system",
@@ -56769,12 +58327,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56769
58327
  addonId: null,
56770
58328
  access: "create"
56771
58329
  },
56772
- "userManagement.createApiKey": {
56773
- capName: "user-management",
56774
- capScope: "system",
56775
- addonId: null,
56776
- access: "create"
56777
- },
56778
58330
  "userManagement.createScopedToken": {
56779
58331
  capName: "user-management",
56780
58332
  capScope: "system",
@@ -56811,12 +58363,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56811
58363
  addonId: null,
56812
58364
  access: "view"
56813
58365
  },
56814
- "userManagement.linkExternalIdentity": {
56815
- capName: "user-management",
56816
- capScope: "system",
56817
- addonId: null,
56818
- access: "create"
56819
- },
56820
58366
  "userManagement.listApiKeys": {
56821
58367
  capName: "user-management",
56822
58368
  capScope: "system",
@@ -56919,12 +58465,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56919
58465
  addonId: null,
56920
58466
  access: "create"
56921
58467
  },
56922
- "userManagement.validateApiKey": {
56923
- capName: "user-management",
56924
- capScope: "system",
56925
- addonId: null,
56926
- access: "view"
56927
- },
56928
58468
  "userManagement.validateCredentials": {
56929
58469
  capName: "user-management",
56930
58470
  capScope: "system",
@@ -58917,6 +60457,8 @@ var ALL_CAPABILITY_DEFINITIONS = [
58917
60457
  temperatureSensorCapability,
58918
60458
  terminalSessionCapability,
58919
60459
  toastCapability,
60460
+ trainingCapability,
60461
+ trainingProviderCapability,
58920
60462
  turnProviderCapability,
58921
60463
  updateCapability,
58922
60464
  userManagementCapability,
@@ -61295,6 +62837,11 @@ Object.freeze({
61295
62837
  form: "single",
61296
62838
  optional: false
61297
62839
  }],
62840
+ "training.canaryResult": [{
62841
+ name: "deviceIds",
62842
+ form: "array",
62843
+ optional: false
62844
+ }],
61298
62845
  "update.getStatus": [{
61299
62846
  name: "deviceId",
61300
62847
  form: "single",
@@ -62932,7 +64479,12 @@ var CREDENTIAL_PARAM_PATTERNS = [
62932
64479
  /^bearer$/i,
62933
64480
  /^api[_-]?key$/i,
62934
64481
  /^access[_-]?token$/i,
62935
- /^refresh[_-]?token$/i
64482
+ /^refresh[_-]?token$/i,
64483
+ /^bridge$/i,
64484
+ /^code$/i,
64485
+ /^state$/i,
64486
+ /^challenge$/i,
64487
+ /^link$/i
62936
64488
  ];
62937
64489
  function isCredentialParam(name) {
62938
64490
  return CREDENTIAL_PARAM_PATTERNS.some((re) => re.test(name));
@@ -62954,6 +64506,10 @@ function isCredentialParam(name) {
62954
64506
  * are NOT masked (those are entity references, not credentials).
62955
64507
  */
62956
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
+ }
62957
64513
  try {
62958
64514
  const u = new URL(rawUrl);
62959
64515
  let touched = false;
@@ -63309,6 +64865,12 @@ Object.defineProperty(exports, "HF_REPO", {
63309
64865
  return HF_REPO;
63310
64866
  }
63311
64867
  });
64868
+ Object.defineProperty(exports, "KEYCHAIN_RESET_TRIGGER_ACTOR", {
64869
+ enumerable: true,
64870
+ get: function() {
64871
+ return KEYCHAIN_RESET_TRIGGER_ACTOR;
64872
+ }
64873
+ });
63312
64874
  Object.defineProperty(exports, "LOG_LEVEL_RANK", {
63313
64875
  enumerable: true,
63314
64876
  get: function() {
@@ -63387,6 +64949,12 @@ Object.defineProperty(exports, "RecordingStatusSchema", {
63387
64949
  return RecordingStatusSchema;
63388
64950
  }
63389
64951
  });
64952
+ Object.defineProperty(exports, "SECRET_CLEAR", {
64953
+ enumerable: true,
64954
+ get: function() {
64955
+ return SECRET_CLEAR;
64956
+ }
64957
+ });
63390
64958
  Object.defineProperty(exports, "STORAGE_ACCESS_FALLBACK", {
63391
64959
  enumerable: true,
63392
64960
  get: function() {
@@ -63891,6 +65459,12 @@ Object.defineProperty(exports, "isEvent", {
63891
65459
  return isEvent;
63892
65460
  }
63893
65461
  });
65462
+ Object.defineProperty(exports, "isKeychainStoredField", {
65463
+ enumerable: true,
65464
+ get: function() {
65465
+ return isKeychainStoredField;
65466
+ }
65467
+ });
63894
65468
  Object.defineProperty(exports, "isLocationEnabled", {
63895
65469
  enumerable: true,
63896
65470
  get: function() {
@@ -63903,12 +65477,24 @@ Object.defineProperty(exports, "isObjectInput", {
63903
65477
  return isObjectInput;
63904
65478
  }
63905
65479
  });
65480
+ Object.defineProperty(exports, "isSecretClear", {
65481
+ enumerable: true,
65482
+ get: function() {
65483
+ return isSecretClear;
65484
+ }
65485
+ });
63906
65486
  Object.defineProperty(exports, "isSecretMarker", {
63907
65487
  enumerable: true,
63908
65488
  get: function() {
63909
65489
  return isSecretMarker;
63910
65490
  }
63911
65491
  });
65492
+ Object.defineProperty(exports, "isSecretMarkerLike", {
65493
+ enumerable: true,
65494
+ get: function() {
65495
+ return isSecretMarkerLike;
65496
+ }
65497
+ });
63912
65498
  Object.defineProperty(exports, "isVoidInput", {
63913
65499
  enumerable: true,
63914
65500
  get: function() {
@@ -63981,6 +65567,12 @@ Object.defineProperty(exports, "looseSchema", {
63981
65567
  return looseSchema;
63982
65568
  }
63983
65569
  });
65570
+ Object.defineProperty(exports, "makeSecretMarker", {
65571
+ enumerable: true,
65572
+ get: function() {
65573
+ return makeSecretMarker;
65574
+ }
65575
+ });
63984
65576
  Object.defineProperty(exports, "maskUrlCredentials", {
63985
65577
  enumerable: true,
63986
65578
  get: function() {
@@ -64311,6 +65903,12 @@ Object.defineProperty(exports, "toNodeId", {
64311
65903
  return toNodeId;
64312
65904
  }
64313
65905
  });
65906
+ Object.defineProperty(exports, "toSecretFieldView", {
65907
+ enumerable: true,
65908
+ get: function() {
65909
+ return toSecretFieldView;
65910
+ }
65911
+ });
64314
65912
  Object.defineProperty(exports, "transformSecretWrite", {
64315
65913
  enumerable: true,
64316
65914
  get: function() {