@camstack/system 1.2.381 → 1.2.383

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 (133) hide show
  1. package/dist/addon-runner.js +4 -2
  2. package/dist/addon-runner.mjs +4 -2
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/auth/admin-token-manager.d.ts +75 -0
  6. package/dist/auth/api-key-manager.d.ts +1 -14
  7. package/dist/auth/auth-audit-log.d.ts +59 -0
  8. package/dist/auth/auth-manager.d.ts +28 -16
  9. package/dist/auth/cidr.d.ts +22 -0
  10. package/dist/auth/login-session-manager.d.ts +158 -0
  11. package/dist/auth/user-management-internal.d.ts +69 -0
  12. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  13. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  14. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  15. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  16. package/dist/builtins/alerts/alerts.addon.js +1 -1
  17. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  18. package/dist/builtins/autotrack/index.js +1 -1
  19. package/dist/builtins/autotrack/index.mjs +1 -1
  20. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +14 -0
  21. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +291 -3631
  22. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +284 -3617
  23. package/dist/builtins/backup-orchestrator/encryption-key-store.d.ts +36 -0
  24. package/dist/builtins/backup-orchestrator/manifest-store.d.ts +19 -1
  25. package/dist/builtins/backup-orchestrator/restore-archive.d.ts +24 -0
  26. package/dist/builtins/calendars/calendars.addon.js +1 -1
  27. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  28. package/dist/builtins/camera-grid/index.js +1 -1
  29. package/dist/builtins/camera-grid/index.mjs +1 -1
  30. package/dist/builtins/composer/composer.addon.js +2 -2
  31. package/dist/builtins/composer/composer.addon.mjs +2 -2
  32. package/dist/builtins/console-logging/index.js +1 -1
  33. package/dist/builtins/console-logging/index.mjs +1 -1
  34. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  35. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  36. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  37. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  38. package/dist/builtins/hub-forwarder/index.js +1 -1
  39. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  40. package/dist/builtins/keychain/index.js +1110 -735
  41. package/dist/builtins/keychain/index.mjs +1109 -734
  42. package/dist/builtins/keychain/keychain-reset-trigger.d.ts +25 -0
  43. package/dist/builtins/keychain/keychain-service.d.ts +36 -3
  44. package/dist/builtins/keychain/keychain-store.d.ts +24 -1
  45. package/dist/builtins/keychain/system-held-secrets.d.ts +2 -1
  46. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  47. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  48. package/dist/builtins/local-auth/local-auth.addon.d.ts +16 -0
  49. package/dist/builtins/local-auth/local-auth.addon.js +1028 -294
  50. package/dist/builtins/local-auth/local-auth.addon.mjs +1035 -299
  51. package/dist/builtins/local-network/local-network.addon.js +3 -3
  52. package/dist/builtins/local-network/local-network.addon.mjs +3 -3
  53. package/dist/builtins/loki-logging/index.js +97 -29
  54. package/dist/builtins/loki-logging/index.mjs +97 -29
  55. package/dist/builtins/loki-logging/loki-logging.addon.d.ts +7 -23
  56. package/dist/builtins/loki-logging/retire-enabled-flag.d.ts +23 -0
  57. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  58. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  59. package/dist/builtins/platform-probe/index.js +1 -1
  60. package/dist/builtins/platform-probe/index.mjs +1 -1
  61. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  62. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  63. package/dist/builtins/snapshot/index.js +1 -1
  64. package/dist/builtins/snapshot/index.mjs +1 -1
  65. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  66. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  67. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  68. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  69. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
  70. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
  71. package/dist/builtins/system-backup/backup-encryption.d.ts +57 -0
  72. package/dist/builtins/system-backup/system-backup.service.d.ts +16 -1
  73. package/dist/builtins/system-config/cluster-secret-keeper.d.ts +7 -0
  74. package/dist/builtins/system-config/index.d.ts +1 -0
  75. package/dist/builtins/system-config/index.js +2 -0
  76. package/dist/builtins/system-config/index.mjs +2 -2
  77. package/dist/builtins/system-config/mesh-actions.d.ts +19 -0
  78. package/dist/builtins/system-config/system-config.addon.d.ts +47 -4
  79. package/dist/builtins/system-config/system-config.addon.js +87858 -43
  80. package/dist/builtins/system-config/system-config.addon.mjs +87813 -39
  81. package/dist/builtins/winston-logging/index.js +1 -1
  82. package/dist/builtins/winston-logging/index.mjs +1 -1
  83. package/dist/{child-cap-dispatch-hf-Gce_4.js → child-cap-dispatch-BD8fhJ1b.js} +7 -4
  84. package/dist/{child-cap-dispatch-BTe4eKep.mjs → child-cap-dispatch-C68N2a6Y.mjs} +11 -8
  85. package/dist/{composition-sources-4SmsP0O4.js → composition-sources-ChpT_Yw4.js} +1 -1
  86. package/dist/{composition-sources-CgOQVIwa.mjs → composition-sources-DM7Ft_tp.mjs} +1 -1
  87. package/dist/{cluster-secret-BUiG8DCo.js → constants-CDpry1_T.js} +82 -1
  88. package/dist/{cluster-secret-DgJTZ4qY.mjs → constants-e3mCa1jG.mjs} +23 -2
  89. package/dist/{dist-BugrtARh.js → dist-CUNGH0fs.js} +1755 -157
  90. package/dist/{dist-BC0GnGeg.mjs → dist-osl3bkF0.mjs} +1732 -158
  91. package/dist/{event-loop-stall-monitor-GgpnfAC0.mjs → event-loop-stall-monitor-BeeWMnyT.mjs} +15 -15
  92. package/dist/{event-loop-stall-monitor-CnQ-K6Gn.js → event-loop-stall-monitor-Dk28EF8B.js} +15 -15
  93. package/dist/{hub-cap-forward-CH6_VWVO.mjs → hub-cap-forward-CDiN1p98.mjs} +18 -2
  94. package/dist/{hub-cap-forward-COCmYaup.js → hub-cap-forward-W8oEm4ME.js} +23 -1
  95. package/dist/index.d.ts +16 -1
  96. package/dist/index.js +1023 -83525
  97. package/dist/index.mjs +1218 -83794
  98. package/dist/{jwt-signing-key-CsuVKA1W.mjs → jwt-signing-key-MQLxLfgF.mjs} +1 -1
  99. package/dist/{jwt-signing-key-CjoZcol8.js → jwt-signing-key-WI6z7JBo.js} +1 -1
  100. package/dist/kernel/index.d.ts +17 -1
  101. package/dist/kernel/moleculer/agent-mesh-authenticator.d.ts +41 -0
  102. package/dist/kernel/moleculer/broker-factory.d.ts +25 -2
  103. package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +1 -0
  104. package/dist/kernel/moleculer/hub-service.d.ts +9 -0
  105. package/dist/kernel/moleculer/mesh-access-middleware.d.ts +33 -0
  106. package/dist/kernel/moleculer/mesh-auth-mode.d.ts +64 -0
  107. package/dist/kernel/moleculer/mesh-authenticator.d.ts +71 -0
  108. package/dist/kernel/moleculer/mesh-auto-enforce.d.ts +26 -0
  109. package/dist/kernel/moleculer/mesh-join-client.d.ts +51 -0
  110. package/dist/kernel/moleculer/mesh-join-proof.d.ts +39 -0
  111. package/dist/kernel/moleculer/mesh-mtls/constants.d.ts +20 -0
  112. package/dist/kernel/moleculer/mesh-mtls/index.d.ts +9 -0
  113. package/dist/kernel/moleculer/mesh-mtls/mesh-ca-keeper.d.ts +55 -0
  114. package/dist/kernel/moleculer/mesh-mtls/mesh-ca.d.ts +45 -0
  115. package/dist/kernel/moleculer/mesh-mtls/mesh-crl.d.ts +16 -0
  116. package/dist/kernel/moleculer/mesh-mtls/mesh-enroll.d.ts +27 -0
  117. package/dist/kernel/moleculer/mesh-mtls/mesh-join-token.d.ts +55 -0
  118. package/dist/kernel/moleculer/mesh-mtls/mesh-tls-enabler.d.ts +5 -0
  119. package/dist/kernel/moleculer/mesh-mtls/tls-tcp-transporter.d.ts +92 -0
  120. package/dist/kernel/system-settings-policy.d.ts +4 -3
  121. package/dist/kernel/transport/hub-cap-forward.d.ts +14 -3
  122. package/dist/kernel/transport/index.d.ts +1 -1
  123. package/dist/ms-DlIWNkkk.mjs +160 -0
  124. package/dist/ms-t68_FaqE.js +171 -0
  125. package/dist/restore-archive-C5f9yX90.js +4139 -0
  126. package/dist/restore-archive-CZRtI1k9.mjs +4045 -0
  127. package/dist/{retired-settings-keys-MCVhxPkJ.mjs → retired-settings-keys-LuDd4y3M.mjs} +1 -1
  128. package/dist/{retired-settings-keys-DCuetwOt.js → retired-settings-keys-WLrbzdPR.js} +1 -1
  129. package/dist/tls-BWpwU-d5.js +1160 -0
  130. package/dist/tls-C2NT-Orr.mjs +949 -0
  131. package/dist/{tls-CCMX7MjX.js → x509.es-BhTzcFhZ.js} +65 -1108
  132. package/dist/{tls-CJHogwTl.mjs → x509.es-Xf3K_brW.mjs} +40 -950
  133. package/package.json +3 -2
@@ -708,7 +708,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
708
708
  return EventCategory;
709
709
  }({});
710
710
  //#endregion
711
- //#region ../types/dist/sleep-DujUvOcb.mjs
711
+ //#region ../types/dist/sleep-r3RWkGVx.mjs
712
712
  /**
713
713
  * The audio chunk plane's byte format, and the ONE expansion from a coded
714
714
  * window to float samples (D455).
@@ -820,21 +820,21 @@ function collectSecretConfigKeys(schema) {
820
820
  for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkField(field, keys);
821
821
  return keys;
822
822
  }
823
- function isRecord$2(value) {
823
+ function isRecord$3(value) {
824
824
  return typeof value === "object" && value !== null && !Array.isArray(value);
825
825
  }
826
826
  function sectionsOf(schema) {
827
- if (!isRecord$2(schema)) return [];
827
+ if (!isRecord$3(schema)) return [];
828
828
  const sections = schema["sections"];
829
829
  return Array.isArray(sections) ? sections : [];
830
830
  }
831
831
  function fieldsOf(node) {
832
- if (!isRecord$2(node)) return [];
832
+ if (!isRecord$3(node)) return [];
833
833
  const fields = node["fields"];
834
834
  return Array.isArray(fields) ? fields : [];
835
835
  }
836
836
  function walkField(field, out) {
837
- if (!isRecord$2(field)) return;
837
+ if (!isRecord$3(field)) return;
838
838
  const type = field["type"];
839
839
  const key = field["key"];
840
840
  if ((type === "password" || field["secret"] === true) && typeof key === "string" && key.length > 0) out.add(key);
@@ -872,7 +872,7 @@ function walkField(field, out) {
872
872
  var SECRET_CLEAR = { $secretClear: true };
873
873
  /** The `'***'` token D634 redaction writes for non-admins. */
874
874
  var LEGACY_DEVICE_REDACTION = "***";
875
- function isRecord$1(value) {
875
+ function isRecord$2(value) {
876
876
  return typeof value === "object" && value !== null && !Array.isArray(value);
877
877
  }
878
878
  function makeSecretMarker(ref) {
@@ -883,16 +883,16 @@ function makeSecretMarker(ref) {
883
883
  }
884
884
  /** A stored marker: an object whose `$secret` is a non-empty string. */
885
885
  function isSecretMarker(value) {
886
- if (!isRecord$1(value)) return false;
886
+ if (!isRecord$2(value)) return false;
887
887
  const ref = value["$secret"];
888
888
  return typeof ref === "string" && ref.length > 0;
889
889
  }
890
890
  /** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
891
891
  function isSecretMarkerLike(value) {
892
- return isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
892
+ return isRecord$2(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
893
893
  }
894
894
  function isSecretClear(value) {
895
- return isRecord$1(value) && value["$secretClear"] === true;
895
+ return isRecord$2(value) && value["$secretClear"] === true;
896
896
  }
897
897
  /**
898
898
  * The UI view of a stored secret field value: a marker becomes "set", inline
@@ -1561,7 +1561,7 @@ var KeychainWriteRefusedError = class extends Error {
1561
1561
  }
1562
1562
  };
1563
1563
  var KEEP = { kind: "keep" };
1564
- function isRecord(value) {
1564
+ function isRecord$1(value) {
1565
1565
  return typeof value === "object" && value !== null && !Array.isArray(value);
1566
1566
  }
1567
1567
  function defaultMintId() {
@@ -1574,7 +1574,7 @@ function collectSecretMarkerRefs(value, out = []) {
1574
1574
  return out;
1575
1575
  }
1576
1576
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
1577
- else if (isRecord(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
1577
+ else if (isRecord$1(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
1578
1578
  return out;
1579
1579
  }
1580
1580
  async function storedValueEquals(secrets, marker, candidate) {
@@ -1629,7 +1629,7 @@ function rowId(row, idKey) {
1629
1629
  return null;
1630
1630
  }
1631
1631
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
1632
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord) : [];
1632
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$1) : [];
1633
1633
  if (!Array.isArray(incoming)) {
1634
1634
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
1635
1635
  return {
@@ -1645,7 +1645,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
1645
1645
  const kept = /* @__PURE__ */ new Set();
1646
1646
  const rows = [];
1647
1647
  for (const row of incoming) {
1648
- if (!isRecord(row)) {
1648
+ if (!isRecord$1(row)) {
1649
1649
  rows.push(row);
1650
1650
  continue;
1651
1651
  }
@@ -1670,7 +1670,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
1670
1670
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
1671
1671
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
1672
1672
  case "object": {
1673
- if (!isRecord(incoming)) {
1673
+ if (!isRecord$1(incoming)) {
1674
1674
  if (incoming === void 0) return KEEP;
1675
1675
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
1676
1676
  return {
@@ -1678,7 +1678,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
1678
1678
  value: incoming
1679
1679
  };
1680
1680
  }
1681
- const storedObj = isRecord(stored) ? stored : {};
1681
+ const storedObj = isRecord$1(stored) ? stored : {};
1682
1682
  return {
1683
1683
  kind: "value",
1684
1684
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -1737,12 +1737,19 @@ async function transformSecretWrite(input) {
1737
1737
  }
1738
1738
  };
1739
1739
  }
1740
+ /** Is `ref` one of `owner`'s refs? */
1741
+ function isSecretRefOwnedBy(ref, owner) {
1742
+ return parseSecretRef(ref)?.owner === owner;
1743
+ }
1740
1744
  /**
1741
1745
  * Replace every marker in `value` with its keychain value. Returns the SAME
1742
1746
  * reference when there is no marker (no allocation, no keychain call).
1743
1747
  */
1744
- async function resolveSecretMarkers(value, secrets) {
1745
- const refs = collectSecretMarkerRefs(value);
1748
+ async function resolveSecretMarkers(value, secrets, options = {}) {
1749
+ const keepMarker = options.unresolved === "keep-marker";
1750
+ const owner = options.ownedBy;
1751
+ const inScope = (ref) => owner === void 0 || isSecretRefOwnedBy(ref, owner);
1752
+ const refs = collectSecretMarkerRefs(value).filter(inScope);
1746
1753
  if (refs.length === 0) return {
1747
1754
  value,
1748
1755
  unresolved: []
@@ -1766,10 +1773,18 @@ async function resolveSecretMarkers(value, secrets) {
1766
1773
  }
1767
1774
  const substitute = (v) => {
1768
1775
  if (isSecretMarker(v)) {
1776
+ if (!inScope(v.$secret)) return {
1777
+ keep: true,
1778
+ value: v
1779
+ };
1769
1780
  const answer = answers[v.$secret];
1770
- return answer?.kind === "value" ? {
1781
+ if (answer?.kind === "value") return {
1771
1782
  keep: true,
1772
1783
  value: answer.value
1784
+ };
1785
+ return keepMarker ? {
1786
+ keep: true,
1787
+ value: v
1773
1788
  } : {
1774
1789
  keep: false,
1775
1790
  value: void 0
@@ -1786,7 +1801,7 @@ async function resolveSecretMarkers(value, secrets) {
1786
1801
  value: items
1787
1802
  };
1788
1803
  }
1789
- if (isRecord(v)) {
1804
+ if (isRecord$1(v)) {
1790
1805
  const out = {};
1791
1806
  for (const [k, child] of Object.entries(v)) {
1792
1807
  const s = substitute(child);
@@ -1807,14 +1822,152 @@ async function resolveSecretMarkers(value, secrets) {
1807
1822
  unresolved
1808
1823
  };
1809
1824
  }
1810
- function touchesTree(tree, patch) {
1811
- if (tree.size === 0) return false;
1812
- for (const key of Object.keys(patch)) {
1813
- const at = key.indexOf("@");
1814
- if (tree.has(at > 0 ? key.slice(0, at) : key)) return true;
1825
+ /** An addon tried to replace a stored secret it could not read. Nothing was written. */
1826
+ var SecretOverwriteRefusedError = class extends Error {
1827
+ ref;
1828
+ reason;
1829
+ constructor(ref, reason) {
1830
+ super(`secret ${ref} was not replaced: its stored value cannot be read right now (${reason}), and an addon may not overwrite a secret it cannot see. Nothing was written.`);
1831
+ this.ref = ref;
1832
+ this.reason = reason;
1833
+ this.name = "SecretOverwriteRefusedError";
1834
+ }
1835
+ };
1836
+ function isRecord(value) {
1837
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1838
+ }
1839
+ function resolvedValue(pass, marker) {
1840
+ const answer = pass.answers[marker.$secret];
1841
+ return answer?.kind === "value" ? answer.value : null;
1842
+ }
1843
+ async function atMarker(pass, incoming, stored) {
1844
+ if (incoming === null) return stored;
1845
+ if (typeof incoming !== "string") return incoming;
1846
+ if (incoming === resolvedValue(pass, stored)) return stored;
1847
+ const intent = classifySecretWrite(incoming);
1848
+ if (intent.kind !== "set") return stored;
1849
+ const ref = stored.$secret;
1850
+ if (!isSecretRefOwnedBy(ref, pass.secrets.owner)) return incoming;
1851
+ const answer = pass.answers[ref];
1852
+ if ((answer === void 0 || answer.kind === "unavailable" || answer.kind === "forbidden") && !pass.operatorWrite) throw new SecretOverwriteRefusedError(ref, answer === void 0 ? "not-initialized" : answer.kind === "unavailable" ? answer.reason : answer.kind);
1853
+ try {
1854
+ await pass.secrets.put(ref, intent.value);
1855
+ } catch (err) {
1856
+ throw new KeychainWriteRefusedError(ref, err);
1857
+ }
1858
+ pass.changedRefs.push(ref);
1859
+ return stored;
1860
+ }
1861
+ function inArray(pass, incoming, stored) {
1862
+ return incoming.map((item, index) => {
1863
+ const twin = stored[index];
1864
+ if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
1865
+ if (isRecord(item) && isRecord(twin)) return inArrayRow(pass, item, twin);
1866
+ if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
1867
+ return item;
1868
+ });
1869
+ }
1870
+ function inArrayRow(pass, incoming, stored) {
1871
+ const out = { ...incoming };
1872
+ for (const [key, value] of Object.entries(incoming)) {
1873
+ const twin = stored[key];
1874
+ if (isSecretMarker(twin)) {
1875
+ if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
1876
+ } else if (isRecord(value) && isRecord(twin)) out[key] = inArrayRow(pass, value, twin);
1877
+ else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
1878
+ }
1879
+ return out;
1880
+ }
1881
+ async function walk(pass, incoming, stored) {
1882
+ if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
1883
+ if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
1884
+ if (!isRecord(incoming) || !isRecord(stored)) return incoming;
1885
+ const out = { ...incoming };
1886
+ for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
1887
+ return out;
1888
+ }
1889
+ /** Restore markers a write-back carries as values. See the file doc. */
1890
+ async function restoreUnchangedSecrets(input) {
1891
+ const refs = [];
1892
+ for (const key of Object.keys(input.patch)) collectSecretMarkerRefs(input.stored[key], refs);
1893
+ const owned = refs.filter((ref) => isSecretRefOwnedBy(ref, input.secrets.owner));
1894
+ if (owned.length === 0) return {
1895
+ patch: input.patch,
1896
+ changedRefs: []
1897
+ };
1898
+ const pass = {
1899
+ secrets: input.secrets,
1900
+ answers: await input.secrets.resolve([...new Set(owned)]),
1901
+ operatorWrite: input.operatorWrite,
1902
+ changedRefs: []
1903
+ };
1904
+ const out = {};
1905
+ for (const [key, value] of Object.entries(input.patch)) out[key] = await walk(pass, value, input.stored[key]);
1906
+ return {
1907
+ patch: out,
1908
+ changedRefs: pass.changedRefs
1909
+ };
1910
+ }
1911
+ /** Structural equality over JSON-shaped values (key order does not matter). */
1912
+ function isJsonEqual(a, b) {
1913
+ if (Object.is(a, b)) return true;
1914
+ if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
1915
+ if (isRecord(a) && isRecord(b)) {
1916
+ const keys = Object.keys(a);
1917
+ if (keys.length !== Object.keys(b).length) return false;
1918
+ return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
1815
1919
  }
1816
1920
  return false;
1817
1921
  }
1922
+ /** The patch without the keys whose value already equals the stored one. */
1923
+ function dropUnchangedKeys(patch, stored) {
1924
+ const out = {};
1925
+ for (const [key, value] of Object.entries(patch)) {
1926
+ if (Object.prototype.hasOwnProperty.call(stored, key) && isJsonEqual(value, stored[key])) continue;
1927
+ out[key] = value;
1928
+ }
1929
+ return out;
1930
+ }
1931
+ /** The top-level keys of a blob that hold at least one of `owner`'s markers. */
1932
+ function markerKeysOf(blob, owner) {
1933
+ const keys = /* @__PURE__ */ new Set();
1934
+ for (const [key, value] of Object.entries(blob)) if (collectSecretMarkerRefs(value).some((ref) => isSecretRefOwnedBy(ref, owner))) keys.add(key);
1935
+ return keys;
1936
+ }
1937
+ function nodeFromStored(value, owner) {
1938
+ if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
1939
+ if (!isRecord(value)) return null;
1940
+ const children = /* @__PURE__ */ new Map();
1941
+ for (const [key, child] of Object.entries(value)) {
1942
+ const node = nodeFromStored(child, owner);
1943
+ if (node !== null) children.set(key, node);
1944
+ }
1945
+ return children.size > 0 ? {
1946
+ kind: "object",
1947
+ children
1948
+ } : null;
1949
+ }
1950
+ /**
1951
+ * The declared secret tree, widened by every place the STORED blob holds a
1952
+ * marker under one of `keys` — so a marker the schema no longer declares (or
1953
+ * never declared: Wyze before b4906cec6) still gets the save path's
1954
+ * KEEP / SET / CLEAR semantics instead of being overwritten by whatever the
1955
+ * addon writes. Markers inside arrays are not added: a row has no id field to
1956
+ * match by unless the schema declares one. Only `owner`'s markers count: a
1957
+ * builtin that keeps another addon's marker (device-manager) never takes it
1958
+ * over.
1959
+ */
1960
+ function widenTreeWithStoredMarkers(declared, stored, keys, owner) {
1961
+ const added = /* @__PURE__ */ new Map();
1962
+ for (const key of keys) {
1963
+ const at = key.indexOf("@");
1964
+ const base = at > 0 ? key.slice(0, at) : key;
1965
+ if (declared.has(base) || added.has(base)) continue;
1966
+ const node = nodeFromStored(stored[key], owner);
1967
+ if (node !== null) added.set(base, node);
1968
+ }
1969
+ return added.size === 0 ? declared : new Map([...declared, ...added]);
1970
+ }
1818
1971
  /** The blob as stored now; a failed read REFUSES the write instead of guessing. */
1819
1972
  async function readStoredAddonBlob(raw) {
1820
1973
  const threeWay = raw.readAddonStoreResult;
@@ -1823,7 +1976,41 @@ async function readStoredAddonBlob(raw) {
1823
1976
  if (result.kind === "failed") throw new Error(`settings write refused: the stored blob could not be read (${result.error})`);
1824
1977
  return result.kind === "missing" ? {} : result.value;
1825
1978
  }
1826
- function createSecretAwareSettings(raw, opts) {
1979
+ function touchesTree(tree, patch) {
1980
+ if (tree.size === 0) return false;
1981
+ for (const key of Object.keys(patch)) {
1982
+ const at = key.indexOf("@");
1983
+ if (tree.has(at > 0 ? key.slice(0, at) : key)) return true;
1984
+ }
1985
+ return false;
1986
+ }
1987
+ /**
1988
+ * Which top-level keys of each store held markers when last seen. `undefined`
1989
+ * for a store not seen yet: its first write reads it, to be safe.
1990
+ */
1991
+ var MarkerKeyIndex = class {
1992
+ owner;
1993
+ byStore = /* @__PURE__ */ new Map();
1994
+ constructor(owner) {
1995
+ this.owner = owner;
1996
+ }
1997
+ seen(store, blob) {
1998
+ this.byStore.set(store, markerKeysOf(blob, this.owner));
1999
+ }
2000
+ wrote(store, patch) {
2001
+ const keys = this.byStore.get(store);
2002
+ if (keys === void 0) return;
2003
+ const now = markerKeysOf(patch, this.owner);
2004
+ for (const key of Object.keys(patch)) if (now.has(key)) keys.add(key);
2005
+ else keys.delete(key);
2006
+ }
2007
+ mayHoldMarkers(store, patch) {
2008
+ const keys = this.byStore.get(store);
2009
+ if (keys === void 0) return true;
2010
+ return Object.keys(patch).some((key) => keys.has(key));
2011
+ }
2012
+ };
2013
+ function createHandle(raw, opts) {
1827
2014
  const chains = /* @__PURE__ */ new Map();
1828
2015
  const serialize = (key, fn) => {
1829
2016
  const next = (chains.get(key) ?? Promise.resolve()).then(fn, fn);
@@ -1834,46 +2021,130 @@ function createSecretAwareSettings(raw, opts) {
1834
2021
  });
1835
2022
  return next;
1836
2023
  };
1837
- const write = async (chainKey, tree, scope, patch, readStored, rawWrite) => {
1838
- if (!touchesTree(tree, patch)) return rawWrite(patch);
1839
- return serialize(chainKey, async () => {
2024
+ const index = new MarkerKeyIndex(opts.secrets.owner);
2025
+ const resolveRead = async (store, blob) => {
2026
+ index.seen(store, blob);
2027
+ const { value, unresolved } = await resolveSecretMarkers(blob, opts.secrets, {
2028
+ unresolved: "keep-marker",
2029
+ ownedBy: opts.secrets.owner
2030
+ });
2031
+ if (unresolved.length > 0) opts.onUnresolved?.(unresolved);
2032
+ return value;
2033
+ };
2034
+ const write = async (store, declared, scope, patch, readStored, rawWrite) => {
2035
+ if (!touchesTree(declared, patch) && !index.mayHoldMarkers(store, patch)) {
2036
+ await rawWrite(patch);
2037
+ index.wrote(store, patch);
2038
+ return { changed: true };
2039
+ }
2040
+ return serialize(store, async () => {
1840
2041
  const stored = await readStored();
2042
+ index.seen(store, stored);
2043
+ const operatorWrite = opts.isOperatorWrite?.() === true;
2044
+ const restored = await restoreUnchangedSecrets({
2045
+ patch,
2046
+ stored,
2047
+ secrets: opts.secrets,
2048
+ operatorWrite
2049
+ });
1841
2050
  const result = await transformSecretWrite({
1842
- tree,
2051
+ tree: widenTreeWithStoredMarkers(declared, stored, Object.keys(restored.patch), opts.secrets.owner),
1843
2052
  secrets: opts.secrets,
1844
- patch,
2053
+ patch: restored.patch,
1845
2054
  stored,
1846
2055
  ...scope !== void 0 ? { scope } : {},
1847
- operatorWrite: opts.isOperatorWrite?.() === true
2056
+ operatorWrite
1848
2057
  });
1849
- if (Object.keys(result.patch).length > 0) await rawWrite(result.patch);
2058
+ const effective = dropUnchangedKeys(result.patch, stored);
2059
+ const wroteBlob = Object.keys(effective).length > 0;
2060
+ if (wroteBlob) {
2061
+ await rawWrite(effective);
2062
+ index.wrote(store, effective);
2063
+ }
1850
2064
  const { orphaned } = await result.afterWrite();
1851
2065
  if (orphaned.length > 0) opts.onOrphaned(orphaned);
2066
+ return { changed: wroteBlob || restored.changedRefs.length > 0 };
1852
2067
  });
1853
2068
  };
1854
- const wrapped = {
1855
- writeAddonStore: (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p)),
1856
- writeDeviceStore: (deviceId, patch) => write(`device:${deviceId}`, opts.deviceTree(), {
1857
- kind: "device",
1858
- deviceId
1859
- }, patch, () => raw.readDeviceStore(deviceId), (p) => raw.writeDeviceStore(deviceId, p))
2069
+ const writeAddon = (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p));
2070
+ const readAddonStoreResult = raw.readAddonStoreResult;
2071
+ const readAddonStoreKeyResult = raw.readAddonStoreKeyResult;
2072
+ const readDeviceStoreBatch = raw.readDeviceStoreBatch;
2073
+ const overrides = {
2074
+ readAddonStore: async () => resolveRead("addon", await raw.readAddonStore()),
2075
+ ...readAddonStoreResult !== void 0 ? { readAddonStoreResult: async () => {
2076
+ const result = await readAddonStoreResult.call(raw);
2077
+ return result.kind === "value" ? {
2078
+ kind: "value",
2079
+ value: await resolveRead("addon", result.value)
2080
+ } : result;
2081
+ } } : {},
2082
+ ...readAddonStoreKeyResult !== void 0 ? { readAddonStoreKeyResult: async (key) => {
2083
+ const result = await readAddonStoreKeyResult.call(raw, key);
2084
+ if (result.kind !== "present") return result;
2085
+ const { value, unresolved } = await resolveSecretMarkers({ [key]: result.value }, opts.secrets, {
2086
+ unresolved: "keep-marker",
2087
+ ownedBy: opts.secrets.owner
2088
+ });
2089
+ if (unresolved.length > 0) opts.onUnresolved?.(unresolved);
2090
+ return {
2091
+ kind: "present",
2092
+ value: value[key]
2093
+ };
2094
+ } } : {},
2095
+ readDeviceStore: async (deviceId) => resolveRead(`device:${deviceId}`, await raw.readDeviceStore(deviceId)),
2096
+ ...readDeviceStoreBatch !== void 0 ? { readDeviceStoreBatch: async (deviceIds) => {
2097
+ const rows = await readDeviceStoreBatch.call(raw, deviceIds);
2098
+ const out = /* @__PURE__ */ new Map();
2099
+ for (const [deviceId, blob] of rows) out.set(deviceId, await resolveRead(`device:${deviceId}`, blob));
2100
+ return out;
2101
+ } } : {},
2102
+ writeAddonStore: async (patch) => {
2103
+ await writeAddon(patch);
2104
+ },
2105
+ writeDeviceStore: async (deviceId, patch) => {
2106
+ await write(`device:${deviceId}`, opts.deviceTree(), {
2107
+ kind: "device",
2108
+ deviceId
2109
+ }, patch, () => raw.readDeviceStore(deviceId), (p) => raw.writeDeviceStore(deviceId, p));
2110
+ }
2111
+ };
2112
+ return {
2113
+ settings: new Proxy(raw, { get(target, prop, receiver) {
2114
+ if (typeof prop === "string" && Object.prototype.hasOwnProperty.call(overrides, prop)) return Reflect.get(overrides, prop);
2115
+ const value = Reflect.get(target, prop, receiver);
2116
+ return typeof value === "function" ? value.bind(target) : value;
2117
+ } }),
2118
+ raw: {
2119
+ readAddonStore: async () => {
2120
+ const blob = await raw.readAddonStore();
2121
+ index.seen("addon", blob);
2122
+ return blob;
2123
+ },
2124
+ readDeviceStore: async (deviceId) => {
2125
+ const blob = await raw.readDeviceStore(deviceId);
2126
+ index.seen(`device:${deviceId}`, blob);
2127
+ return blob;
2128
+ }
2129
+ },
2130
+ writeAddonStore: writeAddon
1860
2131
  };
1861
- return new Proxy(raw, { get(target, prop, receiver) {
1862
- if (prop === "writeAddonStore") return wrapped.writeAddonStore;
1863
- if (prop === "writeDeviceStore") return wrapped.writeDeviceStore;
1864
- const value = Reflect.get(target, prop, receiver);
1865
- return typeof value === "function" ? value.bind(target) : value;
1866
- } });
1867
- }
1868
- /** The context the addon sees: identical, except that its settings writes are secret-aware. */
1869
- function withSecretAwareSettings(context, opts) {
2132
+ }
2133
+ /** The context the addon sees, plus the private raw view and write outcome. */
2134
+ function createSecretAwareContext(context, opts) {
1870
2135
  const raw = context.settings;
1871
- if (raw === void 0) return context;
1872
- const settings = createSecretAwareSettings(raw, opts);
1873
- return new Proxy(context, { get(target, prop, receiver) {
1874
- if (prop === "settings") return settings;
1875
- return Reflect.get(target, prop, receiver);
1876
- } });
2136
+ if (raw === void 0) return {
2137
+ context,
2138
+ handle: null
2139
+ };
2140
+ const handle = createHandle(raw, opts);
2141
+ return {
2142
+ context: new Proxy(context, { get(target, prop, receiver) {
2143
+ if (prop === "settings") return handle.settings;
2144
+ return Reflect.get(target, prop, receiver);
2145
+ } }),
2146
+ handle
2147
+ };
1877
2148
  }
1878
2149
  var LEAF = { kind: "leaf" };
1879
2150
  var DEFAULT_ITEM_ID_KEY = "id";
@@ -2061,6 +2332,14 @@ var BaseAddon = class {
2061
2332
  _secretChangeUnsub = null;
2062
2333
  /** >0 while an OPERATOR's settings write is being applied. */
2063
2334
  _operatorWriteDepth = 0;
2335
+ /**
2336
+ * The keychain-aware settings handle (D798). Its `raw` view is the ONLY way
2337
+ * to the marker-bearing blobs and stays private to this class: the form
2338
+ * hydrate and config resolution need markers, addon code never does.
2339
+ */
2340
+ _secretSettings = null;
2341
+ /** Refs an addon-facing read could not resolve — each is logged once per process. */
2342
+ _loggedUnresolvedReads = /* @__PURE__ */ new Set();
2064
2343
  constructor(defaults) {
2065
2344
  this.defaults = defaults;
2066
2345
  this._config = { ...defaults };
@@ -2097,13 +2376,21 @@ var BaseAddon = class {
2097
2376
  }
2098
2377
  async initialize(context) {
2099
2378
  const secrets = context.secrets;
2100
- this._ctx = secrets !== void 0 ? withSecretAwareSettings(context, {
2101
- secrets,
2102
- addonTree: () => this.addonSecretTree(),
2103
- deviceTree: () => this.deviceSecretTree(),
2104
- onOrphaned: (refs) => context.logger.warn("keychain refs dropped by a settings write could not be deleted", { meta: { refs: [...refs] } }),
2105
- isOperatorWrite: () => this._operatorWriteDepth > 0
2106
- }) : context;
2379
+ if (secrets !== void 0) {
2380
+ const { context: wrapped, handle } = createSecretAwareContext(context, {
2381
+ secrets,
2382
+ addonTree: () => this.addonSecretTree(),
2383
+ deviceTree: () => this.deviceSecretTree(),
2384
+ onOrphaned: (refs) => context.logger.warn("keychain refs dropped by a settings write could not be deleted", { meta: { refs: [...refs] } }),
2385
+ isOperatorWrite: () => this._operatorWriteDepth > 0,
2386
+ onUnresolved: (unresolved) => this.logUnresolvedRead(unresolved)
2387
+ });
2388
+ this._ctx = wrapped;
2389
+ this._secretSettings = handle;
2390
+ } else {
2391
+ this._ctx = context;
2392
+ this._secretSettings = null;
2393
+ }
2107
2394
  await this.resolveConfig();
2108
2395
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
2109
2396
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -2220,11 +2507,8 @@ var BaseAddon = class {
2220
2507
  async getGlobalSettings(overlay, cap, nodeId) {
2221
2508
  const schema = this.globalSettingsSchema(cap);
2222
2509
  if (!schema) return { sections: [] };
2223
- const projected = await this.resolveGlobalStore(nodeId, cap);
2224
- return hydrateSchema(schema, overlay ? {
2225
- ...projected,
2226
- ...overlay
2227
- } : projected, { inlineSecretsAsViews: true });
2510
+ const projected = await this.readRawProjectedStore(nodeId, cap);
2511
+ return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
2228
2512
  }
2229
2513
  /**
2230
2514
  * The integration-level view of this addon's settings: exactly the sections
@@ -2269,7 +2553,7 @@ var BaseAddon = class {
2269
2553
  });
2270
2554
  }
2271
2555
  if (sections.length === 0) return null;
2272
- const projected = await this.resolveGlobalStore(nodeId);
2556
+ const projected = await this.readRawProjectedStore(nodeId);
2273
2557
  return hydrateSchema({
2274
2558
  ...schema,
2275
2559
  sections
@@ -2286,12 +2570,50 @@ var BaseAddon = class {
2286
2570
  * the store for custom option logic (option narrowing, value snapping) to
2287
2571
  * read it per-node — never `ctx.settings.readAddonStore()` directly.
2288
2572
  * `nodeId` omitted ⇒ the local node (node-blind injection from ctx).
2573
+ *
2574
+ * Keychain values come back RESOLVED (D798): a secret reads as its value,
2575
+ * never as a `{ $secret }` marker. One the keychain cannot serve right now
2576
+ * stays a marker — "stored, unavailable" (`isSecretMarker()` is true, and it
2577
+ * is listed in {@link unresolvedSecrets}) — never `''` and never absent, so
2578
+ * it cannot be mistaken for "nothing stored, mint a new one". Writing the
2579
+ * resolved blob back is a no-op for every unchanged secret.
2289
2580
  */
2290
2581
  async resolveGlobalStore(nodeId, cap) {
2291
2582
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
2583
+ return this.projectForNode(raw, nodeId, cap);
2584
+ }
2585
+ projectForNode(store, nodeId, cap) {
2292
2586
  const keys = this.perNodeKeys(cap);
2293
2587
  const node = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
2294
- return keys.size > 0 ? projectStore(raw, keys, node) : raw;
2588
+ return keys.size > 0 ? projectStore(store, keys, node) : store;
2589
+ }
2590
+ /** The PROJECTED store with its markers — the form hydrate only (D798). */
2591
+ async readRawProjectedStore(nodeId, cap) {
2592
+ const raw = await this.rawSettings()?.readAddonStore() ?? {};
2593
+ return this.projectForNode(raw, nodeId, cap);
2594
+ }
2595
+ /**
2596
+ * `{ ...stored, ...overlay }`, except that an overlay member equal to the
2597
+ * RESOLVED stored one keeps the stored (marker-bearing) member — so a form
2598
+ * hydrated from a resolved overlay still shows a secret as "set", never as
2599
+ * its value.
2600
+ */
2601
+ async overlayKeepingMarkers(stored, overlay) {
2602
+ const secrets = this._ctx?.secrets;
2603
+ const merged = {
2604
+ ...stored,
2605
+ ...overlay
2606
+ };
2607
+ if (secrets === void 0 || collectSecretMarkerRefs(stored).length === 0) return merged;
2608
+ const { value: resolved } = await resolveSecretMarkers(stored, secrets, {
2609
+ unresolved: "keep-marker",
2610
+ ownedBy: secrets.owner
2611
+ });
2612
+ for (const key of Object.keys(overlay)) {
2613
+ if (collectSecretMarkerRefs(stored[key]).length === 0) continue;
2614
+ if (isJsonEqual(overlay[key], resolved[key])) merged[key] = stored[key];
2615
+ }
2616
+ return merged;
2295
2617
  }
2296
2618
  /**
2297
2619
  * Whether ONE global key of this addon's store was written, read three-way
@@ -2356,8 +2678,12 @@ var BaseAddon = class {
2356
2678
  async clearSecrets(keys, nodeId) {
2357
2679
  const tree = this.addonSecretTree();
2358
2680
  const keychain = this._ctx?.secrets !== void 0;
2681
+ const stored = keychain ? await this.readAddonStoreWithRetry() : {};
2359
2682
  const patch = {};
2360
- for (const key of keys) patch[key] = keychain && tree.has(key) ? SECRET_CLEAR : "";
2683
+ for (const key of keys) {
2684
+ const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
2685
+ patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
2686
+ }
2361
2687
  await this.applyGlobalPatch(patch, nodeId);
2362
2688
  }
2363
2689
  async applyGlobalPatch(barePatch, nodeId) {
@@ -2365,7 +2691,11 @@ var BaseAddon = class {
2365
2691
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
2366
2692
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
2367
2693
  const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
2368
- await this._ctx?.settings?.writeAddonStore(scoped);
2694
+ const handle = this._secretSettings;
2695
+ if (handle !== null) {
2696
+ const { changed } = await handle.writeAddonStore(scoped);
2697
+ if (!changed) return;
2698
+ } else await this._ctx?.settings?.writeAddonStore(scoped);
2369
2699
  if (target !== localNode) return;
2370
2700
  await this.resolveConfig();
2371
2701
  await this.onConfigChanged();
@@ -2418,7 +2748,7 @@ var BaseAddon = class {
2418
2748
  async getDeviceSettings(deviceId) {
2419
2749
  const schema = this.deviceSettingsSchema();
2420
2750
  if (!schema) return { sections: [] };
2421
- return hydrateSchema(schema, await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
2751
+ return hydrateSchema(schema, await this.rawSettings()?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
2422
2752
  }
2423
2753
  async updateDeviceSettings(deviceId, patch) {
2424
2754
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
@@ -2590,7 +2920,7 @@ var BaseAddon = class {
2590
2920
  key,
2591
2921
  schema,
2592
2922
  fallback,
2593
- read: async () => this.resolveStoredSecrets(await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, false),
2923
+ read: async () => this.resolveStoredSecrets(await this.rawSettings()?.readDeviceStore(deviceId) ?? {}, false),
2594
2924
  write: async (patch) => {
2595
2925
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
2596
2926
  },
@@ -2652,6 +2982,23 @@ var BaseAddon = class {
2652
2982
  }
2653
2983
  return value;
2654
2984
  }
2985
+ /**
2986
+ * The marker-bearing settings reads (D798) — private on purpose. Without a
2987
+ * keychain the context's own view is already raw.
2988
+ */
2989
+ rawSettings() {
2990
+ return this._secretSettings?.raw ?? this._ctx?.settings;
2991
+ }
2992
+ /** An addon-facing read met markers it could not resolve: say so once per ref. */
2993
+ logUnresolvedRead(unresolved) {
2994
+ const fresh = unresolved.filter((u) => !this._loggedUnresolvedReads.has(u.ref));
2995
+ if (fresh.length === 0) return;
2996
+ for (const u of fresh) this._loggedUnresolvedReads.add(u.ref);
2997
+ this._ctx?.logger?.warn?.("settings read: a stored secret is unavailable — it reads as its marker, not as a value", { meta: { secrets: fresh.map((u) => ({
2998
+ ref: u.ref,
2999
+ reason: u.reason
3000
+ })) } });
3001
+ }
2655
3002
  /** Re-resolve the config (a keychain-only change, or a retry) and apply it. */
2656
3003
  scheduleSecretReresolve(delayMs) {
2657
3004
  const timerOf = delayMs === SECRET_CHANGE_DEBOUNCE_MS ? "_secretChangeTimer" : "_secretRetryTimer";
@@ -2678,7 +3025,7 @@ var BaseAddon = class {
2678
3025
  * `{}` (defaults) — the addon's first successful patch will rehydrate.
2679
3026
  */
2680
3027
  async readAddonStoreWithRetry() {
2681
- const settings = this._ctx?.settings;
3028
+ const settings = this.rawSettings();
2682
3029
  if (!settings) return {};
2683
3030
  const delaysMs = [
2684
3031
  150,
@@ -3796,6 +4143,28 @@ var adminUiCapability = {
3796
4143
  getVersion: method(zod.z.void(), VersionOutputSchema$1, { auth: "admin" })
3797
4144
  }
3798
4145
  };
4146
+ /**
4147
+ * Identity — preserves literal types for downstream inference.
4148
+ *
4149
+ * The constraint is `Record<string, unknown>` (not `CustomActionsSpec`) so
4150
+ * TypeScript does not widen each entry's literal `kind`/`auth` fields to
4151
+ * the broader unions declared on `CustomActionSpec`'s default generics.
4152
+ * Shape validity is enforced separately by the `customAction(...)` helper
4153
+ * whose return type is already a `CustomActionSpec<...>`.
4154
+ */
4155
+ function defineCustomActions(spec) {
4156
+ return spec;
4157
+ }
4158
+ function customAction(input, output, options) {
4159
+ return {
4160
+ input,
4161
+ output,
4162
+ kind: options?.kind ?? "query",
4163
+ auth: options?.auth ?? "protected",
4164
+ scope: options?.scope ?? { kind: "system" },
4165
+ ...options?.caller ? { caller: "required" } : {}
4166
+ };
4167
+ }
3799
4168
  var StaticDirOutputSchema = zod.z.object({ staticDir: zod.z.string() });
3800
4169
  var VersionOutputSchema = zod.z.object({ version: zod.z.string() });
3801
4170
  /**
@@ -6652,6 +7021,278 @@ var DERIVED_CAMERA_SILENCED_CAP_NAMES = [
6652
7021
  "scene-monitor"
6653
7022
  ];
6654
7023
  /**
7024
+ * The trainer contract — what the hub hands the cloud trainer and what it
7025
+ * gets back (cloud training, D805+).
7026
+ *
7027
+ * The trainer (`hf/camstack-trainer`, published as the Hugging Face repo
7028
+ * `camstack/camstack-trainer`) is a separate AGPL program the hub never
7029
+ * imports: `job.json` goes in, files come out, and progress is `CAMSTACK_EVENT`
7030
+ * lines on stdout. These schemas are the hub's half of that boundary; the
7031
+ * trainer's pydantic models (and the JSON Schemas generated from them under
7032
+ * `hf/camstack-trainer/schemas/`) are the other half. A change here is a
7033
+ * change there, in the same piece of work.
7034
+ *
7035
+ * No `.default()` anywhere: defaults do not run on the addon cap path, so the
7036
+ * orchestrator fills every field of a job spec explicitly.
7037
+ */
7038
+ var TRAINING_JOB_SCHEMA = "camstack.training/v1";
7039
+ var TRAINING_RESULT_SCHEMA = "camstack.training-result/v1";
7040
+ /** What the trainer can export. Edge TPU is a later, separate image. */
7041
+ var TrainingExportSchema = zod.z.enum([
7042
+ "onnx",
7043
+ "openvino-fp16",
7044
+ "openvino-int8",
7045
+ "coreml-fp16"
7046
+ ]);
7047
+ var FloorsSchema = zod.z.record(zod.z.string(), zod.z.number().min(0).max(1));
7048
+ zod.z.object({
7049
+ schema: zod.z.literal(TRAINING_JOB_SCHEMA),
7050
+ jobKey: zod.z.string().uuid(),
7051
+ target: zod.z.object({
7052
+ kind: zod.z.literal("detector"),
7053
+ step: zod.z.literal("object-detection")
7054
+ }),
7055
+ base: zod.z.object({
7056
+ modelId: zod.z.string().min(1),
7057
+ /** Ultralytics weights id, e.g. `yolov9m.pt`. */
7058
+ weights: zod.z.string().min(1),
7059
+ imgsz: zod.z.number().int().positive()
7060
+ }),
7061
+ baseline: zod.z.object({
7062
+ modelId: zod.z.string().min(1),
7063
+ /** The baseline ONNX the holdout comparison runs; public mirror URL. */
7064
+ onnxUrl: zod.z.string().url(),
7065
+ imgsz: zod.z.number().int().positive(),
7066
+ floors: FloorsSchema
7067
+ }),
7068
+ dataset: zod.z.object({
7069
+ versionId: zod.z.string().min(1),
7070
+ annotationHash: zod.z.string(),
7071
+ /** sha256 of the uploaded archive; the trainer refuses a mismatch (exit 65). */
7072
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
7073
+ }),
7074
+ split: zod.z.object({
7075
+ holdoutCameras: zod.z.array(zod.z.number().int()),
7076
+ /** When no camera is held out: the last N days of every camera. */
7077
+ holdoutLastDays: zod.z.number().int().nonnegative(),
7078
+ valFraction: zod.z.number().min(0).max(.5),
7079
+ seed: zod.z.number().int()
7080
+ }),
7081
+ hyper: zod.z.object({
7082
+ epochs: zod.z.number().int().positive(),
7083
+ batch: zod.z.number().int().positive(),
7084
+ lr0: zod.z.number().positive(),
7085
+ /** Layers frozen from the start of the backbone; 0 = none. */
7086
+ freeze: zod.z.number().int().nonnegative(),
7087
+ patience: zod.z.number().int().nonnegative(),
7088
+ seed: zod.z.number().int()
7089
+ }),
7090
+ replay: zod.z.object({
7091
+ source: zod.z.literal("coco2017"),
7092
+ count: zod.z.number().int().nonnegative()
7093
+ }),
7094
+ exports: zod.z.array(TrainingExportSchema).min(1),
7095
+ calibration: zod.z.object({ maxFrames: zod.z.number().int().positive() }),
7096
+ floors: zod.z.object({
7097
+ minSupport: zod.z.number().int().positive(),
7098
+ clamp: zod.z.number().min(0).max(1)
7099
+ }),
7100
+ int8Gate: zod.z.object({ maxMap50Drop: zod.z.number().min(0).max(1) }),
7101
+ catalog: zod.z.object({
7102
+ id: zod.z.string().regex(/^[a-z0-9][a-z0-9-]{2,80}$/),
7103
+ name: zod.z.string().min(1).max(160)
7104
+ })
7105
+ });
7106
+ /** One regular file of the bundle. An mlpackage contributes one row per member. */
7107
+ var TrainingResultFileSchema = zod.z.object({
7108
+ path: zod.z.string().min(1),
7109
+ bytes: zod.z.number().int().nonnegative(),
7110
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/),
7111
+ format: zod.z.enum([
7112
+ "onnx",
7113
+ "openvino",
7114
+ "coreml",
7115
+ "pt"
7116
+ ]),
7117
+ variant: zod.z.enum(["base", "int8"])
7118
+ });
7119
+ var DraftFormatSchema = zod.z.object({
7120
+ /** RELATIVE path inside the bundle; the hub rewrites it to `camstack-local://`. */
7121
+ url: zod.z.string().min(1),
7122
+ sizeMB: zod.z.number().nonnegative(),
7123
+ isDirectory: zod.z.boolean().optional(),
7124
+ files: zod.z.array(zod.z.string()).optional(),
7125
+ runtimes: zod.z.array(zod.z.literal("python")).optional(),
7126
+ revision: zod.z.string().min(1)
7127
+ });
7128
+ /** The trainer's half of a catalog entry; the hub completes it (labels, licence, group). */
7129
+ var TrainingCatalogDraftSchema = zod.z.object({
7130
+ id: zod.z.string().min(1),
7131
+ name: zod.z.string().min(1),
7132
+ description: zod.z.string(),
7133
+ labels: zod.z.array(zod.z.unknown()),
7134
+ inputSize: zod.z.object({
7135
+ width: zod.z.number().int().positive(),
7136
+ height: zod.z.number().int().positive()
7137
+ }),
7138
+ preprocessMode: zod.z.literal("letterbox"),
7139
+ defaultClassFloors: FloorsSchema,
7140
+ formats: zod.z.object({
7141
+ onnx: DraftFormatSchema.optional(),
7142
+ openvino: DraftFormatSchema.optional(),
7143
+ coreml: DraftFormatSchema.optional()
7144
+ })
7145
+ });
7146
+ /** One class of one model's holdout report (the eval harness's summary). */
7147
+ var TrainingEvalClassSchema = zod.z.object({
7148
+ positives: zod.z.number().int(),
7149
+ floor: zod.z.number().nullable(),
7150
+ tp: zod.z.number().int(),
7151
+ fp: zod.z.number().int(),
7152
+ fn: zod.z.number().int(),
7153
+ precision: zod.z.number().nullable(),
7154
+ recall: zod.z.number().nullable(),
7155
+ ap50: zod.z.number().nullable(),
7156
+ recallByCamera: zod.z.record(zod.z.string(), zod.z.number().nullable()).optional(),
7157
+ recallByLight: zod.z.record(zod.z.string(), zod.z.number().nullable()).optional(),
7158
+ fpByCamera: zod.z.record(zod.z.string(), zod.z.number()).optional(),
7159
+ smallFound: zod.z.number().int().optional(),
7160
+ smallTotal: zod.z.number().int().optional()
7161
+ }).passthrough();
7162
+ var TrainingEvalReportSchema = zod.z.object({
7163
+ frames: zod.z.number().int(),
7164
+ classes: zod.z.record(zod.z.string(), TrainingEvalClassSchema)
7165
+ }).passthrough();
7166
+ /**
7167
+ * A succeeded result carries every block; a failed one carries the error and
7168
+ * whatever was known by then — the other blocks are ABSENT, not null. The
7169
+ * orchestrator checks the blocks it needs before it trusts a success.
7170
+ */
7171
+ var TrainingResultSchema = zod.z.object({
7172
+ schema: zod.z.literal(TRAINING_RESULT_SCHEMA),
7173
+ jobKey: zod.z.string(),
7174
+ status: zod.z.enum(["succeeded", "failed"]),
7175
+ error: zod.z.object({
7176
+ stage: zod.z.string(),
7177
+ code: zod.z.string(),
7178
+ message: zod.z.string()
7179
+ }).nullable(),
7180
+ trainer: zod.z.object({
7181
+ version: zod.z.string(),
7182
+ revision: zod.z.string()
7183
+ }),
7184
+ toolchain: zod.z.record(zod.z.string(), zod.z.string().nullable()),
7185
+ dataset: zod.z.object({
7186
+ versionId: zod.z.string(),
7187
+ annotationHash: zod.z.string(),
7188
+ sha256: zod.z.string(),
7189
+ frames: zod.z.object({
7190
+ train: zod.z.number().int(),
7191
+ val: zod.z.number().int(),
7192
+ holdout: zod.z.number().int()
7193
+ }),
7194
+ holdoutCameras: zod.z.array(zod.z.number().int()),
7195
+ replayImages: zod.z.number().int()
7196
+ }).optional(),
7197
+ training: zod.z.object({
7198
+ epochsRun: zod.z.number().int(),
7199
+ bestEpoch: zod.z.number().int(),
7200
+ bestMap50: zod.z.number(),
7201
+ bestMap50_95: zod.z.number(),
7202
+ seconds: zod.z.number()
7203
+ }).optional(),
7204
+ evaluation: zod.z.object({
7205
+ holdoutFrames: zod.z.number().int(),
7206
+ baseline: TrainingEvalReportSchema.nullable(),
7207
+ candidate: TrainingEvalReportSchema.nullable(),
7208
+ candidateInt8: TrainingEvalReportSchema.nullable(),
7209
+ recallDeltaByCamera: zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.number())).nullable(),
7210
+ map50: zod.z.record(zod.z.string(), zod.z.number().nullable())
7211
+ }).optional(),
7212
+ floors: zod.z.object({
7213
+ baseline: FloorsSchema,
7214
+ candidate: FloorsSchema,
7215
+ candidateInt8: FloorsSchema.nullable(),
7216
+ notes: zod.z.array(zod.z.string())
7217
+ }).optional(),
7218
+ int8: zod.z.object({
7219
+ kept: zod.z.boolean(),
7220
+ map50Fp16: zod.z.number().nullable(),
7221
+ map50Int8: zod.z.number().nullable(),
7222
+ reason: zod.z.string().nullable()
7223
+ }).optional(),
7224
+ exportsDropped: zod.z.array(zod.z.object({
7225
+ export: zod.z.string(),
7226
+ reason: zod.z.string()
7227
+ })).optional(),
7228
+ files: zod.z.array(TrainingResultFileSchema).optional(),
7229
+ bundle: zod.z.object({
7230
+ path: zod.z.literal("bundle.tar"),
7231
+ bytes: zod.z.number().int().nonnegative(),
7232
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
7233
+ }).nullable().optional(),
7234
+ catalogDraft: zod.z.object({
7235
+ base: TrainingCatalogDraftSchema,
7236
+ int8: TrainingCatalogDraftSchema.nullable()
7237
+ }).nullable().optional(),
7238
+ licence: zod.z.string()
7239
+ });
7240
+ var TrainingStageSchema = zod.z.enum([
7241
+ "prepare",
7242
+ "replay",
7243
+ "train",
7244
+ "eval",
7245
+ "export",
7246
+ "package"
7247
+ ]);
7248
+ var EventBase = {
7249
+ v: zod.z.literal(1),
7250
+ ts: zod.z.number()
7251
+ };
7252
+ zod.z.discriminatedUnion("type", [
7253
+ zod.z.object({
7254
+ ...EventBase,
7255
+ type: zod.z.literal("stage"),
7256
+ stage: TrainingStageSchema
7257
+ }),
7258
+ zod.z.object({
7259
+ ...EventBase,
7260
+ type: zod.z.literal("epoch"),
7261
+ epoch: zod.z.number().int(),
7262
+ epochs: zod.z.number().int(),
7263
+ boxLoss: zod.z.number().nullable().optional(),
7264
+ map50: zod.z.number().nullable().optional(),
7265
+ map50_95: zod.z.number().nullable().optional(),
7266
+ etaSec: zod.z.number().nullable().optional()
7267
+ }),
7268
+ zod.z.object({
7269
+ ...EventBase,
7270
+ type: zod.z.literal("metric"),
7271
+ name: zod.z.string(),
7272
+ value: zod.z.number()
7273
+ }),
7274
+ zod.z.object({
7275
+ ...EventBase,
7276
+ type: zod.z.literal("artifact"),
7277
+ path: zod.z.string(),
7278
+ bytes: zod.z.number(),
7279
+ sha256: zod.z.string()
7280
+ }),
7281
+ zod.z.object({
7282
+ ...EventBase,
7283
+ type: zod.z.literal("done"),
7284
+ status: zod.z.enum(["succeeded", "failed"]),
7285
+ resultSha256: zod.z.string().nullable().optional()
7286
+ }),
7287
+ zod.z.object({
7288
+ ...EventBase,
7289
+ type: zod.z.literal("error"),
7290
+ stage: zod.z.string(),
7291
+ code: zod.z.string(),
7292
+ message: zod.z.string()
7293
+ })
7294
+ ]);
7295
+ /**
6655
7296
  * What the gate decided about a birth, from the CALLER's point of view.
6656
7297
  *
6657
7298
  * Deliberately not `ConfirmationVerdict`: `undecided` is not a birth decision
@@ -9408,7 +10049,9 @@ var AddonHttpRouteSchema = zod.z.object({
9408
10049
  "authenticated",
9409
10050
  "admin"
9410
10051
  ]).optional(),
9411
- description: zod.z.string().optional()
10052
+ description: zod.z.string().optional(),
10053
+ /** The hub limits this route per client address before the handler runs. */
10054
+ rateLimit: zod.z.enum(["auth"]).optional()
9412
10055
  });
9413
10056
  /**
9414
10057
  * Cross-process route invocation envelope. The hub captures the
@@ -10372,6 +11015,37 @@ var BackupDestinationInfoSchema = zod.z.object({
10372
11015
  lastError: zod.z.string().optional()
10373
11016
  });
10374
11017
  /**
11018
+ * How an archive is encrypted (operator decision 2026-10-09): to a backup
11019
+ * PUBLIC key the hub holds, so only the operator's secret key opens it.
11020
+ * Absent on a plain `.tar.gz` — every archive made before encryption, or
11021
+ * with it off — which restores without a key.
11022
+ */
11023
+ var BackupArchiveEncryptionSchema = zod.z.object({
11024
+ /** `x25519-hkdf-aes256gcm-stream-v1`. */
11025
+ scheme: zod.z.string(),
11026
+ /** 16 hex — the backup key whose secret restores this archive. */
11027
+ keyId: zod.z.string()
11028
+ });
11029
+ /**
11030
+ * The backup encryption setting. The hub keeps PUBLIC keys only: the active
11031
+ * one new archives are encrypted to, and the retired ones older archives
11032
+ * still need. The secret key is shown once, at `setupEncryption`.
11033
+ */
11034
+ var BackupEncryptionStatusSchema = zod.z.object({
11035
+ enabled: zod.z.boolean(),
11036
+ active: zod.z.object({
11037
+ keyId: zod.z.string(),
11038
+ publicKey: zod.z.string(),
11039
+ createdAt: zod.z.number()
11040
+ }).nullable(),
11041
+ /** Every key ever set up, newest first. */
11042
+ keys: zod.z.array(zod.z.object({
11043
+ keyId: zod.z.string(),
11044
+ createdAt: zod.z.number(),
11045
+ retiredAt: zod.z.number().nullable()
11046
+ })).readonly()
11047
+ });
11048
+ /**
10375
11049
  * Per-archive entry returned by `backup.listArchives({ destinationId })`.
10376
11050
  * Same shape the destination drill-in renders. Sourced from the per-
10377
11051
  * location `manifests.json` (no per-archive tarball read required).
@@ -10383,7 +11057,9 @@ var BackupArchiveEntrySchema = zod.z.object({
10383
11057
  sizeBytes: zod.z.number(),
10384
11058
  label: zod.z.string().optional(),
10385
11059
  /** Top-level locations included in the archive (db, addons, tls, …). */
10386
- locations: zod.z.array(zod.z.string()).readonly()
11060
+ locations: zod.z.array(zod.z.string()).readonly(),
11061
+ /** Present when the archive is encrypted; restore then needs that key. */
11062
+ encryption: BackupArchiveEncryptionSchema.optional()
10387
11063
  });
10388
11064
  var BackupEntrySchema = zod.z.object({
10389
11065
  id: zod.z.string(),
@@ -10392,7 +11068,8 @@ var BackupEntrySchema = zod.z.object({
10392
11068
  label: zod.z.string().optional(),
10393
11069
  createdAt: zod.z.number(),
10394
11070
  sizeBytes: zod.z.number(),
10395
- locations: zod.z.array(zod.z.string()).optional()
11071
+ locations: zod.z.array(zod.z.string()).optional(),
11072
+ encryption: BackupArchiveEncryptionSchema.optional()
10396
11073
  });
10397
11074
  var ArchiveEntrySchema = zod.z.object({
10398
11075
  path: zod.z.string(),
@@ -10647,11 +11324,58 @@ var backupCapability = {
10647
11324
  * this list and skips entries whose path doesn't start with
10648
11325
  * any of them.
10649
11326
  */
10650
- locations: zod.z.array(zod.z.string()).optional()
11327
+ locations: zod.z.array(zod.z.string()).optional(),
11328
+ /**
11329
+ * The backup SECRET key, for an encrypted archive (ignored for a plain
11330
+ * one). The archive is decrypted into the hub's private staging
11331
+ * before the restart; the key is not stored. A wrong or missing key
11332
+ * is refused before the archive is downloaded.
11333
+ */
11334
+ backupSecretKey: zod.z.string().optional()
10651
11335
  }), zod.z.void(), {
10652
11336
  kind: "mutation",
10653
11337
  auth: "admin"
10654
11338
  }),
11339
+ /** The encryption setting — public keys only. */
11340
+ getEncryption: method(zod.z.void(), BackupEncryptionStatusSchema, { auth: "admin" }),
11341
+ /**
11342
+ * Turn encryption on, or replace the key: generates a new key pair, keeps
11343
+ * the PUBLIC key, and returns the SECRET key — once. It is never stored
11344
+ * on the hub and cannot be shown again; archives made from now on need
11345
+ * it to be restored. A previous key is retired: archives made with it
11346
+ * still need IT.
11347
+ */
11348
+ setupEncryption: method(zod.z.object({}), zod.z.object({
11349
+ keyId: zod.z.string(),
11350
+ secretKey: zod.z.string(),
11351
+ publicKey: zod.z.string(),
11352
+ createdAt: zod.z.number(),
11353
+ replacedKeyId: zod.z.string().nullable()
11354
+ }), {
11355
+ kind: "mutation",
11356
+ auth: "admin"
11357
+ }),
11358
+ /** New archives are plain again. Existing encrypted archives keep needing their key. */
11359
+ disableEncryption: method(zod.z.object({}), zod.z.object({ disabledKeyId: zod.z.string().nullable() }), {
11360
+ kind: "mutation",
11361
+ auth: "admin"
11362
+ }),
11363
+ /**
11364
+ * Does this secret key parse, and which backup key is it? A mutation so
11365
+ * the key never travels in a URL. Nothing is stored.
11366
+ */
11367
+ checkBackupKey: method(zod.z.object({ backupSecretKey: zod.z.string() }), zod.z.object({
11368
+ valid: zod.z.boolean(),
11369
+ keyId: zod.z.string().nullable(),
11370
+ /** The hub knows this key (active or retired). */
11371
+ known: zod.z.boolean(),
11372
+ /** It is the key new archives are encrypted to. */
11373
+ active: zod.z.boolean(),
11374
+ error: zod.z.string().optional()
11375
+ }), {
11376
+ kind: "mutation",
11377
+ auth: "admin"
11378
+ }),
10655
11379
  delete: method(zod.z.object({
10656
11380
  destinationId: zod.z.string(),
10657
11381
  backupId: zod.z.string()
@@ -14162,22 +14886,87 @@ var CustomModelDescriptorSchema = zod.z.object({
14162
14886
  stepId: zod.z.string(),
14163
14887
  entry: ModelCatalogEntrySchema
14164
14888
  });
14889
+ /** One regular file of an imported bundle, as the producer measured it. */
14890
+ var ModelBundleFileSchema = zod.z.object({
14891
+ /** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
14892
+ path: zod.z.string().min(1),
14893
+ bytes: zod.z.number().int().nonnegative(),
14894
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
14895
+ });
14896
+ var ImportModelBundleInputSchema = zod.z.object({
14897
+ /** The registry provider that keeps the model (Model Studio). */
14898
+ addonId: zod.z.string(),
14899
+ stepId: zod.z.string().min(1),
14900
+ /**
14901
+ * The catalog rows to register, complete, every format URL already
14902
+ * `camstack-local://<modelId>/<file>` naming a file of the bundle. One
14903
+ * bundle may carry several rows (a base and its INT8 variant).
14904
+ */
14905
+ entries: zod.z.array(ModelCatalogEntrySchema).min(1).max(4),
14906
+ /** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
14907
+ bundle: zod.z.object({
14908
+ ticket: PeerBytesTicketSchema,
14909
+ bytes: zod.z.number().int().positive(),
14910
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
14911
+ }),
14912
+ /** Every file the tar must hold; anything missing, extra or different is refused. */
14913
+ files: zod.z.array(ModelBundleFileSchema).min(1).max(64),
14914
+ /** Convert missing engine formats and push the model to every agent afterwards. */
14915
+ distribute: zod.z.boolean()
14916
+ });
14917
+ var ImportModelBundleResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
14918
+ kind: zod.z.literal("imported"),
14919
+ modelIds: zod.z.array(zod.z.string()).readonly(),
14920
+ distributed: zod.z.array(zod.z.object({
14921
+ modelId: zod.z.string(),
14922
+ nodeId: zod.z.string(),
14923
+ format: zod.z.enum(MODEL_FORMATS),
14924
+ ok: zod.z.boolean(),
14925
+ error: zod.z.string().optional()
14926
+ })).readonly()
14927
+ }), zod.z.object({
14928
+ kind: zod.z.literal("refused"),
14929
+ reason: zod.z.enum([
14930
+ "transfer-failed",
14931
+ "checksum-mismatch",
14932
+ "bundle-mismatch",
14933
+ "invalid-entry",
14934
+ "id-taken"
14935
+ ]),
14936
+ detail: zod.z.string()
14937
+ })]);
14165
14938
  var customModelRegistryCapability = {
14166
14939
  name: "custom-model-registry",
14167
14940
  scope: "system",
14168
14941
  mode: "collection",
14169
14942
  internal: true,
14170
- methods: {
14171
- /**
14172
- * `internal: true` did not gate the mount (see the 2026-08-26 note on
14173
- * `data-store-provider`) — this was reachable on the AppRouter by ANY
14174
- * authenticated session at the default `auth: 'protected'`. The only
14175
- * consumer, the detection-pipeline's model resolution
14176
- * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
14177
- * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
14178
- * never tRPC — so `auth: 'admin'` costs it nothing.
14179
- */
14180
- listModels: method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }) }
14943
+ methods: {
14944
+ /**
14945
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
14946
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
14947
+ * authenticated session at the default `auth: 'protected'`. The only
14948
+ * consumer, the detection-pipeline's model resolution
14949
+ * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
14950
+ * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
14951
+ * never tRPC — so `auth: 'admin'` costs it nothing.
14952
+ */
14953
+ listModels: method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }),
14954
+ /**
14955
+ * Register a model whose files arrive as ONE bundle tar (D806): the tar
14956
+ * is taken over `ctx.peerBytes`, checked against `bundle.sha256` and
14957
+ * every row of `files`, unpacked into the hub's flat modelsDir (directory
14958
+ * formats included), each format's `.revision` record written, the rows
14959
+ * upserted under the operator's name, the hub marked available and —
14960
+ * when asked — engine formats converted and the model distributed. A
14961
+ * model id already registered by another source is refused, never
14962
+ * overwritten.
14963
+ */
14964
+ importModelBundle: method(ImportModelBundleInputSchema, ImportModelBundleResultSchema, {
14965
+ kind: "mutation",
14966
+ auth: "admin",
14967
+ timeoutMs: 30 * 6e4
14968
+ })
14969
+ }
14181
14970
  };
14182
14971
  /**
14183
14972
  * Query filter for settings-store collections.
@@ -14984,6 +15773,42 @@ var KeychainKeySourceSchema = zod.z.object({
14984
15773
  */
14985
15774
  sharesBackupWithDatabase: zod.z.boolean()
14986
15775
  });
15776
+ /**
15777
+ * Who reset the keychain: an operator from System → Secrets (`user:<id>`), or
15778
+ * the one-shot `<dataDir>/.keychain/RESET_KEYCHAIN` trigger file at boot
15779
+ * (`trigger-file`) — the only way back into a hub whose master key is lost,
15780
+ * because a locked keychain leaves no session that could reach the page (D797).
15781
+ */
15782
+ var KEYCHAIN_RESET_TRIGGER_ACTOR = "trigger-file";
15783
+ /**
15784
+ * One keychain reset, as the Secrets page reports it until the operator
15785
+ * acknowledges it. Never a value: refs, counts and table names only.
15786
+ */
15787
+ var KeychainResetRecordSchema = zod.z.object({
15788
+ id: zod.z.string(),
15789
+ at: zod.z.number(),
15790
+ /** `user:<id>` or {@link KEYCHAIN_RESET_TRIGGER_ACTOR}. */
15791
+ actor: zod.z.string(),
15792
+ /** Why the keychain was locked when it was reset; `null` when it was not locked. */
15793
+ lockedReason: zod.z.string().nullable(),
15794
+ /** Values moved into the archive table (ciphertext under the lost key). */
15795
+ archived: zod.z.number().int(),
15796
+ /** `keychain:secrets-orphaned-<ts>`, or `null` when nothing was stored. */
15797
+ archivedCollection: zod.z.string().nullable(),
15798
+ /**
15799
+ * `keychain:keys-orphaned-<ts>`: the wrapped data keys of the archive, kept
15800
+ * so the archive can still be opened if the lost master key turns up.
15801
+ */
15802
+ archivedKeysCollection: zod.z.string().nullable(),
15803
+ /**
15804
+ * `system-settings` keys whose marker the reset cleared — the cluster
15805
+ * secret's: a marker left pointing at an archived value would refuse every
15806
+ * agent forever (D797).
15807
+ */
15808
+ clearedSettingsKeys: zod.z.array(zod.z.string()).readonly(),
15809
+ /** When an operator dismissed the notice on the Secrets page; `null` until then. */
15810
+ acknowledgedAt: zod.z.number().nullable()
15811
+ });
14987
15812
  var KeychainStatusSchema = zod.z.object({
14988
15813
  keychain: KeychainStateSchema,
14989
15814
  /** The source of the master key in use; `null` while locked. */
@@ -15001,7 +15826,9 @@ var KeychainStatusSchema = zod.z.object({
15001
15826
  /** Wrapped copies of the data key under a previous master key, kept until purged. */
15002
15827
  retiredWraps: zod.z.number().int(),
15003
15828
  recoveryKeyDownloadedAt: zod.z.number().nullable(),
15004
- secretCount: zod.z.number().int()
15829
+ secretCount: zod.z.number().int(),
15830
+ /** The most recent reset, acknowledged or not; `null` when the keychain was never reset. */
15831
+ lastReset: KeychainResetRecordSchema.nullable()
15005
15832
  });
15006
15833
  /**
15007
15834
  * Whether a stored value is still pointed at by its owner's settings.
@@ -15023,13 +15850,14 @@ var KeychainAuditActionSchema = zod.z.enum([
15023
15850
  "download-recovery-key",
15024
15851
  "delete-orphans",
15025
15852
  "reset",
15853
+ "acknowledge-reset",
15026
15854
  "initialize"
15027
15855
  ]);
15028
15856
  var KeychainAuditEntrySchema = zod.z.object({
15029
15857
  id: zod.z.string(),
15030
15858
  at: zod.z.number(),
15031
15859
  action: KeychainAuditActionSchema,
15032
- /** `addon:<id>` | `user:<id>` | `system`. */
15860
+ /** `addon:<id>` | `user:<id>` | `system` | `trigger-file`. */
15033
15861
  actor: zod.z.string(),
15034
15862
  ref: zod.z.string().nullable(),
15035
15863
  detail: zod.z.string().nullable()
@@ -15139,6 +15967,21 @@ var keychainAdminCapability = {
15139
15967
  kind: "mutation",
15140
15968
  auth: "admin",
15141
15969
  caller: "required"
15970
+ }),
15971
+ /**
15972
+ * What the last reset left to re-enter: the archived refs (owner, scope,
15973
+ * field — never a value) that hold no value again yet. Process-held refs
15974
+ * the hub regenerates itself (the session signing key) are not listed.
15975
+ */
15976
+ resetReport: method(zod.z.object({}), zod.z.object({
15977
+ reset: KeychainResetRecordSchema.nullable(),
15978
+ pendingReentry: zod.z.array(KeychainSecretMetaSchema).readonly()
15979
+ }), { auth: "admin" }),
15980
+ /** Dismiss the "keychain was reset" notice. Audited. */
15981
+ acknowledgeReset: method(zod.z.object({}), zod.z.object({ acknowledged: zod.z.boolean() }), {
15982
+ kind: "mutation",
15983
+ auth: "admin",
15984
+ caller: "required"
15142
15985
  })
15143
15986
  }
15144
15987
  };
@@ -18395,8 +19238,8 @@ var PUBLIC_ENDPOINT_HTTP_REASON = "The public endpoint needs an https:// address
18395
19238
  * May `baseUrl` carry `role`? The address check above, plus two role rules:
18396
19239
  * the app link carries `notificationTaps` only (it is not a hub address), and
18397
19240
  * a plain-http address carries `publicEndpoint` only when the operator
18398
- * explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password or
18399
- * magic-link sign-in works over http, a passkey or an OIDC callback does not.
19241
+ * explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password
19242
+ * sign-in works over http, a passkey or an OIDC callback does not.
18400
19243
  */
18401
19244
  function checkRoleAddress(role, baseUrl, options = {}) {
18402
19245
  if (isAppLinkBase(baseUrl)) return role === "notificationTaps" ? {
@@ -19426,7 +20269,7 @@ var logDestinationCapability = {
19426
20269
  * `login-method` — collection cap through which auth addons contribute
19427
20270
  * their pre-auth login surfaces to the login page. This is the SINGLE,
19428
20271
  * generic mechanism that supersedes the dead `auth.listProviders` reader:
19429
- * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
20272
+ * every auth addon (OIDC, WebAuthn/passkey) registers a
19430
20273
  * `login-method` provider and the PUBLIC `auth.listLoginMethods`
19431
20274
  * procedure aggregates them for the unauthenticated login page.
19432
20275
  *
@@ -19434,8 +20277,7 @@ var logDestinationCapability = {
19434
20277
  *
19435
20278
  * - `redirect` — a declarative button. The login page renders a generic
19436
20279
  * button that navigates to `startUrl` (an addon-owned HTTP route).
19437
- * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
19438
- * ZERO shell-side JS. A future SSO addon plugs in the same way — the
20280
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) with ZERO shell-side JS. A future SSO addon plugs in the same way — the
19439
20281
  * login page needs NO change.
19440
20282
  *
19441
20283
  * - `widget` — a Module-Federation widget the login page mounts (via
@@ -19454,8 +20296,8 @@ var logDestinationCapability = {
19454
20296
  * decision.
19455
20297
  *
19456
20298
  * Every contribution carries a `stage`:
19457
- * - `primary` — shown on the first credentials screen (OIDC /
19458
- * magic-link buttons; a future usernameless passkey).
20299
+ * - `primary` — shown on the first credentials screen (OIDC
20300
+ * buttons; a usernameless passkey).
19459
20301
  * - `second-factor` — shown AFTER the password leg, gated on the
19460
20302
  * returned `factors` (passkey-as-2FA today).
19461
20303
  *
@@ -19467,7 +20309,7 @@ var logDestinationCapability = {
19467
20309
  var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
19468
20310
  /**
19469
20311
  * A declarative redirect button — the login page navigates to `startUrl`.
19470
- * OIDC and magic-link contribute this; a future SSO addon does too.
20312
+ * OIDC contributes this; a future SSO addon does too.
19471
20313
  */
19472
20314
  var RedirectLoginMethodSchema = zod.z.object({
19473
20315
  kind: zod.z.literal("redirect"),
@@ -19484,7 +20326,7 @@ var RedirectLoginMethodSchema = zod.z.object({
19484
20326
  * The provider names an external identity a signed-in user can link to
19485
20327
  * their own account (D782): `auth.listLinkableProviders` /
19486
20328
  * `beginExternalLink` offer exactly the redirect methods that set this,
19487
- * whatever their id. Absent / false — a login button only (magic-link).
20329
+ * whatever their id. Absent / false — a login button only.
19488
20330
  */
19489
20331
  linkable: zod.z.boolean().optional()
19490
20332
  });
@@ -23755,8 +24597,7 @@ var notificationRulesCapability = {
23755
24597
  * `super_admin` — collapsed onto the single boolean because every
23756
24598
  * non-admin caller is governed by the same scope-check rules; the
23757
24599
  * distinction was only ever used to route role-specific procedures
23758
- * which we no longer have. Agents are admin sessions issued via
23759
- * `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
24600
+ * which we no longer have. CLI / webhook integrations are `cst_*` tokens
23760
24601
  * with `isAdmin: false` + an explicit scope list.
23761
24602
  */
23762
24603
  /** Per-method access flavour. Materialised on every cap method via codegen. */
@@ -27037,6 +27878,26 @@ var pipelineAnalyticsCapability = {
27037
27878
  auth: "admin",
27038
27879
  timeoutMs: 60 * 6e4
27039
27880
  }),
27881
+ /**
27882
+ * OFFER the annotated archive instead of pushing it (D805): the same tar
27883
+ * `writeRetrainArchive` streams, behind a one-shot `ctx.peerBytes` ticket
27884
+ * the caller dials at once (same host). For a consumer that cannot expose
27885
+ * an upload URL — a training provider streaming straight into its cloud.
27886
+ * The answer is the plan; the bytes and their sha256 are the consumer's
27887
+ * to measure as they arrive.
27888
+ */
27889
+ offerRetrainArchive: method(zod.z.object({
27890
+ filter: RetrainExportFilterSchema,
27891
+ layout: RetrainExportLayoutSchema
27892
+ }), zod.z.object({
27893
+ ticket: PeerBytesTicketSchema,
27894
+ frames: zod.z.number().int(),
27895
+ annotations: zod.z.number().int()
27896
+ }), {
27897
+ kind: "mutation",
27898
+ auth: "admin",
27899
+ timeoutMs: 12e4
27900
+ }),
27040
27901
  /** The counts a selection would freeze — class × camera × day/night,
27041
27902
  * completeness — without freezing anything. Index reads only. */
27042
27903
  previewRetrainDataset: method(zod.z.object({
@@ -31783,8 +32644,8 @@ version: zod.z.string().optional() }), ServerUpdateActionResultSchema, {
31783
32644
  * `smtp-provider` — pluggable email delivery surface.
31784
32645
  *
31785
32646
  * Collection cap: a deployment may install multiple SMTP relays (e.g.
31786
- * one for transactional / one for notifications). Consumers (magic-link,
31787
- * notifier addons, …) walk the collection and pick the first
32647
+ * one for transactional / one for notifications). Consumers (notifier
32648
+ * addons, …) walk the collection and pick the first
31788
32649
  * ready provider OR target a specific addonId.
31789
32650
  *
31790
32651
  * The shape stays small on purpose — `sendEmail` is enough for the
@@ -32080,7 +32941,7 @@ targets: zod.z.array(zod.z.object({
32080
32941
  };
32081
32942
  /**
32082
32943
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
32083
- * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
32944
+ * providers (OIDC, SAML, …) mint an HMAC-signed token
32084
32945
  * carrying the post-callback claims before redirecting back to
32085
32946
  * `/api/auth/sso/finish`.
32086
32947
  *
@@ -32143,7 +33004,14 @@ var SsoBridgeClaimsSchema = zod.z.object({
32143
33004
  * Verified `iss` of the id_token behind an OIDC login bridge (D782). A
32144
33005
  * subject is unique only per issuer, so a link is bound to it.
32145
33006
  */
32146
- issuer: zod.z.string().optional()
33007
+ issuer: zod.z.string().optional(),
33008
+ /**
33009
+ * SHA-256 (hex) of the `camstack_sso_state` cookie the IdP `/start` set in
33010
+ * the browser that began this sign-in. `/api/auth/sso/finish` requires the
33011
+ * request to present the matching cookie — a login bridge is useless in
33012
+ * any other browser.
33013
+ */
33014
+ browserBinding: zod.z.string().optional()
32147
33015
  });
32148
33016
  var ssoBridgeCapability = {
32149
33017
  name: "sso-bridge",
@@ -32161,7 +33029,7 @@ var ssoBridgeCapability = {
32161
33029
  * `/api/auth/sso/finish` to become admin — a full privilege escalation
32162
33030
  * through the exact surface this cap exists to close (see the module
32163
33031
  * docblock). Every real caller (`oauth-grants.ts`, `main.ts`
32164
- * registration, the OIDC/magic-link addons) reaches this through
33032
+ * registration, the OIDC addon) reaches this through
32165
33033
  * `ctx.api` / the in-process registry — never through tRPC — so
32166
33034
  * `auth: 'admin'` costs those callers nothing.
32167
33035
  */
@@ -46957,16 +47825,6 @@ var ApiKeySummarySchema = zod.z.object({
46957
47825
  createdAt: zod.z.number(),
46958
47826
  lastUsedAt: zod.z.number().optional()
46959
47827
  });
46960
- var CreateApiKeyInputSchema = zod.z.object({
46961
- label: zod.z.string(),
46962
- isAdmin: zod.z.boolean().default(false),
46963
- allowedProviders: zod.z.union([zod.z.array(zod.z.string()), zod.z.literal("*")]).optional(),
46964
- allowedDevices: zod.z.record(zod.z.string(), zod.z.union([zod.z.array(zod.z.string()), zod.z.literal("*")])).optional()
46965
- });
46966
- var CreateApiKeyResultSchema = zod.z.object({
46967
- token: zod.z.string(),
46968
- record: ApiKeySummarySchema
46969
- });
46970
47828
  var ScopedTokenSummarySchema = zod.z.object({
46971
47829
  id: zod.z.string(),
46972
47830
  userId: zod.z.string(),
@@ -47078,20 +47936,11 @@ var userManagementCapability = {
47078
47936
  access: "view"
47079
47937
  }),
47080
47938
  listApiKeys: method(zod.z.void(), zod.z.array(ApiKeySummarySchema), { auth: "admin" }),
47081
- createApiKey: method(CreateApiKeyInputSchema, CreateApiKeyResultSchema, {
47082
- kind: "mutation",
47083
- auth: "admin",
47084
- access: "create"
47085
- }),
47086
47939
  revokeApiKey: method(zod.z.object({ id: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
47087
47940
  kind: "mutation",
47088
47941
  auth: "admin",
47089
47942
  access: "delete"
47090
47943
  }),
47091
- validateApiKey: method(zod.z.object({ token: zod.z.string() }), ApiKeySummarySchema.nullable(), {
47092
- kind: "mutation",
47093
- access: "view"
47094
- }),
47095
47944
  createScopedToken: method(CreateScopedTokenInputSchema, CreateScopedTokenResultSchema, {
47096
47945
  kind: "mutation",
47097
47946
  auth: "admin",
@@ -47181,20 +48030,6 @@ var userManagementCapability = {
47181
48030
  userId: zod.z.string(),
47182
48031
  issuer: zod.z.string()
47183
48032
  }).nullable(), { auth: "admin" }),
47184
- /**
47185
- * Link `(provider, subject)` to `userId`. Idempotent for the same user;
47186
- * REFUSES (throws) an identity already linked to another user, and a
47187
- * user that does not exist.
47188
- */
47189
- linkExternalIdentity: method(ExternalIdentityKeySchema.extend({
47190
- userId: zod.z.string(),
47191
- issuer: zod.z.string().min(1),
47192
- label: zod.z.string().optional()
47193
- }), zod.z.object({ success: zod.z.literal(true) }), {
47194
- kind: "mutation",
47195
- auth: "admin",
47196
- access: "create"
47197
- }),
47198
48033
  /** Remove one link of `userId`. `success: false` when it was not linked to that user. */
47199
48034
  unlinkExternalIdentity: method(ExternalIdentityKeySchema.extend({ userId: zod.z.string() }), zod.z.object({ success: zod.z.boolean() }), {
47200
48035
  kind: "mutation",
@@ -47923,6 +48758,531 @@ var zoneRulesCapability = {
47923
48758
  */
47924
48759
  durability: "restored"
47925
48760
  };
48761
+ /**
48762
+ * `training-provider` — ONE cloud that can run the CamStack trainer.
48763
+ *
48764
+ * A thin, mockable translation layer: it knows how to run a container with a
48765
+ * GPU, move a dataset in, read the job's stdout and hand artefacts back. It
48766
+ * knows nothing about datasets, splits, floors, cost policy or Model Studio —
48767
+ * those are the `training` orchestrator's (D805). Every rule written there
48768
+ * once is what makes a second provider (RunPod, a local NVIDIA node) a new
48769
+ * manifest entry and nothing else.
48770
+ *
48771
+ * Collection, routed by the `addonId` in every single-row input (the `llm` /
48772
+ * `notification-output` posture); `describe` concat-fans across providers.
48773
+ * Every method is `admin`: these spend money.
48774
+ *
48775
+ * Every cloud object a provider creates is tagged with the orchestrator's
48776
+ * `jobKey`, so a retried submit finds the job it already started instead of
48777
+ * starting a second GPU, and `listJobs` can find orphans.
48778
+ *
48779
+ * Bytes never ride a cap field: the dataset arrives as a `ctx.peerBytes`
48780
+ * ticket the producer (pipeline-analytics) minted, and artefacts leave as a
48781
+ * ticket the provider mints (D613).
48782
+ */
48783
+ var TrainingGpuOptionSchema = zod.z.object({
48784
+ id: zod.z.string(),
48785
+ label: zod.z.string(),
48786
+ /** List price, USD per GPU-hour, before any region multiplier. */
48787
+ usdPerHour: zod.z.number().nonnegative(),
48788
+ vramGb: zod.z.number().nonnegative()
48789
+ });
48790
+ var TrainingRegionOptionSchema = zod.z.object({
48791
+ /** `''` = any region (the provider's choice). */
48792
+ id: zod.z.string(),
48793
+ label: zod.z.string(),
48794
+ priceMultiplier: zod.z.number().positive()
48795
+ });
48796
+ /**
48797
+ * The provider's per-account settings, as the orchestrator reads them. They
48798
+ * live in the PROVIDER addon's settings (operator decision 2026-10-08: they
48799
+ * depend on the user's account), never hard-coded in the orchestrator.
48800
+ */
48801
+ var TrainingProviderDefaultsSchema = zod.z.object({
48802
+ gpuType: zod.z.string(),
48803
+ region: zod.z.string(),
48804
+ costCapUsd: zod.z.number().nonnegative(),
48805
+ maxRuntimeSec: zod.z.number().int().positive(),
48806
+ /** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
48807
+ failedDatasetGraceHours: zod.z.number().nonnegative()
48808
+ });
48809
+ var TrainingProviderDescriptorSchema = zod.z.object({
48810
+ addonId: zod.z.string(),
48811
+ /** `modal`, `runpod`, `fake`, … */
48812
+ kind: zod.z.string(),
48813
+ label: zod.z.string(),
48814
+ gpuTypes: zod.z.array(TrainingGpuOptionSchema).readonly(),
48815
+ regions: zod.z.array(TrainingRegionOptionSchema).readonly(),
48816
+ maxDatasetBytes: zod.z.number().int().positive(),
48817
+ maxRuntimeSec: zod.z.number().int().positive(),
48818
+ freeTierNote: zod.z.string().optional(),
48819
+ /** Where the data goes, in one sentence the confirm dialog repeats. */
48820
+ dataResidencyNote: zod.z.string(),
48821
+ /** Credentials are present — never the values. */
48822
+ configured: zod.z.boolean(),
48823
+ defaults: TrainingProviderDefaultsSchema
48824
+ });
48825
+ var TrainingCredentialCheckSchema = zod.z.object({
48826
+ ok: zod.z.boolean(),
48827
+ /** Workspace / account name, when the provider says. */
48828
+ account: zod.z.string().optional(),
48829
+ reason: zod.z.enum([
48830
+ "missing",
48831
+ "invalid",
48832
+ "forbidden",
48833
+ "network",
48834
+ "quota"
48835
+ ]).optional(),
48836
+ detail: zod.z.string().optional()
48837
+ });
48838
+ var ProviderJobStateSchema = zod.z.enum([
48839
+ "pending",
48840
+ "starting",
48841
+ "running",
48842
+ "succeeded",
48843
+ "failed",
48844
+ "cancelled",
48845
+ "lost"
48846
+ ]);
48847
+ var ProviderJobStatusSchema = zod.z.object({
48848
+ jobKey: zod.z.string(),
48849
+ providerJobId: zod.z.string().optional(),
48850
+ state: ProviderJobStateSchema,
48851
+ startedAt: zod.z.number().optional(),
48852
+ endedAt: zod.z.number().optional(),
48853
+ exitCode: zod.z.number().int().optional(),
48854
+ failureReason: zod.z.string().optional(),
48855
+ gpuType: zod.z.string(),
48856
+ region: zod.z.string().optional(),
48857
+ billedSeconds: zod.z.number().nonnegative(),
48858
+ costUsd: zod.z.number().nonnegative(),
48859
+ /** True when `costUsd` is runtime × list price, not a billing answer. */
48860
+ costIsEstimate: zod.z.boolean(),
48861
+ lastFetchedAt: zod.z.number()
48862
+ });
48863
+ /**
48864
+ * The trainer the job runs, by SOURCE revision: the provider turns it into an
48865
+ * image its own way (Modal builds the published Dockerfile at that revision;
48866
+ * a registry-pull provider maps it to a tag). Recorded on the job and echoed
48867
+ * in `result.json`.
48868
+ */
48869
+ var TrainerSourceRefSchema = zod.z.object({
48870
+ /** Hugging Face repo id, e.g. `camstack/camstack-trainer`. */
48871
+ repo: zod.z.string().min(1),
48872
+ /** A commit sha (never a branch) — the job must be reproducible. */
48873
+ revision: zod.z.string().regex(/^[0-9a-f]{7,64}$/)
48874
+ });
48875
+ var JobRef = {
48876
+ addonId: zod.z.string(),
48877
+ jobKey: zod.z.string().uuid()
48878
+ };
48879
+ var TrainingLogLineSchema = zod.z.object({
48880
+ ts: zod.z.number(),
48881
+ text: zod.z.string()
48882
+ });
48883
+ var trainingProviderCapability = {
48884
+ name: "training-provider",
48885
+ scope: "system",
48886
+ mode: "collection",
48887
+ internal: true,
48888
+ methods: {
48889
+ /** Every provider's offer and settings. Concat-fans across providers. */
48890
+ describe: method(zod.z.object({}), zod.z.array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }),
48891
+ /** Call the provider with the stored credentials. Never returns them. */
48892
+ validateCredentials: method(zod.z.object({ addonId: zod.z.string() }), TrainingCredentialCheckSchema, {
48893
+ kind: "mutation",
48894
+ auth: "admin",
48895
+ timeoutMs: 3e4
48896
+ }),
48897
+ /** USD range for a run of `gpuSeconds` on `gpuType` in `region`. */
48898
+ estimateJob: method(zod.z.object({
48899
+ addonId: zod.z.string(),
48900
+ gpuType: zod.z.string(),
48901
+ region: zod.z.string(),
48902
+ datasetBytes: zod.z.number().int().nonnegative(),
48903
+ gpuSecondsLow: zod.z.number().nonnegative(),
48904
+ gpuSecondsHigh: zod.z.number().nonnegative()
48905
+ }), zod.z.object({
48906
+ usdLow: zod.z.number().nonnegative(),
48907
+ usdHigh: zod.z.number().nonnegative(),
48908
+ notes: zod.z.array(zod.z.string()).readonly()
48909
+ }), { auth: "admin" }),
48910
+ /**
48911
+ * Take the dataset archive behind `ticket` (minted by pipeline-analytics,
48912
+ * same host) and land it as the job's dataset in the cloud, streamed —
48913
+ * nothing is staged on the hub's disk. Idempotent per `jobKey`: a second
48914
+ * call replaces a partial upload. Answers what actually landed.
48915
+ */
48916
+ uploadDataset: method(zod.z.object({
48917
+ ...JobRef,
48918
+ ticket: PeerBytesTicketSchema
48919
+ }), zod.z.object({
48920
+ bytes: zod.z.number().int().nonnegative(),
48921
+ sha256: zod.z.string()
48922
+ }), {
48923
+ kind: "mutation",
48924
+ auth: "admin",
48925
+ timeoutMs: 120 * 6e4
48926
+ }),
48927
+ /**
48928
+ * Start the trainer on a GPU. Looks the job up by `jobKey` first, so a
48929
+ * retried submit never starts a second GPU. `jobSpecJson` is written next
48930
+ * to the dataset as `job.json`; `maxRuntimeSec` is enforced by the
48931
+ * provider (a hard timeout), not by the hub.
48932
+ */
48933
+ submitJob: method(zod.z.object({
48934
+ ...JobRef,
48935
+ trainer: TrainerSourceRefSchema,
48936
+ jobSpecJson: zod.z.string().min(2).max(64e3),
48937
+ gpuType: zod.z.string(),
48938
+ region: zod.z.string(),
48939
+ maxRuntimeSec: zod.z.number().int().positive()
48940
+ }), ProviderJobStatusSchema, {
48941
+ kind: "mutation",
48942
+ auth: "admin",
48943
+ timeoutMs: 30 * 6e4
48944
+ }),
48945
+ getJob: method(zod.z.object(JobRef), ProviderJobStatusSchema, {
48946
+ auth: "admin",
48947
+ timeoutMs: 6e4
48948
+ }),
48949
+ /** Stdout/stderr since `cursor` (opaque), oldest first, at most `max` lines. */
48950
+ listJobEvents: method(zod.z.object({
48951
+ ...JobRef,
48952
+ cursor: zod.z.string().optional(),
48953
+ max: zod.z.number().int().min(1).max(500)
48954
+ }), zod.z.object({
48955
+ lines: zod.z.array(TrainingLogLineSchema).readonly(),
48956
+ cursor: zod.z.string()
48957
+ }), {
48958
+ auth: "admin",
48959
+ timeoutMs: 6e4
48960
+ }),
48961
+ /** Stop the job. Idempotent: an unknown or finished job is a no-op. */
48962
+ cancelJob: method(zod.z.object(JobRef), zod.z.object({ ok: zod.z.literal(true) }), {
48963
+ kind: "mutation",
48964
+ auth: "admin",
48965
+ timeoutMs: 6e4
48966
+ }),
48967
+ /** The job's `result.json`, as text (small, JSON). `found: false` before it exists. */
48968
+ readResult: method(zod.z.object(JobRef), zod.z.object({
48969
+ found: zod.z.boolean(),
48970
+ text: zod.z.string().optional()
48971
+ }), {
48972
+ kind: "mutation",
48973
+ auth: "admin",
48974
+ timeoutMs: 5 * 6e4
48975
+ }),
48976
+ /**
48977
+ * Offer one output file of the job (`bundle.tar`) to the caller as a
48978
+ * one-shot `ctx.peerBytes` ticket, streamed from the cloud — the provider
48979
+ * stages nothing on disk. The caller dials it at once.
48980
+ */
48981
+ offerArtifact: method(zod.z.object({
48982
+ ...JobRef,
48983
+ path: zod.z.enum(["bundle.tar"])
48984
+ }), zod.z.object({ ticket: PeerBytesTicketSchema }), {
48985
+ kind: "mutation",
48986
+ auth: "admin",
48987
+ timeoutMs: 5 * 6e4
48988
+ }),
48989
+ /** Delete every cloud object tagged with `jobKey` (the dataset first). Idempotent. */
48990
+ cleanupJob: method(zod.z.object(JobRef), zod.z.object({ deleted: zod.z.array(zod.z.string()).readonly() }), {
48991
+ kind: "mutation",
48992
+ auth: "admin",
48993
+ timeoutMs: 5 * 6e4
48994
+ }),
48995
+ /** Every job the provider can still see — the orphan sweep diffs it against our records. */
48996
+ listJobs: method(zod.z.object({ addonId: zod.z.string() }), zod.z.array(ProviderJobStatusSchema).readonly(), {
48997
+ auth: "admin",
48998
+ timeoutMs: 2 * 6e4
48999
+ })
49000
+ }
49001
+ };
49002
+ /**
49003
+ * `training` — the cloud-training orchestrator (D805). Hub-only singleton,
49004
+ * provided by `addon-training`.
49005
+ *
49006
+ * It owns what every provider must agree on, written once:
49007
+ *
49008
+ * - **Dataset versions.** Taken over from pipeline-analytics (D796): the rows
49009
+ * live here (D808); pipeline-analytics still CUTS a version (it owns the corpus and
49010
+ * the export plan) and reaches this store through the five row methods
49011
+ * below, so the Retrain page and the export did not change.
49012
+ * - **Jobs.** A persisted state machine, a reconcile loop against the
49013
+ * provider, a cost guard, a daily orphan sweep and the retention rules.
49014
+ * - **The return flow.** Verified artefacts → Model Studio
49015
+ * (`custom-model-registry.importModelBundle`) → canary on cameras →
49016
+ * rollback. Nothing is ever registered automatically.
49017
+ *
49018
+ * Every method is `admin`.
49019
+ */
49020
+ /** A version WITH its frame list — what the export and a job read. */
49021
+ var TrainingDatasetVersionRowSchema = RetrainDatasetVersionSchema.omit({ frameCount: true }).extend({ frameIds: zod.z.array(zod.z.string()).max(2e4).readonly() });
49022
+ /** A base model a detector job can fine-tune, and the baseline it is judged against. */
49023
+ var TrainingBaseModelSchema = zod.z.object({
49024
+ modelId: zod.z.string(),
49025
+ label: zod.z.string(),
49026
+ weights: zod.z.string(),
49027
+ imgsz: zod.z.number().int(),
49028
+ baselineOnnxUrl: zod.z.string(),
49029
+ baselineFloors: zod.z.record(zod.z.string(), zod.z.number()),
49030
+ /** The base the fleet runs most — the UI's default. */
49031
+ recommended: zod.z.boolean()
49032
+ });
49033
+ var TrainingPresetSchema = zod.z.enum([
49034
+ "quick",
49035
+ "standard",
49036
+ "thorough"
49037
+ ]);
49038
+ var TrainingJobRequestSchema = zod.z.object({
49039
+ datasetVersionId: zod.z.string().min(1),
49040
+ /** The `training-provider` addon that runs it. */
49041
+ addonId: zod.z.string().min(1),
49042
+ gpuType: zod.z.string().min(1),
49043
+ region: zod.z.string(),
49044
+ baseModelId: zod.z.string().min(1),
49045
+ preset: TrainingPresetSchema,
49046
+ /** Freeze the backbone's first N layers; `null` = the preset's choice. */
49047
+ freeze: zod.z.number().int().nonnegative().nullable(),
49048
+ /** Cameras held out whole for evaluation; `null` = the orchestrator's default. */
49049
+ holdoutCameras: zod.z.array(zod.z.number().int()).nullable()
49050
+ });
49051
+ var TrainingSplitPlanSchema = zod.z.object({
49052
+ holdoutCameras: zod.z.array(zod.z.number().int()).readonly(),
49053
+ /** Used when no camera is held out: the last N days of every camera. */
49054
+ holdoutLastDays: zod.z.number().int().nonnegative(),
49055
+ /** Holdout subjects per class (camera holdout only; `null` = known only after the split). */
49056
+ holdoutSupport: zod.z.record(zod.z.string(), zod.z.number().int()).nullable(),
49057
+ trainSubjects: zod.z.number().int()
49058
+ });
49059
+ var TrainingRefusalReasonSchema = zod.z.enum([
49060
+ "unknown-dataset",
49061
+ "unknown-provider",
49062
+ "provider-not-configured",
49063
+ "unknown-base-model",
49064
+ "unknown-gpu",
49065
+ "dataset-too-large",
49066
+ "no-training-subjects",
49067
+ "job-already-running"
49068
+ ]);
49069
+ var TrainingJobPlanSchema = zod.z.object({
49070
+ refusal: zod.z.object({
49071
+ reason: TrainingRefusalReasonSchema,
49072
+ detail: zod.z.string()
49073
+ }).nullable(),
49074
+ warnings: zod.z.array(zod.z.string()).readonly(),
49075
+ split: TrainingSplitPlanSchema.nullable(),
49076
+ estimate: zod.z.object({
49077
+ datasetBytes: zod.z.number().int(),
49078
+ uploadMinutesAt20Mbps: zod.z.number(),
49079
+ gpuSecondsLow: zod.z.number(),
49080
+ gpuSecondsHigh: zod.z.number(),
49081
+ usdLow: zod.z.number(),
49082
+ usdHigh: zod.z.number(),
49083
+ notes: zod.z.array(zod.z.string()).readonly()
49084
+ }).nullable(),
49085
+ /** Pre-filled from the provider's settings; the operator may lower or raise them. */
49086
+ defaults: zod.z.object({
49087
+ maxCostUsd: zod.z.number(),
49088
+ maxRuntimeSec: zod.z.number().int()
49089
+ }).nullable(),
49090
+ /** The sentence the confirm dialog repeats: where the frames go and when they are deleted. */
49091
+ dataNotice: zod.z.string()
49092
+ });
49093
+ /**
49094
+ * Orchestrator-owned states, persisted. `uploading` covers exporting the
49095
+ * archive (it is streamed, never staged). Terminal: `failed`, `cancelled`,
49096
+ * `lost`, `expired`, `discarded`. `registered` and `canary` are where a
49097
+ * result lives after the operator acted on it.
49098
+ */
49099
+ var TrainingJobStateSchema = zod.z.enum([
49100
+ "uploading",
49101
+ "submitted",
49102
+ "starting",
49103
+ "running",
49104
+ "collecting",
49105
+ "ready",
49106
+ "registered",
49107
+ "canary",
49108
+ "failed",
49109
+ "cancelling",
49110
+ "cancelled",
49111
+ "lost",
49112
+ "expired",
49113
+ "discarded"
49114
+ ]);
49115
+ var TrainingFailureReasonSchema = zod.z.enum([
49116
+ "cost-cap",
49117
+ "runtime-cap",
49118
+ "upload-failed",
49119
+ "submit-failed",
49120
+ "trainer-failed",
49121
+ "result-invalid",
49122
+ "checksum-mismatch",
49123
+ "fetch-failed",
49124
+ "provider-error",
49125
+ "hub-restart-during-upload"
49126
+ ]);
49127
+ var TrainingProgressPointSchema = zod.z.object({
49128
+ epoch: zod.z.number().int(),
49129
+ map50: zod.z.number().nullable(),
49130
+ map50_95: zod.z.number().nullable()
49131
+ });
49132
+ var TrainingCanarySchema = zod.z.object({
49133
+ deviceIds: zod.z.array(zod.z.number().int()).readonly(),
49134
+ since: zod.z.number(),
49135
+ /** Every override the canary replaced, exactly, so rollback restores it. */
49136
+ previous: zod.z.array(zod.z.object({
49137
+ deviceId: zod.z.number().int(),
49138
+ nodeId: zod.z.string(),
49139
+ deviceKey: zod.z.string(),
49140
+ patch: zod.z.object({
49141
+ modelId: zod.z.string().optional(),
49142
+ settings: zod.z.record(zod.z.string(), zod.z.unknown()).readonly().optional()
49143
+ }).nullable()
49144
+ })).readonly()
49145
+ });
49146
+ var TrainingJobSchema = zod.z.object({
49147
+ id: zod.z.string(),
49148
+ idempotencyKey: zod.z.string(),
49149
+ name: zod.z.string(),
49150
+ createdAt: zod.z.number(),
49151
+ updatedAt: zod.z.number(),
49152
+ state: TrainingJobStateSchema,
49153
+ stage: TrainingStageSchema.nullable(),
49154
+ failure: zod.z.object({
49155
+ reason: TrainingFailureReasonSchema,
49156
+ stage: zod.z.string().nullable(),
49157
+ detail: zod.z.string()
49158
+ }).nullable(),
49159
+ request: TrainingJobRequestSchema,
49160
+ maxCostUsd: zod.z.number().nonnegative(),
49161
+ maxRuntimeSec: zod.z.number().int().positive(),
49162
+ trainer: TrainerSourceRefSchema,
49163
+ datasetLayout: RetrainExportLayoutSchema,
49164
+ dataset: zod.z.object({
49165
+ bytes: zod.z.number().int(),
49166
+ sha256: zod.z.string(),
49167
+ uploadedAt: zod.z.number()
49168
+ }).nullable(),
49169
+ split: TrainingSplitPlanSchema,
49170
+ provider: zod.z.object({
49171
+ state: ProviderJobStateSchema.nullable(),
49172
+ providerJobId: zod.z.string().nullable(),
49173
+ startedAt: zod.z.number().nullable(),
49174
+ endedAt: zod.z.number().nullable(),
49175
+ billedSeconds: zod.z.number(),
49176
+ costUsd: zod.z.number(),
49177
+ costIsEstimate: zod.z.boolean(),
49178
+ lastPolledAt: zod.z.number().nullable()
49179
+ }),
49180
+ progress: zod.z.object({
49181
+ epoch: zod.z.number().int().nullable(),
49182
+ epochs: zod.z.number().int().nullable(),
49183
+ etaSec: zod.z.number().nullable(),
49184
+ series: zod.z.array(TrainingProgressPointSchema).readonly()
49185
+ }),
49186
+ /** The last lines of the job's output (≤ 200), newest last. Never frame paths. */
49187
+ logTail: zod.z.array(zod.z.string()).readonly(),
49188
+ logCursor: zod.z.string().nullable(),
49189
+ artifacts: zod.z.object({
49190
+ bundleBytes: zod.z.number().int(),
49191
+ bundleSha256: zod.z.string(),
49192
+ fetchedAt: zod.z.number(),
49193
+ /** When the staged artefacts are deleted from the hub. */
49194
+ expiresAt: zod.z.number()
49195
+ }).nullable(),
49196
+ /** When the provider-side copy of the frames was deleted; null while it exists. */
49197
+ cloudDatasetDeletedAt: zod.z.number().nullable(),
49198
+ /** A failed job's dataset is kept until then (the provider's grace), then deleted. */
49199
+ cloudCleanupDueAt: zod.z.number().nullable(),
49200
+ /** Model ids registered from this job (base, and int8 when kept). */
49201
+ registeredModelIds: zod.z.array(zod.z.string()).readonly(),
49202
+ canary: TrainingCanarySchema.nullable()
49203
+ });
49204
+ var IdInput = zod.z.object({ id: zod.z.string().min(1) });
49205
+ var trainingCapability = {
49206
+ name: "training",
49207
+ scope: "system",
49208
+ mode: "singleton",
49209
+ mount: { kind: "hub-only" },
49210
+ methods: {
49211
+ /** Store a version once. `stored: false` = that id already exists (immutable). */
49212
+ putDatasetVersion: method(TrainingDatasetVersionRowSchema, zod.z.object({ stored: zod.z.boolean() }), {
49213
+ kind: "mutation",
49214
+ auth: "admin"
49215
+ }),
49216
+ getDatasetVersion: method(IdInput, TrainingDatasetVersionRowSchema.nullable(), { auth: "admin" }),
49217
+ /** Every version without its frame list, newest first. */
49218
+ listDatasetVersions: method(zod.z.object({}), zod.z.array(RetrainDatasetVersionSchema).readonly(), { auth: "admin" }),
49219
+ findDatasetVersionIdByName: method(zod.z.object({ name: zod.z.string() }), zod.z.object({ id: zod.z.string().nullable() }), { auth: "admin" }),
49220
+ removeDatasetVersion: method(IdInput, zod.z.object({ removed: zod.z.boolean() }), {
49221
+ kind: "mutation",
49222
+ auth: "admin"
49223
+ }),
49224
+ listBaseModels: method(zod.z.object({}), zod.z.array(TrainingBaseModelSchema).readonly(), { auth: "admin" }),
49225
+ /** Split, estimate, defaults and any named refusal — nothing is started. */
49226
+ planJob: method(TrainingJobRequestSchema, TrainingJobPlanSchema, {
49227
+ auth: "admin",
49228
+ timeoutMs: 6e4
49229
+ }),
49230
+ /**
49231
+ * Start a job. The same `idempotencyKey` returns the job it already
49232
+ * started. Answers at once (`uploading`); the upload and the submit run in
49233
+ * the background and the job record says how far they got.
49234
+ */
49235
+ startJob: method(TrainingJobRequestSchema.extend({
49236
+ name: zod.z.string().min(1).max(120),
49237
+ maxCostUsd: zod.z.number().positive(),
49238
+ maxRuntimeSec: zod.z.number().int().positive(),
49239
+ idempotencyKey: zod.z.string().min(8).max(80)
49240
+ }), zod.z.discriminatedUnion("kind", [zod.z.object({
49241
+ kind: zod.z.literal("started"),
49242
+ job: TrainingJobSchema
49243
+ }), zod.z.object({
49244
+ kind: zod.z.literal("refused"),
49245
+ reason: TrainingRefusalReasonSchema,
49246
+ detail: zod.z.string()
49247
+ })]), {
49248
+ kind: "mutation",
49249
+ auth: "admin",
49250
+ timeoutMs: 12e4
49251
+ }),
49252
+ listJobs: method(zod.z.object({}), zod.z.array(TrainingJobSchema).readonly(), { auth: "admin" }),
49253
+ getJob: method(IdInput, TrainingJobSchema.nullable(), { auth: "admin" }),
49254
+ cancelJob: method(IdInput, TrainingJobSchema, {
49255
+ kind: "mutation",
49256
+ auth: "admin",
49257
+ timeoutMs: 12e4
49258
+ }),
49259
+ /** The verified `result.json` of a job in `ready` or later; null before. */
49260
+ getJobResult: method(IdInput, TrainingResultSchema.nullable(), { auth: "admin" }),
49261
+ /** Hand the verified bundle to Model Studio; optionally canary right after. */
49262
+ registerResult: method(IdInput.extend({ canaryDeviceIds: zod.z.array(zod.z.number().int()).readonly() }), TrainingJobSchema, {
49263
+ kind: "mutation",
49264
+ auth: "admin",
49265
+ timeoutMs: 30 * 6e4
49266
+ }),
49267
+ /** Pin the registered model on these cameras, remembering what it replaced. */
49268
+ canaryResult: method(IdInput.extend({ deviceIds: zod.z.array(zod.z.number().int()).min(1).readonly() }), TrainingJobSchema, {
49269
+ kind: "mutation",
49270
+ auth: "admin",
49271
+ timeoutMs: 12e4
49272
+ }),
49273
+ /** Put every canary camera back exactly as it was. */
49274
+ rollbackCanary: method(IdInput, TrainingJobSchema, {
49275
+ kind: "mutation",
49276
+ auth: "admin",
49277
+ timeoutMs: 12e4
49278
+ }),
49279
+ /** Delete the staged artefacts of an unregistered result. */
49280
+ discardResult: method(IdInput, TrainingJobSchema, {
49281
+ kind: "mutation",
49282
+ auth: "admin"
49283
+ })
49284
+ }
49285
+ };
47926
49286
  function isValueOf(slot, value) {
47927
49287
  return value.slot === slot;
47928
49288
  }
@@ -50371,6 +51731,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50371
51731
  addonId: null,
50372
51732
  access: "create"
50373
51733
  },
51734
+ "backup.checkBackupKey": {
51735
+ capName: "backup",
51736
+ capScope: "system",
51737
+ addonId: null,
51738
+ access: "create"
51739
+ },
50374
51740
  "backup.delete": {
50375
51741
  capName: "backup",
50376
51742
  capScope: "system",
@@ -50383,6 +51749,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
50383
51749
  addonId: null,
50384
51750
  access: "delete"
50385
51751
  },
51752
+ "backup.disableEncryption": {
51753
+ capName: "backup",
51754
+ capScope: "system",
51755
+ addonId: null,
51756
+ access: "create"
51757
+ },
51758
+ "backup.getEncryption": {
51759
+ capName: "backup",
51760
+ capScope: "system",
51761
+ addonId: null,
51762
+ access: "view"
51763
+ },
50386
51764
  "backup.getEntries": {
50387
51765
  capName: "backup",
50388
51766
  capScope: "system",
@@ -50437,6 +51815,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50437
51815
  addonId: null,
50438
51816
  access: "create"
50439
51817
  },
51818
+ "backup.setupEncryption": {
51819
+ capName: "backup",
51820
+ capScope: "system",
51821
+ addonId: null,
51822
+ access: "create"
51823
+ },
50440
51824
  "backup.trigger": {
50441
51825
  capName: "backup",
50442
51826
  capScope: "system",
@@ -50989,6 +52373,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50989
52373
  addonId: null,
50990
52374
  access: "create"
50991
52375
  },
52376
+ "customModelRegistry.importModelBundle": {
52377
+ capName: "custom-model-registry",
52378
+ capScope: "system",
52379
+ addonId: null,
52380
+ access: "create"
52381
+ },
50992
52382
  "customModelRegistry.listModels": {
50993
52383
  capName: "custom-model-registry",
50994
52384
  capScope: "system",
@@ -52429,6 +53819,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52429
53819
  addonId: null,
52430
53820
  access: "view"
52431
53821
  },
53822
+ "keychainAdmin.acknowledgeReset": {
53823
+ capName: "keychain-admin",
53824
+ capScope: "system",
53825
+ addonId: null,
53826
+ access: "create"
53827
+ },
52432
53828
  "keychainAdmin.deleteOrphans": {
52433
53829
  capName: "keychain-admin",
52434
53830
  capScope: "system",
@@ -52465,6 +53861,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52465
53861
  addonId: null,
52466
53862
  access: "delete"
52467
53863
  },
53864
+ "keychainAdmin.resetReport": {
53865
+ capName: "keychain-admin",
53866
+ capScope: "system",
53867
+ addonId: null,
53868
+ access: "delete"
53869
+ },
52468
53870
  "keychainAdmin.reveal": {
52469
53871
  capName: "keychain-admin",
52470
53872
  capScope: "system",
@@ -54211,6 +55613,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
54211
55613
  addonId: null,
54212
55614
  access: "view"
54213
55615
  },
55616
+ "pipelineAnalytics.offerRetrainArchive": {
55617
+ capName: "pipeline-analytics",
55618
+ capScope: "device",
55619
+ addonId: null,
55620
+ access: "create"
55621
+ },
54214
55622
  "pipelineAnalytics.ownersWithMedia": {
54215
55623
  capName: "pipeline-analytics",
54216
55624
  capScope: "device",
@@ -56749,6 +58157,174 @@ var METHOD_ACCESS_MAP = Object.freeze({
56749
58157
  addonId: null,
56750
58158
  access: "view"
56751
58159
  },
58160
+ "training.canaryResult": {
58161
+ capName: "training",
58162
+ capScope: "system",
58163
+ addonId: null,
58164
+ access: "create"
58165
+ },
58166
+ "training.cancelJob": {
58167
+ capName: "training",
58168
+ capScope: "system",
58169
+ addonId: null,
58170
+ access: "create"
58171
+ },
58172
+ "training.discardResult": {
58173
+ capName: "training",
58174
+ capScope: "system",
58175
+ addonId: null,
58176
+ access: "create"
58177
+ },
58178
+ "training.findDatasetVersionIdByName": {
58179
+ capName: "training",
58180
+ capScope: "system",
58181
+ addonId: null,
58182
+ access: "view"
58183
+ },
58184
+ "training.getDatasetVersion": {
58185
+ capName: "training",
58186
+ capScope: "system",
58187
+ addonId: null,
58188
+ access: "view"
58189
+ },
58190
+ "training.getJob": {
58191
+ capName: "training",
58192
+ capScope: "system",
58193
+ addonId: null,
58194
+ access: "view"
58195
+ },
58196
+ "training.getJobResult": {
58197
+ capName: "training",
58198
+ capScope: "system",
58199
+ addonId: null,
58200
+ access: "view"
58201
+ },
58202
+ "training.listBaseModels": {
58203
+ capName: "training",
58204
+ capScope: "system",
58205
+ addonId: null,
58206
+ access: "view"
58207
+ },
58208
+ "training.listDatasetVersions": {
58209
+ capName: "training",
58210
+ capScope: "system",
58211
+ addonId: null,
58212
+ access: "view"
58213
+ },
58214
+ "training.listJobs": {
58215
+ capName: "training",
58216
+ capScope: "system",
58217
+ addonId: null,
58218
+ access: "view"
58219
+ },
58220
+ "training.planJob": {
58221
+ capName: "training",
58222
+ capScope: "system",
58223
+ addonId: null,
58224
+ access: "view"
58225
+ },
58226
+ "training.putDatasetVersion": {
58227
+ capName: "training",
58228
+ capScope: "system",
58229
+ addonId: null,
58230
+ access: "create"
58231
+ },
58232
+ "training.registerResult": {
58233
+ capName: "training",
58234
+ capScope: "system",
58235
+ addonId: null,
58236
+ access: "create"
58237
+ },
58238
+ "training.removeDatasetVersion": {
58239
+ capName: "training",
58240
+ capScope: "system",
58241
+ addonId: null,
58242
+ access: "delete"
58243
+ },
58244
+ "training.rollbackCanary": {
58245
+ capName: "training",
58246
+ capScope: "system",
58247
+ addonId: null,
58248
+ access: "create"
58249
+ },
58250
+ "training.startJob": {
58251
+ capName: "training",
58252
+ capScope: "system",
58253
+ addonId: null,
58254
+ access: "create"
58255
+ },
58256
+ "trainingProvider.cancelJob": {
58257
+ capName: "training-provider",
58258
+ capScope: "system",
58259
+ addonId: null,
58260
+ access: "create"
58261
+ },
58262
+ "trainingProvider.cleanupJob": {
58263
+ capName: "training-provider",
58264
+ capScope: "system",
58265
+ addonId: null,
58266
+ access: "create"
58267
+ },
58268
+ "trainingProvider.describe": {
58269
+ capName: "training-provider",
58270
+ capScope: "system",
58271
+ addonId: null,
58272
+ access: "view"
58273
+ },
58274
+ "trainingProvider.estimateJob": {
58275
+ capName: "training-provider",
58276
+ capScope: "system",
58277
+ addonId: null,
58278
+ access: "view"
58279
+ },
58280
+ "trainingProvider.getJob": {
58281
+ capName: "training-provider",
58282
+ capScope: "system",
58283
+ addonId: null,
58284
+ access: "view"
58285
+ },
58286
+ "trainingProvider.listJobEvents": {
58287
+ capName: "training-provider",
58288
+ capScope: "system",
58289
+ addonId: null,
58290
+ access: "view"
58291
+ },
58292
+ "trainingProvider.listJobs": {
58293
+ capName: "training-provider",
58294
+ capScope: "system",
58295
+ addonId: null,
58296
+ access: "view"
58297
+ },
58298
+ "trainingProvider.offerArtifact": {
58299
+ capName: "training-provider",
58300
+ capScope: "system",
58301
+ addonId: null,
58302
+ access: "create"
58303
+ },
58304
+ "trainingProvider.readResult": {
58305
+ capName: "training-provider",
58306
+ capScope: "system",
58307
+ addonId: null,
58308
+ access: "create"
58309
+ },
58310
+ "trainingProvider.submitJob": {
58311
+ capName: "training-provider",
58312
+ capScope: "system",
58313
+ addonId: null,
58314
+ access: "create"
58315
+ },
58316
+ "trainingProvider.uploadDataset": {
58317
+ capName: "training-provider",
58318
+ capScope: "system",
58319
+ addonId: null,
58320
+ access: "create"
58321
+ },
58322
+ "trainingProvider.validateCredentials": {
58323
+ capName: "training-provider",
58324
+ capScope: "system",
58325
+ addonId: null,
58326
+ access: "create"
58327
+ },
56752
58328
  "turnProvider.getTurnServers": {
56753
58329
  capName: "turn-provider",
56754
58330
  capScope: "system",
@@ -56773,12 +58349,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56773
58349
  addonId: null,
56774
58350
  access: "create"
56775
58351
  },
56776
- "userManagement.createApiKey": {
56777
- capName: "user-management",
56778
- capScope: "system",
56779
- addonId: null,
56780
- access: "create"
56781
- },
56782
58352
  "userManagement.createScopedToken": {
56783
58353
  capName: "user-management",
56784
58354
  capScope: "system",
@@ -56815,12 +58385,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56815
58385
  addonId: null,
56816
58386
  access: "view"
56817
58387
  },
56818
- "userManagement.linkExternalIdentity": {
56819
- capName: "user-management",
56820
- capScope: "system",
56821
- addonId: null,
56822
- access: "create"
56823
- },
56824
58388
  "userManagement.listApiKeys": {
56825
58389
  capName: "user-management",
56826
58390
  capScope: "system",
@@ -56923,12 +58487,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
56923
58487
  addonId: null,
56924
58488
  access: "create"
56925
58489
  },
56926
- "userManagement.validateApiKey": {
56927
- capName: "user-management",
56928
- capScope: "system",
56929
- addonId: null,
56930
- access: "view"
56931
- },
56932
58490
  "userManagement.validateCredentials": {
56933
58491
  capName: "user-management",
56934
58492
  capScope: "system",
@@ -58921,6 +60479,8 @@ var ALL_CAPABILITY_DEFINITIONS = [
58921
60479
  temperatureSensorCapability,
58922
60480
  terminalSessionCapability,
58923
60481
  toastCapability,
60482
+ trainingCapability,
60483
+ trainingProviderCapability,
58924
60484
  turnProviderCapability,
58925
60485
  updateCapability,
58926
60486
  userManagementCapability,
@@ -61299,6 +62859,11 @@ Object.freeze({
61299
62859
  form: "single",
61300
62860
  optional: false
61301
62861
  }],
62862
+ "training.canaryResult": [{
62863
+ name: "deviceIds",
62864
+ form: "array",
62865
+ optional: false
62866
+ }],
61302
62867
  "update.getStatus": [{
61303
62868
  name: "deviceId",
61304
62869
  form: "single",
@@ -62936,7 +64501,12 @@ var CREDENTIAL_PARAM_PATTERNS = [
62936
64501
  /^bearer$/i,
62937
64502
  /^api[_-]?key$/i,
62938
64503
  /^access[_-]?token$/i,
62939
- /^refresh[_-]?token$/i
64504
+ /^refresh[_-]?token$/i,
64505
+ /^bridge$/i,
64506
+ /^code$/i,
64507
+ /^state$/i,
64508
+ /^challenge$/i,
64509
+ /^link$/i
62940
64510
  ];
62941
64511
  function isCredentialParam(name) {
62942
64512
  return CREDENTIAL_PARAM_PATTERNS.some((re) => re.test(name));
@@ -62958,6 +64528,10 @@ function isCredentialParam(name) {
62958
64528
  * are NOT masked (those are entity references, not credentials).
62959
64529
  */
62960
64530
  function maskUrlCredentials(rawUrl) {
64531
+ if (rawUrl.startsWith("/") && !rawUrl.startsWith("//")) {
64532
+ const masked = maskUrlCredentials(`http://relative.invalid${rawUrl}`);
64533
+ return masked.startsWith("http://relative.invalid") ? masked.slice(23) : rawUrl;
64534
+ }
62961
64535
  try {
62962
64536
  const u = new URL(rawUrl);
62963
64537
  let touched = false;
@@ -63313,6 +64887,12 @@ Object.defineProperty(exports, "HF_REPO", {
63313
64887
  return HF_REPO;
63314
64888
  }
63315
64889
  });
64890
+ Object.defineProperty(exports, "KEYCHAIN_RESET_TRIGGER_ACTOR", {
64891
+ enumerable: true,
64892
+ get: function() {
64893
+ return KEYCHAIN_RESET_TRIGGER_ACTOR;
64894
+ }
64895
+ });
63316
64896
  Object.defineProperty(exports, "LOG_LEVEL_RANK", {
63317
64897
  enumerable: true,
63318
64898
  get: function() {
@@ -63679,6 +65259,12 @@ Object.defineProperty(exports, "createStatefulExpressionMemory", {
63679
65259
  return createStatefulExpressionMemory;
63680
65260
  }
63681
65261
  });
65262
+ Object.defineProperty(exports, "customAction", {
65263
+ enumerable: true,
65264
+ get: function() {
65265
+ return customAction;
65266
+ }
65267
+ });
63682
65268
  Object.defineProperty(exports, "dataStoreProviderCapability", {
63683
65269
  enumerable: true,
63684
65270
  get: function() {
@@ -63703,6 +65289,12 @@ Object.defineProperty(exports, "decodeVectorBase64", {
63703
65289
  return decodeVectorBase64;
63704
65290
  }
63705
65291
  });
65292
+ Object.defineProperty(exports, "defineCustomActions", {
65293
+ enumerable: true,
65294
+ get: function() {
65295
+ return defineCustomActions;
65296
+ }
65297
+ });
63706
65298
  Object.defineProperty(exports, "defineExtensionSlotProvider", {
63707
65299
  enumerable: true,
63708
65300
  get: function() {
@@ -64009,6 +65601,12 @@ Object.defineProperty(exports, "looseSchema", {
64009
65601
  return looseSchema;
64010
65602
  }
64011
65603
  });
65604
+ Object.defineProperty(exports, "makeSecretMarker", {
65605
+ enumerable: true,
65606
+ get: function() {
65607
+ return makeSecretMarker;
65608
+ }
65609
+ });
64012
65610
  Object.defineProperty(exports, "maskUrlCredentials", {
64013
65611
  enumerable: true,
64014
65612
  get: function() {