@camstack/system 1.2.381 → 1.2.382
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-runner.js +4 -2
- package/dist/addon-runner.mjs +4 -2
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/auth/admin-token-manager.d.ts +75 -0
- package/dist/auth/api-key-manager.d.ts +1 -14
- package/dist/auth/auth-audit-log.d.ts +59 -0
- package/dist/auth/auth-manager.d.ts +28 -16
- package/dist/auth/cidr.d.ts +22 -0
- package/dist/auth/login-session-manager.d.ts +158 -0
- package/dist/auth/user-management-internal.d.ts +69 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +14 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +291 -3631
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +284 -3617
- package/dist/builtins/backup-orchestrator/encryption-key-store.d.ts +36 -0
- package/dist/builtins/backup-orchestrator/manifest-store.d.ts +19 -1
- package/dist/builtins/backup-orchestrator/restore-archive.d.ts +24 -0
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +1108 -733
- package/dist/builtins/keychain/index.mjs +1108 -733
- package/dist/builtins/keychain/keychain-reset-trigger.d.ts +25 -0
- package/dist/builtins/keychain/keychain-service.d.ts +36 -3
- package/dist/builtins/keychain/keychain-store.d.ts +24 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.d.ts +16 -0
- package/dist/builtins/local-auth/local-auth.addon.js +1021 -118
- package/dist/builtins/local-auth/local-auth.addon.mjs +1034 -141
- package/dist/builtins/local-network/local-network.addon.js +3 -3
- package/dist/builtins/local-network/local-network.addon.mjs +3 -3
- package/dist/builtins/loki-logging/index.js +97 -29
- package/dist/builtins/loki-logging/index.mjs +97 -29
- package/dist/builtins/loki-logging/loki-logging.addon.d.ts +7 -23
- package/dist/builtins/loki-logging/retire-enabled-flag.d.ts +23 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
- package/dist/builtins/system-backup/backup-encryption.d.ts +57 -0
- package/dist/builtins/system-backup/system-backup.service.d.ts +16 -1
- package/dist/builtins/system-config/cluster-secret-keeper.d.ts +7 -0
- package/dist/builtins/system-config/system-config.addon.d.ts +13 -3
- package/dist/builtins/system-config/system-config.addon.js +324 -38
- package/dist/builtins/system-config/system-config.addon.mjs +312 -39
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-hf-Gce_4.js → child-cap-dispatch-BrMdKHfp.js} +6 -3
- package/dist/{child-cap-dispatch-BTe4eKep.mjs → child-cap-dispatch-WsYPNvnK.mjs} +10 -7
- package/dist/{cluster-secret-BUiG8DCo.js → cluster-secret-BEPLh3LL.js} +1 -1
- package/dist/{cluster-secret-DgJTZ4qY.mjs → cluster-secret-DhPdZ0VA.mjs} +1 -1
- package/dist/{composition-sources-4SmsP0O4.js → composition-sources-C6ZynCZ2.js} +1 -1
- package/dist/{composition-sources-CgOQVIwa.mjs → composition-sources-XqRX51db.mjs} +1 -1
- package/dist/{dist-BC0GnGeg.mjs → dist-CP5TTjwd.mjs} +1710 -158
- package/dist/{dist-BugrtARh.js → dist-tnAO12ak.js} +1721 -157
- package/dist/{event-loop-stall-monitor-CnQ-K6Gn.js → event-loop-stall-monitor-9kR0sAPS.js} +15 -15
- package/dist/{event-loop-stall-monitor-GgpnfAC0.mjs → event-loop-stall-monitor-WkssIXjq.mjs} +15 -15
- package/dist/{hub-cap-forward-CH6_VWVO.mjs → hub-cap-forward-CDiN1p98.mjs} +18 -2
- package/dist/{hub-cap-forward-COCmYaup.js → hub-cap-forward-W8oEm4ME.js} +23 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +3341 -79
- package/dist/index.mjs +3567 -354
- package/dist/{jwt-signing-key-CsuVKA1W.mjs → jwt-signing-key-CAbozwvF.mjs} +1 -1
- package/dist/{jwt-signing-key-CjoZcol8.js → jwt-signing-key-Dgf8sIXY.js} +1 -1
- package/dist/kernel/index.d.ts +12 -0
- package/dist/kernel/moleculer/broker-factory.d.ts +8 -1
- package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +1 -0
- package/dist/kernel/moleculer/hub-service.d.ts +9 -0
- package/dist/kernel/moleculer/mesh-access-middleware.d.ts +28 -0
- package/dist/kernel/moleculer/mesh-auth-mode.d.ts +64 -0
- package/dist/kernel/moleculer/mesh-authenticator.d.ts +71 -0
- package/dist/kernel/moleculer/mesh-auto-enforce.d.ts +26 -0
- package/dist/kernel/moleculer/mesh-join-client.d.ts +51 -0
- package/dist/kernel/moleculer/mesh-join-proof.d.ts +39 -0
- package/dist/kernel/system-settings-policy.d.ts +4 -3
- package/dist/kernel/transport/hub-cap-forward.d.ts +14 -3
- package/dist/kernel/transport/index.d.ts +1 -1
- package/dist/restore-archive-BV39nY46.mjs +4045 -0
- package/dist/restore-archive-CbvrfPuB.js +4139 -0
- package/dist/{retired-settings-keys-DCuetwOt.js → retired-settings-keys-4bHpL-0m.js} +1 -1
- package/dist/{retired-settings-keys-MCVhxPkJ.mjs → retired-settings-keys-CzpIkwS-.mjs} +1 -1
- package/dist/{tls-CJHogwTl.mjs → tls-4cRH24Id.mjs} +3 -3
- package/package.json +1 -1
- package/dist/{tls-CCMX7MjX.js → tls-CljF057D.js} +1 -1
|
@@ -708,7 +708,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
708
708
|
return EventCategory;
|
|
709
709
|
}({});
|
|
710
710
|
//#endregion
|
|
711
|
-
//#region ../types/dist/sleep-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
892
|
+
return isRecord$2(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
|
|
893
893
|
}
|
|
894
894
|
function isSecretClear(value) {
|
|
895
|
-
return isRecord$
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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
|
|
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
|
|
1838
|
-
|
|
1839
|
-
|
|
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
|
|
2056
|
+
operatorWrite
|
|
1848
2057
|
});
|
|
1849
|
-
|
|
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
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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)
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
3028
|
+
const settings = this.rawSettings();
|
|
2682
3029
|
if (!settings) return {};
|
|
2683
3030
|
const delaysMs = [
|
|
2684
3031
|
150,
|
|
@@ -6652,6 +6999,278 @@ var DERIVED_CAMERA_SILENCED_CAP_NAMES = [
|
|
|
6652
6999
|
"scene-monitor"
|
|
6653
7000
|
];
|
|
6654
7001
|
/**
|
|
7002
|
+
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
7003
|
+
* gets back (cloud training, D805+).
|
|
7004
|
+
*
|
|
7005
|
+
* The trainer (`hf/camstack-trainer`, published as the Hugging Face repo
|
|
7006
|
+
* `camstack/camstack-trainer`) is a separate AGPL program the hub never
|
|
7007
|
+
* imports: `job.json` goes in, files come out, and progress is `CAMSTACK_EVENT`
|
|
7008
|
+
* lines on stdout. These schemas are the hub's half of that boundary; the
|
|
7009
|
+
* trainer's pydantic models (and the JSON Schemas generated from them under
|
|
7010
|
+
* `hf/camstack-trainer/schemas/`) are the other half. A change here is a
|
|
7011
|
+
* change there, in the same piece of work.
|
|
7012
|
+
*
|
|
7013
|
+
* No `.default()` anywhere: defaults do not run on the addon cap path, so the
|
|
7014
|
+
* orchestrator fills every field of a job spec explicitly.
|
|
7015
|
+
*/
|
|
7016
|
+
var TRAINING_JOB_SCHEMA = "camstack.training/v1";
|
|
7017
|
+
var TRAINING_RESULT_SCHEMA = "camstack.training-result/v1";
|
|
7018
|
+
/** What the trainer can export. Edge TPU is a later, separate image. */
|
|
7019
|
+
var TrainingExportSchema = zod.z.enum([
|
|
7020
|
+
"onnx",
|
|
7021
|
+
"openvino-fp16",
|
|
7022
|
+
"openvino-int8",
|
|
7023
|
+
"coreml-fp16"
|
|
7024
|
+
]);
|
|
7025
|
+
var FloorsSchema = zod.z.record(zod.z.string(), zod.z.number().min(0).max(1));
|
|
7026
|
+
zod.z.object({
|
|
7027
|
+
schema: zod.z.literal(TRAINING_JOB_SCHEMA),
|
|
7028
|
+
jobKey: zod.z.string().uuid(),
|
|
7029
|
+
target: zod.z.object({
|
|
7030
|
+
kind: zod.z.literal("detector"),
|
|
7031
|
+
step: zod.z.literal("object-detection")
|
|
7032
|
+
}),
|
|
7033
|
+
base: zod.z.object({
|
|
7034
|
+
modelId: zod.z.string().min(1),
|
|
7035
|
+
/** Ultralytics weights id, e.g. `yolov9m.pt`. */
|
|
7036
|
+
weights: zod.z.string().min(1),
|
|
7037
|
+
imgsz: zod.z.number().int().positive()
|
|
7038
|
+
}),
|
|
7039
|
+
baseline: zod.z.object({
|
|
7040
|
+
modelId: zod.z.string().min(1),
|
|
7041
|
+
/** The baseline ONNX the holdout comparison runs; public mirror URL. */
|
|
7042
|
+
onnxUrl: zod.z.string().url(),
|
|
7043
|
+
imgsz: zod.z.number().int().positive(),
|
|
7044
|
+
floors: FloorsSchema
|
|
7045
|
+
}),
|
|
7046
|
+
dataset: zod.z.object({
|
|
7047
|
+
versionId: zod.z.string().min(1),
|
|
7048
|
+
annotationHash: zod.z.string(),
|
|
7049
|
+
/** sha256 of the uploaded archive; the trainer refuses a mismatch (exit 65). */
|
|
7050
|
+
sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
7051
|
+
}),
|
|
7052
|
+
split: zod.z.object({
|
|
7053
|
+
holdoutCameras: zod.z.array(zod.z.number().int()),
|
|
7054
|
+
/** When no camera is held out: the last N days of every camera. */
|
|
7055
|
+
holdoutLastDays: zod.z.number().int().nonnegative(),
|
|
7056
|
+
valFraction: zod.z.number().min(0).max(.5),
|
|
7057
|
+
seed: zod.z.number().int()
|
|
7058
|
+
}),
|
|
7059
|
+
hyper: zod.z.object({
|
|
7060
|
+
epochs: zod.z.number().int().positive(),
|
|
7061
|
+
batch: zod.z.number().int().positive(),
|
|
7062
|
+
lr0: zod.z.number().positive(),
|
|
7063
|
+
/** Layers frozen from the start of the backbone; 0 = none. */
|
|
7064
|
+
freeze: zod.z.number().int().nonnegative(),
|
|
7065
|
+
patience: zod.z.number().int().nonnegative(),
|
|
7066
|
+
seed: zod.z.number().int()
|
|
7067
|
+
}),
|
|
7068
|
+
replay: zod.z.object({
|
|
7069
|
+
source: zod.z.literal("coco2017"),
|
|
7070
|
+
count: zod.z.number().int().nonnegative()
|
|
7071
|
+
}),
|
|
7072
|
+
exports: zod.z.array(TrainingExportSchema).min(1),
|
|
7073
|
+
calibration: zod.z.object({ maxFrames: zod.z.number().int().positive() }),
|
|
7074
|
+
floors: zod.z.object({
|
|
7075
|
+
minSupport: zod.z.number().int().positive(),
|
|
7076
|
+
clamp: zod.z.number().min(0).max(1)
|
|
7077
|
+
}),
|
|
7078
|
+
int8Gate: zod.z.object({ maxMap50Drop: zod.z.number().min(0).max(1) }),
|
|
7079
|
+
catalog: zod.z.object({
|
|
7080
|
+
id: zod.z.string().regex(/^[a-z0-9][a-z0-9-]{2,80}$/),
|
|
7081
|
+
name: zod.z.string().min(1).max(160)
|
|
7082
|
+
})
|
|
7083
|
+
});
|
|
7084
|
+
/** One regular file of the bundle. An mlpackage contributes one row per member. */
|
|
7085
|
+
var TrainingResultFileSchema = zod.z.object({
|
|
7086
|
+
path: zod.z.string().min(1),
|
|
7087
|
+
bytes: zod.z.number().int().nonnegative(),
|
|
7088
|
+
sha256: zod.z.string().regex(/^[0-9a-f]{64}$/),
|
|
7089
|
+
format: zod.z.enum([
|
|
7090
|
+
"onnx",
|
|
7091
|
+
"openvino",
|
|
7092
|
+
"coreml",
|
|
7093
|
+
"pt"
|
|
7094
|
+
]),
|
|
7095
|
+
variant: zod.z.enum(["base", "int8"])
|
|
7096
|
+
});
|
|
7097
|
+
var DraftFormatSchema = zod.z.object({
|
|
7098
|
+
/** RELATIVE path inside the bundle; the hub rewrites it to `camstack-local://`. */
|
|
7099
|
+
url: zod.z.string().min(1),
|
|
7100
|
+
sizeMB: zod.z.number().nonnegative(),
|
|
7101
|
+
isDirectory: zod.z.boolean().optional(),
|
|
7102
|
+
files: zod.z.array(zod.z.string()).optional(),
|
|
7103
|
+
runtimes: zod.z.array(zod.z.literal("python")).optional(),
|
|
7104
|
+
revision: zod.z.string().min(1)
|
|
7105
|
+
});
|
|
7106
|
+
/** The trainer's half of a catalog entry; the hub completes it (labels, licence, group). */
|
|
7107
|
+
var TrainingCatalogDraftSchema = zod.z.object({
|
|
7108
|
+
id: zod.z.string().min(1),
|
|
7109
|
+
name: zod.z.string().min(1),
|
|
7110
|
+
description: zod.z.string(),
|
|
7111
|
+
labels: zod.z.array(zod.z.unknown()),
|
|
7112
|
+
inputSize: zod.z.object({
|
|
7113
|
+
width: zod.z.number().int().positive(),
|
|
7114
|
+
height: zod.z.number().int().positive()
|
|
7115
|
+
}),
|
|
7116
|
+
preprocessMode: zod.z.literal("letterbox"),
|
|
7117
|
+
defaultClassFloors: FloorsSchema,
|
|
7118
|
+
formats: zod.z.object({
|
|
7119
|
+
onnx: DraftFormatSchema.optional(),
|
|
7120
|
+
openvino: DraftFormatSchema.optional(),
|
|
7121
|
+
coreml: DraftFormatSchema.optional()
|
|
7122
|
+
})
|
|
7123
|
+
});
|
|
7124
|
+
/** One class of one model's holdout report (the eval harness's summary). */
|
|
7125
|
+
var TrainingEvalClassSchema = zod.z.object({
|
|
7126
|
+
positives: zod.z.number().int(),
|
|
7127
|
+
floor: zod.z.number().nullable(),
|
|
7128
|
+
tp: zod.z.number().int(),
|
|
7129
|
+
fp: zod.z.number().int(),
|
|
7130
|
+
fn: zod.z.number().int(),
|
|
7131
|
+
precision: zod.z.number().nullable(),
|
|
7132
|
+
recall: zod.z.number().nullable(),
|
|
7133
|
+
ap50: zod.z.number().nullable(),
|
|
7134
|
+
recallByCamera: zod.z.record(zod.z.string(), zod.z.number().nullable()).optional(),
|
|
7135
|
+
recallByLight: zod.z.record(zod.z.string(), zod.z.number().nullable()).optional(),
|
|
7136
|
+
fpByCamera: zod.z.record(zod.z.string(), zod.z.number()).optional(),
|
|
7137
|
+
smallFound: zod.z.number().int().optional(),
|
|
7138
|
+
smallTotal: zod.z.number().int().optional()
|
|
7139
|
+
}).passthrough();
|
|
7140
|
+
var TrainingEvalReportSchema = zod.z.object({
|
|
7141
|
+
frames: zod.z.number().int(),
|
|
7142
|
+
classes: zod.z.record(zod.z.string(), TrainingEvalClassSchema)
|
|
7143
|
+
}).passthrough();
|
|
7144
|
+
/**
|
|
7145
|
+
* A succeeded result carries every block; a failed one carries the error and
|
|
7146
|
+
* whatever was known by then — the other blocks are ABSENT, not null. The
|
|
7147
|
+
* orchestrator checks the blocks it needs before it trusts a success.
|
|
7148
|
+
*/
|
|
7149
|
+
var TrainingResultSchema = zod.z.object({
|
|
7150
|
+
schema: zod.z.literal(TRAINING_RESULT_SCHEMA),
|
|
7151
|
+
jobKey: zod.z.string(),
|
|
7152
|
+
status: zod.z.enum(["succeeded", "failed"]),
|
|
7153
|
+
error: zod.z.object({
|
|
7154
|
+
stage: zod.z.string(),
|
|
7155
|
+
code: zod.z.string(),
|
|
7156
|
+
message: zod.z.string()
|
|
7157
|
+
}).nullable(),
|
|
7158
|
+
trainer: zod.z.object({
|
|
7159
|
+
version: zod.z.string(),
|
|
7160
|
+
revision: zod.z.string()
|
|
7161
|
+
}),
|
|
7162
|
+
toolchain: zod.z.record(zod.z.string(), zod.z.string().nullable()),
|
|
7163
|
+
dataset: zod.z.object({
|
|
7164
|
+
versionId: zod.z.string(),
|
|
7165
|
+
annotationHash: zod.z.string(),
|
|
7166
|
+
sha256: zod.z.string(),
|
|
7167
|
+
frames: zod.z.object({
|
|
7168
|
+
train: zod.z.number().int(),
|
|
7169
|
+
val: zod.z.number().int(),
|
|
7170
|
+
holdout: zod.z.number().int()
|
|
7171
|
+
}),
|
|
7172
|
+
holdoutCameras: zod.z.array(zod.z.number().int()),
|
|
7173
|
+
replayImages: zod.z.number().int()
|
|
7174
|
+
}).optional(),
|
|
7175
|
+
training: zod.z.object({
|
|
7176
|
+
epochsRun: zod.z.number().int(),
|
|
7177
|
+
bestEpoch: zod.z.number().int(),
|
|
7178
|
+
bestMap50: zod.z.number(),
|
|
7179
|
+
bestMap50_95: zod.z.number(),
|
|
7180
|
+
seconds: zod.z.number()
|
|
7181
|
+
}).optional(),
|
|
7182
|
+
evaluation: zod.z.object({
|
|
7183
|
+
holdoutFrames: zod.z.number().int(),
|
|
7184
|
+
baseline: TrainingEvalReportSchema.nullable(),
|
|
7185
|
+
candidate: TrainingEvalReportSchema.nullable(),
|
|
7186
|
+
candidateInt8: TrainingEvalReportSchema.nullable(),
|
|
7187
|
+
recallDeltaByCamera: zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.number())).nullable(),
|
|
7188
|
+
map50: zod.z.record(zod.z.string(), zod.z.number().nullable())
|
|
7189
|
+
}).optional(),
|
|
7190
|
+
floors: zod.z.object({
|
|
7191
|
+
baseline: FloorsSchema,
|
|
7192
|
+
candidate: FloorsSchema,
|
|
7193
|
+
candidateInt8: FloorsSchema.nullable(),
|
|
7194
|
+
notes: zod.z.array(zod.z.string())
|
|
7195
|
+
}).optional(),
|
|
7196
|
+
int8: zod.z.object({
|
|
7197
|
+
kept: zod.z.boolean(),
|
|
7198
|
+
map50Fp16: zod.z.number().nullable(),
|
|
7199
|
+
map50Int8: zod.z.number().nullable(),
|
|
7200
|
+
reason: zod.z.string().nullable()
|
|
7201
|
+
}).optional(),
|
|
7202
|
+
exportsDropped: zod.z.array(zod.z.object({
|
|
7203
|
+
export: zod.z.string(),
|
|
7204
|
+
reason: zod.z.string()
|
|
7205
|
+
})).optional(),
|
|
7206
|
+
files: zod.z.array(TrainingResultFileSchema).optional(),
|
|
7207
|
+
bundle: zod.z.object({
|
|
7208
|
+
path: zod.z.literal("bundle.tar"),
|
|
7209
|
+
bytes: zod.z.number().int().nonnegative(),
|
|
7210
|
+
sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
7211
|
+
}).nullable().optional(),
|
|
7212
|
+
catalogDraft: zod.z.object({
|
|
7213
|
+
base: TrainingCatalogDraftSchema,
|
|
7214
|
+
int8: TrainingCatalogDraftSchema.nullable()
|
|
7215
|
+
}).nullable().optional(),
|
|
7216
|
+
licence: zod.z.string()
|
|
7217
|
+
});
|
|
7218
|
+
var TrainingStageSchema = zod.z.enum([
|
|
7219
|
+
"prepare",
|
|
7220
|
+
"replay",
|
|
7221
|
+
"train",
|
|
7222
|
+
"eval",
|
|
7223
|
+
"export",
|
|
7224
|
+
"package"
|
|
7225
|
+
]);
|
|
7226
|
+
var EventBase = {
|
|
7227
|
+
v: zod.z.literal(1),
|
|
7228
|
+
ts: zod.z.number()
|
|
7229
|
+
};
|
|
7230
|
+
zod.z.discriminatedUnion("type", [
|
|
7231
|
+
zod.z.object({
|
|
7232
|
+
...EventBase,
|
|
7233
|
+
type: zod.z.literal("stage"),
|
|
7234
|
+
stage: TrainingStageSchema
|
|
7235
|
+
}),
|
|
7236
|
+
zod.z.object({
|
|
7237
|
+
...EventBase,
|
|
7238
|
+
type: zod.z.literal("epoch"),
|
|
7239
|
+
epoch: zod.z.number().int(),
|
|
7240
|
+
epochs: zod.z.number().int(),
|
|
7241
|
+
boxLoss: zod.z.number().nullable().optional(),
|
|
7242
|
+
map50: zod.z.number().nullable().optional(),
|
|
7243
|
+
map50_95: zod.z.number().nullable().optional(),
|
|
7244
|
+
etaSec: zod.z.number().nullable().optional()
|
|
7245
|
+
}),
|
|
7246
|
+
zod.z.object({
|
|
7247
|
+
...EventBase,
|
|
7248
|
+
type: zod.z.literal("metric"),
|
|
7249
|
+
name: zod.z.string(),
|
|
7250
|
+
value: zod.z.number()
|
|
7251
|
+
}),
|
|
7252
|
+
zod.z.object({
|
|
7253
|
+
...EventBase,
|
|
7254
|
+
type: zod.z.literal("artifact"),
|
|
7255
|
+
path: zod.z.string(),
|
|
7256
|
+
bytes: zod.z.number(),
|
|
7257
|
+
sha256: zod.z.string()
|
|
7258
|
+
}),
|
|
7259
|
+
zod.z.object({
|
|
7260
|
+
...EventBase,
|
|
7261
|
+
type: zod.z.literal("done"),
|
|
7262
|
+
status: zod.z.enum(["succeeded", "failed"]),
|
|
7263
|
+
resultSha256: zod.z.string().nullable().optional()
|
|
7264
|
+
}),
|
|
7265
|
+
zod.z.object({
|
|
7266
|
+
...EventBase,
|
|
7267
|
+
type: zod.z.literal("error"),
|
|
7268
|
+
stage: zod.z.string(),
|
|
7269
|
+
code: zod.z.string(),
|
|
7270
|
+
message: zod.z.string()
|
|
7271
|
+
})
|
|
7272
|
+
]);
|
|
7273
|
+
/**
|
|
6655
7274
|
* What the gate decided about a birth, from the CALLER's point of view.
|
|
6656
7275
|
*
|
|
6657
7276
|
* Deliberately not `ConfirmationVerdict`: `undecided` is not a birth decision
|
|
@@ -9408,7 +10027,9 @@ var AddonHttpRouteSchema = zod.z.object({
|
|
|
9408
10027
|
"authenticated",
|
|
9409
10028
|
"admin"
|
|
9410
10029
|
]).optional(),
|
|
9411
|
-
description: zod.z.string().optional()
|
|
10030
|
+
description: zod.z.string().optional(),
|
|
10031
|
+
/** The hub limits this route per client address before the handler runs. */
|
|
10032
|
+
rateLimit: zod.z.enum(["auth"]).optional()
|
|
9412
10033
|
});
|
|
9413
10034
|
/**
|
|
9414
10035
|
* Cross-process route invocation envelope. The hub captures the
|
|
@@ -10372,6 +10993,37 @@ var BackupDestinationInfoSchema = zod.z.object({
|
|
|
10372
10993
|
lastError: zod.z.string().optional()
|
|
10373
10994
|
});
|
|
10374
10995
|
/**
|
|
10996
|
+
* How an archive is encrypted (operator decision 2026-10-09): to a backup
|
|
10997
|
+
* PUBLIC key the hub holds, so only the operator's secret key opens it.
|
|
10998
|
+
* Absent on a plain `.tar.gz` — every archive made before encryption, or
|
|
10999
|
+
* with it off — which restores without a key.
|
|
11000
|
+
*/
|
|
11001
|
+
var BackupArchiveEncryptionSchema = zod.z.object({
|
|
11002
|
+
/** `x25519-hkdf-aes256gcm-stream-v1`. */
|
|
11003
|
+
scheme: zod.z.string(),
|
|
11004
|
+
/** 16 hex — the backup key whose secret restores this archive. */
|
|
11005
|
+
keyId: zod.z.string()
|
|
11006
|
+
});
|
|
11007
|
+
/**
|
|
11008
|
+
* The backup encryption setting. The hub keeps PUBLIC keys only: the active
|
|
11009
|
+
* one new archives are encrypted to, and the retired ones older archives
|
|
11010
|
+
* still need. The secret key is shown once, at `setupEncryption`.
|
|
11011
|
+
*/
|
|
11012
|
+
var BackupEncryptionStatusSchema = zod.z.object({
|
|
11013
|
+
enabled: zod.z.boolean(),
|
|
11014
|
+
active: zod.z.object({
|
|
11015
|
+
keyId: zod.z.string(),
|
|
11016
|
+
publicKey: zod.z.string(),
|
|
11017
|
+
createdAt: zod.z.number()
|
|
11018
|
+
}).nullable(),
|
|
11019
|
+
/** Every key ever set up, newest first. */
|
|
11020
|
+
keys: zod.z.array(zod.z.object({
|
|
11021
|
+
keyId: zod.z.string(),
|
|
11022
|
+
createdAt: zod.z.number(),
|
|
11023
|
+
retiredAt: zod.z.number().nullable()
|
|
11024
|
+
})).readonly()
|
|
11025
|
+
});
|
|
11026
|
+
/**
|
|
10375
11027
|
* Per-archive entry returned by `backup.listArchives({ destinationId })`.
|
|
10376
11028
|
* Same shape the destination drill-in renders. Sourced from the per-
|
|
10377
11029
|
* location `manifests.json` (no per-archive tarball read required).
|
|
@@ -10383,7 +11035,9 @@ var BackupArchiveEntrySchema = zod.z.object({
|
|
|
10383
11035
|
sizeBytes: zod.z.number(),
|
|
10384
11036
|
label: zod.z.string().optional(),
|
|
10385
11037
|
/** Top-level locations included in the archive (db, addons, tls, …). */
|
|
10386
|
-
locations: zod.z.array(zod.z.string()).readonly()
|
|
11038
|
+
locations: zod.z.array(zod.z.string()).readonly(),
|
|
11039
|
+
/** Present when the archive is encrypted; restore then needs that key. */
|
|
11040
|
+
encryption: BackupArchiveEncryptionSchema.optional()
|
|
10387
11041
|
});
|
|
10388
11042
|
var BackupEntrySchema = zod.z.object({
|
|
10389
11043
|
id: zod.z.string(),
|
|
@@ -10392,7 +11046,8 @@ var BackupEntrySchema = zod.z.object({
|
|
|
10392
11046
|
label: zod.z.string().optional(),
|
|
10393
11047
|
createdAt: zod.z.number(),
|
|
10394
11048
|
sizeBytes: zod.z.number(),
|
|
10395
|
-
locations: zod.z.array(zod.z.string()).optional()
|
|
11049
|
+
locations: zod.z.array(zod.z.string()).optional(),
|
|
11050
|
+
encryption: BackupArchiveEncryptionSchema.optional()
|
|
10396
11051
|
});
|
|
10397
11052
|
var ArchiveEntrySchema = zod.z.object({
|
|
10398
11053
|
path: zod.z.string(),
|
|
@@ -10647,11 +11302,58 @@ var backupCapability = {
|
|
|
10647
11302
|
* this list and skips entries whose path doesn't start with
|
|
10648
11303
|
* any of them.
|
|
10649
11304
|
*/
|
|
10650
|
-
locations: zod.z.array(zod.z.string()).optional()
|
|
11305
|
+
locations: zod.z.array(zod.z.string()).optional(),
|
|
11306
|
+
/**
|
|
11307
|
+
* The backup SECRET key, for an encrypted archive (ignored for a plain
|
|
11308
|
+
* one). The archive is decrypted into the hub's private staging
|
|
11309
|
+
* before the restart; the key is not stored. A wrong or missing key
|
|
11310
|
+
* is refused before the archive is downloaded.
|
|
11311
|
+
*/
|
|
11312
|
+
backupSecretKey: zod.z.string().optional()
|
|
10651
11313
|
}), zod.z.void(), {
|
|
10652
11314
|
kind: "mutation",
|
|
10653
11315
|
auth: "admin"
|
|
10654
11316
|
}),
|
|
11317
|
+
/** The encryption setting — public keys only. */
|
|
11318
|
+
getEncryption: method(zod.z.void(), BackupEncryptionStatusSchema, { auth: "admin" }),
|
|
11319
|
+
/**
|
|
11320
|
+
* Turn encryption on, or replace the key: generates a new key pair, keeps
|
|
11321
|
+
* the PUBLIC key, and returns the SECRET key — once. It is never stored
|
|
11322
|
+
* on the hub and cannot be shown again; archives made from now on need
|
|
11323
|
+
* it to be restored. A previous key is retired: archives made with it
|
|
11324
|
+
* still need IT.
|
|
11325
|
+
*/
|
|
11326
|
+
setupEncryption: method(zod.z.object({}), zod.z.object({
|
|
11327
|
+
keyId: zod.z.string(),
|
|
11328
|
+
secretKey: zod.z.string(),
|
|
11329
|
+
publicKey: zod.z.string(),
|
|
11330
|
+
createdAt: zod.z.number(),
|
|
11331
|
+
replacedKeyId: zod.z.string().nullable()
|
|
11332
|
+
}), {
|
|
11333
|
+
kind: "mutation",
|
|
11334
|
+
auth: "admin"
|
|
11335
|
+
}),
|
|
11336
|
+
/** New archives are plain again. Existing encrypted archives keep needing their key. */
|
|
11337
|
+
disableEncryption: method(zod.z.object({}), zod.z.object({ disabledKeyId: zod.z.string().nullable() }), {
|
|
11338
|
+
kind: "mutation",
|
|
11339
|
+
auth: "admin"
|
|
11340
|
+
}),
|
|
11341
|
+
/**
|
|
11342
|
+
* Does this secret key parse, and which backup key is it? A mutation so
|
|
11343
|
+
* the key never travels in a URL. Nothing is stored.
|
|
11344
|
+
*/
|
|
11345
|
+
checkBackupKey: method(zod.z.object({ backupSecretKey: zod.z.string() }), zod.z.object({
|
|
11346
|
+
valid: zod.z.boolean(),
|
|
11347
|
+
keyId: zod.z.string().nullable(),
|
|
11348
|
+
/** The hub knows this key (active or retired). */
|
|
11349
|
+
known: zod.z.boolean(),
|
|
11350
|
+
/** It is the key new archives are encrypted to. */
|
|
11351
|
+
active: zod.z.boolean(),
|
|
11352
|
+
error: zod.z.string().optional()
|
|
11353
|
+
}), {
|
|
11354
|
+
kind: "mutation",
|
|
11355
|
+
auth: "admin"
|
|
11356
|
+
}),
|
|
10655
11357
|
delete: method(zod.z.object({
|
|
10656
11358
|
destinationId: zod.z.string(),
|
|
10657
11359
|
backupId: zod.z.string()
|
|
@@ -14162,22 +14864,87 @@ var CustomModelDescriptorSchema = zod.z.object({
|
|
|
14162
14864
|
stepId: zod.z.string(),
|
|
14163
14865
|
entry: ModelCatalogEntrySchema
|
|
14164
14866
|
});
|
|
14867
|
+
/** One regular file of an imported bundle, as the producer measured it. */
|
|
14868
|
+
var ModelBundleFileSchema = zod.z.object({
|
|
14869
|
+
/** Relative path inside the bundle tar; an mlpackage member is `<x>.mlpackage/<member>`. */
|
|
14870
|
+
path: zod.z.string().min(1),
|
|
14871
|
+
bytes: zod.z.number().int().nonnegative(),
|
|
14872
|
+
sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
14873
|
+
});
|
|
14874
|
+
var ImportModelBundleInputSchema = zod.z.object({
|
|
14875
|
+
/** The registry provider that keeps the model (Model Studio). */
|
|
14876
|
+
addonId: zod.z.string(),
|
|
14877
|
+
stepId: zod.z.string().min(1),
|
|
14878
|
+
/**
|
|
14879
|
+
* The catalog rows to register, complete, every format URL already
|
|
14880
|
+
* `camstack-local://<modelId>/<file>` naming a file of the bundle. One
|
|
14881
|
+
* bundle may carry several rows (a base and its INT8 variant).
|
|
14882
|
+
*/
|
|
14883
|
+
entries: zod.z.array(ModelCatalogEntrySchema).min(1).max(4),
|
|
14884
|
+
/** The bundle tar, offered by the producer over `ctx.peerBytes` (same host). */
|
|
14885
|
+
bundle: zod.z.object({
|
|
14886
|
+
ticket: PeerBytesTicketSchema,
|
|
14887
|
+
bytes: zod.z.number().int().positive(),
|
|
14888
|
+
sha256: zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
14889
|
+
}),
|
|
14890
|
+
/** Every file the tar must hold; anything missing, extra or different is refused. */
|
|
14891
|
+
files: zod.z.array(ModelBundleFileSchema).min(1).max(64),
|
|
14892
|
+
/** Convert missing engine formats and push the model to every agent afterwards. */
|
|
14893
|
+
distribute: zod.z.boolean()
|
|
14894
|
+
});
|
|
14895
|
+
var ImportModelBundleResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
|
|
14896
|
+
kind: zod.z.literal("imported"),
|
|
14897
|
+
modelIds: zod.z.array(zod.z.string()).readonly(),
|
|
14898
|
+
distributed: zod.z.array(zod.z.object({
|
|
14899
|
+
modelId: zod.z.string(),
|
|
14900
|
+
nodeId: zod.z.string(),
|
|
14901
|
+
format: zod.z.enum(MODEL_FORMATS),
|
|
14902
|
+
ok: zod.z.boolean(),
|
|
14903
|
+
error: zod.z.string().optional()
|
|
14904
|
+
})).readonly()
|
|
14905
|
+
}), zod.z.object({
|
|
14906
|
+
kind: zod.z.literal("refused"),
|
|
14907
|
+
reason: zod.z.enum([
|
|
14908
|
+
"transfer-failed",
|
|
14909
|
+
"checksum-mismatch",
|
|
14910
|
+
"bundle-mismatch",
|
|
14911
|
+
"invalid-entry",
|
|
14912
|
+
"id-taken"
|
|
14913
|
+
]),
|
|
14914
|
+
detail: zod.z.string()
|
|
14915
|
+
})]);
|
|
14165
14916
|
var customModelRegistryCapability = {
|
|
14166
14917
|
name: "custom-model-registry",
|
|
14167
14918
|
scope: "system",
|
|
14168
14919
|
mode: "collection",
|
|
14169
14920
|
internal: true,
|
|
14170
|
-
methods: {
|
|
14171
|
-
|
|
14172
|
-
|
|
14173
|
-
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
listModels: method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" })
|
|
14921
|
+
methods: {
|
|
14922
|
+
/**
|
|
14923
|
+
* `internal: true` did not gate the mount (see the 2026-08-26 note on
|
|
14924
|
+
* `data-store-provider`) — this was reachable on the AppRouter by ANY
|
|
14925
|
+
* authenticated session at the default `auth: 'protected'`. The only
|
|
14926
|
+
* consumer, the detection-pipeline's model resolution
|
|
14927
|
+
* (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
|
|
14928
|
+
* `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
|
|
14929
|
+
* never tRPC — so `auth: 'admin'` costs it nothing.
|
|
14930
|
+
*/
|
|
14931
|
+
listModels: method(zod.z.void(), zod.z.array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }),
|
|
14932
|
+
/**
|
|
14933
|
+
* Register a model whose files arrive as ONE bundle tar (D806): the tar
|
|
14934
|
+
* is taken over `ctx.peerBytes`, checked against `bundle.sha256` and
|
|
14935
|
+
* every row of `files`, unpacked into the hub's flat modelsDir (directory
|
|
14936
|
+
* formats included), each format's `.revision` record written, the rows
|
|
14937
|
+
* upserted under the operator's name, the hub marked available and —
|
|
14938
|
+
* when asked — engine formats converted and the model distributed. A
|
|
14939
|
+
* model id already registered by another source is refused, never
|
|
14940
|
+
* overwritten.
|
|
14941
|
+
*/
|
|
14942
|
+
importModelBundle: method(ImportModelBundleInputSchema, ImportModelBundleResultSchema, {
|
|
14943
|
+
kind: "mutation",
|
|
14944
|
+
auth: "admin",
|
|
14945
|
+
timeoutMs: 30 * 6e4
|
|
14946
|
+
})
|
|
14947
|
+
}
|
|
14181
14948
|
};
|
|
14182
14949
|
/**
|
|
14183
14950
|
* Query filter for settings-store collections.
|
|
@@ -14984,6 +15751,42 @@ var KeychainKeySourceSchema = zod.z.object({
|
|
|
14984
15751
|
*/
|
|
14985
15752
|
sharesBackupWithDatabase: zod.z.boolean()
|
|
14986
15753
|
});
|
|
15754
|
+
/**
|
|
15755
|
+
* Who reset the keychain: an operator from System → Secrets (`user:<id>`), or
|
|
15756
|
+
* the one-shot `<dataDir>/.keychain/RESET_KEYCHAIN` trigger file at boot
|
|
15757
|
+
* (`trigger-file`) — the only way back into a hub whose master key is lost,
|
|
15758
|
+
* because a locked keychain leaves no session that could reach the page (D797).
|
|
15759
|
+
*/
|
|
15760
|
+
var KEYCHAIN_RESET_TRIGGER_ACTOR = "trigger-file";
|
|
15761
|
+
/**
|
|
15762
|
+
* One keychain reset, as the Secrets page reports it until the operator
|
|
15763
|
+
* acknowledges it. Never a value: refs, counts and table names only.
|
|
15764
|
+
*/
|
|
15765
|
+
var KeychainResetRecordSchema = zod.z.object({
|
|
15766
|
+
id: zod.z.string(),
|
|
15767
|
+
at: zod.z.number(),
|
|
15768
|
+
/** `user:<id>` or {@link KEYCHAIN_RESET_TRIGGER_ACTOR}. */
|
|
15769
|
+
actor: zod.z.string(),
|
|
15770
|
+
/** Why the keychain was locked when it was reset; `null` when it was not locked. */
|
|
15771
|
+
lockedReason: zod.z.string().nullable(),
|
|
15772
|
+
/** Values moved into the archive table (ciphertext under the lost key). */
|
|
15773
|
+
archived: zod.z.number().int(),
|
|
15774
|
+
/** `keychain:secrets-orphaned-<ts>`, or `null` when nothing was stored. */
|
|
15775
|
+
archivedCollection: zod.z.string().nullable(),
|
|
15776
|
+
/**
|
|
15777
|
+
* `keychain:keys-orphaned-<ts>`: the wrapped data keys of the archive, kept
|
|
15778
|
+
* so the archive can still be opened if the lost master key turns up.
|
|
15779
|
+
*/
|
|
15780
|
+
archivedKeysCollection: zod.z.string().nullable(),
|
|
15781
|
+
/**
|
|
15782
|
+
* `system-settings` keys whose marker the reset cleared — the cluster
|
|
15783
|
+
* secret's: a marker left pointing at an archived value would refuse every
|
|
15784
|
+
* agent forever (D797).
|
|
15785
|
+
*/
|
|
15786
|
+
clearedSettingsKeys: zod.z.array(zod.z.string()).readonly(),
|
|
15787
|
+
/** When an operator dismissed the notice on the Secrets page; `null` until then. */
|
|
15788
|
+
acknowledgedAt: zod.z.number().nullable()
|
|
15789
|
+
});
|
|
14987
15790
|
var KeychainStatusSchema = zod.z.object({
|
|
14988
15791
|
keychain: KeychainStateSchema,
|
|
14989
15792
|
/** The source of the master key in use; `null` while locked. */
|
|
@@ -15001,7 +15804,9 @@ var KeychainStatusSchema = zod.z.object({
|
|
|
15001
15804
|
/** Wrapped copies of the data key under a previous master key, kept until purged. */
|
|
15002
15805
|
retiredWraps: zod.z.number().int(),
|
|
15003
15806
|
recoveryKeyDownloadedAt: zod.z.number().nullable(),
|
|
15004
|
-
secretCount: zod.z.number().int()
|
|
15807
|
+
secretCount: zod.z.number().int(),
|
|
15808
|
+
/** The most recent reset, acknowledged or not; `null` when the keychain was never reset. */
|
|
15809
|
+
lastReset: KeychainResetRecordSchema.nullable()
|
|
15005
15810
|
});
|
|
15006
15811
|
/**
|
|
15007
15812
|
* Whether a stored value is still pointed at by its owner's settings.
|
|
@@ -15023,13 +15828,14 @@ var KeychainAuditActionSchema = zod.z.enum([
|
|
|
15023
15828
|
"download-recovery-key",
|
|
15024
15829
|
"delete-orphans",
|
|
15025
15830
|
"reset",
|
|
15831
|
+
"acknowledge-reset",
|
|
15026
15832
|
"initialize"
|
|
15027
15833
|
]);
|
|
15028
15834
|
var KeychainAuditEntrySchema = zod.z.object({
|
|
15029
15835
|
id: zod.z.string(),
|
|
15030
15836
|
at: zod.z.number(),
|
|
15031
15837
|
action: KeychainAuditActionSchema,
|
|
15032
|
-
/** `addon:<id>` | `user:<id>` | `system`. */
|
|
15838
|
+
/** `addon:<id>` | `user:<id>` | `system` | `trigger-file`. */
|
|
15033
15839
|
actor: zod.z.string(),
|
|
15034
15840
|
ref: zod.z.string().nullable(),
|
|
15035
15841
|
detail: zod.z.string().nullable()
|
|
@@ -15139,6 +15945,21 @@ var keychainAdminCapability = {
|
|
|
15139
15945
|
kind: "mutation",
|
|
15140
15946
|
auth: "admin",
|
|
15141
15947
|
caller: "required"
|
|
15948
|
+
}),
|
|
15949
|
+
/**
|
|
15950
|
+
* What the last reset left to re-enter: the archived refs (owner, scope,
|
|
15951
|
+
* field — never a value) that hold no value again yet. Process-held refs
|
|
15952
|
+
* the hub regenerates itself (the session signing key) are not listed.
|
|
15953
|
+
*/
|
|
15954
|
+
resetReport: method(zod.z.object({}), zod.z.object({
|
|
15955
|
+
reset: KeychainResetRecordSchema.nullable(),
|
|
15956
|
+
pendingReentry: zod.z.array(KeychainSecretMetaSchema).readonly()
|
|
15957
|
+
}), { auth: "admin" }),
|
|
15958
|
+
/** Dismiss the "keychain was reset" notice. Audited. */
|
|
15959
|
+
acknowledgeReset: method(zod.z.object({}), zod.z.object({ acknowledged: zod.z.boolean() }), {
|
|
15960
|
+
kind: "mutation",
|
|
15961
|
+
auth: "admin",
|
|
15962
|
+
caller: "required"
|
|
15142
15963
|
})
|
|
15143
15964
|
}
|
|
15144
15965
|
};
|
|
@@ -18395,8 +19216,8 @@ var PUBLIC_ENDPOINT_HTTP_REASON = "The public endpoint needs an https:// address
|
|
|
18395
19216
|
* May `baseUrl` carry `role`? The address check above, plus two role rules:
|
|
18396
19217
|
* the app link carries `notificationTaps` only (it is not a hub address), and
|
|
18397
19218
|
* a plain-http address carries `publicEndpoint` only when the operator
|
|
18398
|
-
* explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password
|
|
18399
|
-
*
|
|
19219
|
+
* explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password
|
|
19220
|
+
* sign-in works over http, a passkey or an OIDC callback does not.
|
|
18400
19221
|
*/
|
|
18401
19222
|
function checkRoleAddress(role, baseUrl, options = {}) {
|
|
18402
19223
|
if (isAppLinkBase(baseUrl)) return role === "notificationTaps" ? {
|
|
@@ -19426,7 +20247,7 @@ var logDestinationCapability = {
|
|
|
19426
20247
|
* `login-method` — collection cap through which auth addons contribute
|
|
19427
20248
|
* their pre-auth login surfaces to the login page. This is the SINGLE,
|
|
19428
20249
|
* generic mechanism that supersedes the dead `auth.listProviders` reader:
|
|
19429
|
-
* every auth addon (OIDC,
|
|
20250
|
+
* every auth addon (OIDC, WebAuthn/passkey) registers a
|
|
19430
20251
|
* `login-method` provider and the PUBLIC `auth.listLoginMethods`
|
|
19431
20252
|
* procedure aggregates them for the unauthenticated login page.
|
|
19432
20253
|
*
|
|
@@ -19434,8 +20255,7 @@ var logDestinationCapability = {
|
|
|
19434
20255
|
*
|
|
19435
20256
|
* - `redirect` — a declarative button. The login page renders a generic
|
|
19436
20257
|
* button that navigates to `startUrl` (an addon-owned HTTP route).
|
|
19437
|
-
* Covers OIDC (`/addon/auth-oidc/<id>/start`)
|
|
19438
|
-
* ZERO shell-side JS. A future SSO addon plugs in the same way — the
|
|
20258
|
+
* Covers OIDC (`/addon/auth-oidc/<id>/start`) with ZERO shell-side JS. A future SSO addon plugs in the same way — the
|
|
19439
20259
|
* login page needs NO change.
|
|
19440
20260
|
*
|
|
19441
20261
|
* - `widget` — a Module-Federation widget the login page mounts (via
|
|
@@ -19454,8 +20274,8 @@ var logDestinationCapability = {
|
|
|
19454
20274
|
* decision.
|
|
19455
20275
|
*
|
|
19456
20276
|
* Every contribution carries a `stage`:
|
|
19457
|
-
* - `primary` — shown on the first credentials screen (OIDC
|
|
19458
|
-
*
|
|
20277
|
+
* - `primary` — shown on the first credentials screen (OIDC
|
|
20278
|
+
* buttons; a usernameless passkey).
|
|
19459
20279
|
* - `second-factor` — shown AFTER the password leg, gated on the
|
|
19460
20280
|
* returned `factors` (passkey-as-2FA today).
|
|
19461
20281
|
*
|
|
@@ -19467,7 +20287,7 @@ var logDestinationCapability = {
|
|
|
19467
20287
|
var LoginStageEnum = zod.z.enum(["primary", "second-factor"]);
|
|
19468
20288
|
/**
|
|
19469
20289
|
* A declarative redirect button — the login page navigates to `startUrl`.
|
|
19470
|
-
* OIDC
|
|
20290
|
+
* OIDC contributes this; a future SSO addon does too.
|
|
19471
20291
|
*/
|
|
19472
20292
|
var RedirectLoginMethodSchema = zod.z.object({
|
|
19473
20293
|
kind: zod.z.literal("redirect"),
|
|
@@ -19484,7 +20304,7 @@ var RedirectLoginMethodSchema = zod.z.object({
|
|
|
19484
20304
|
* The provider names an external identity a signed-in user can link to
|
|
19485
20305
|
* their own account (D782): `auth.listLinkableProviders` /
|
|
19486
20306
|
* `beginExternalLink` offer exactly the redirect methods that set this,
|
|
19487
|
-
* whatever their id. Absent / false — a login button only
|
|
20307
|
+
* whatever their id. Absent / false — a login button only.
|
|
19488
20308
|
*/
|
|
19489
20309
|
linkable: zod.z.boolean().optional()
|
|
19490
20310
|
});
|
|
@@ -23755,8 +24575,7 @@ var notificationRulesCapability = {
|
|
|
23755
24575
|
* `super_admin` — collapsed onto the single boolean because every
|
|
23756
24576
|
* non-admin caller is governed by the same scope-check rules; the
|
|
23757
24577
|
* distinction was only ever used to route role-specific procedures
|
|
23758
|
-
* which we no longer have.
|
|
23759
|
-
* `createServiceToken`; CLI / webhook integrations are `cst_*` tokens
|
|
24578
|
+
* which we no longer have. CLI / webhook integrations are `cst_*` tokens
|
|
23760
24579
|
* with `isAdmin: false` + an explicit scope list.
|
|
23761
24580
|
*/
|
|
23762
24581
|
/** Per-method access flavour. Materialised on every cap method via codegen. */
|
|
@@ -27037,6 +27856,26 @@ var pipelineAnalyticsCapability = {
|
|
|
27037
27856
|
auth: "admin",
|
|
27038
27857
|
timeoutMs: 60 * 6e4
|
|
27039
27858
|
}),
|
|
27859
|
+
/**
|
|
27860
|
+
* OFFER the annotated archive instead of pushing it (D805): the same tar
|
|
27861
|
+
* `writeRetrainArchive` streams, behind a one-shot `ctx.peerBytes` ticket
|
|
27862
|
+
* the caller dials at once (same host). For a consumer that cannot expose
|
|
27863
|
+
* an upload URL — a training provider streaming straight into its cloud.
|
|
27864
|
+
* The answer is the plan; the bytes and their sha256 are the consumer's
|
|
27865
|
+
* to measure as they arrive.
|
|
27866
|
+
*/
|
|
27867
|
+
offerRetrainArchive: method(zod.z.object({
|
|
27868
|
+
filter: RetrainExportFilterSchema,
|
|
27869
|
+
layout: RetrainExportLayoutSchema
|
|
27870
|
+
}), zod.z.object({
|
|
27871
|
+
ticket: PeerBytesTicketSchema,
|
|
27872
|
+
frames: zod.z.number().int(),
|
|
27873
|
+
annotations: zod.z.number().int()
|
|
27874
|
+
}), {
|
|
27875
|
+
kind: "mutation",
|
|
27876
|
+
auth: "admin",
|
|
27877
|
+
timeoutMs: 12e4
|
|
27878
|
+
}),
|
|
27040
27879
|
/** The counts a selection would freeze — class × camera × day/night,
|
|
27041
27880
|
* completeness — without freezing anything. Index reads only. */
|
|
27042
27881
|
previewRetrainDataset: method(zod.z.object({
|
|
@@ -31783,8 +32622,8 @@ version: zod.z.string().optional() }), ServerUpdateActionResultSchema, {
|
|
|
31783
32622
|
* `smtp-provider` — pluggable email delivery surface.
|
|
31784
32623
|
*
|
|
31785
32624
|
* Collection cap: a deployment may install multiple SMTP relays (e.g.
|
|
31786
|
-
* one for transactional / one for notifications). Consumers (
|
|
31787
|
-
*
|
|
32625
|
+
* one for transactional / one for notifications). Consumers (notifier
|
|
32626
|
+
* addons, …) walk the collection and pick the first
|
|
31788
32627
|
* ready provider OR target a specific addonId.
|
|
31789
32628
|
*
|
|
31790
32629
|
* The shape stays small on purpose — `sendEmail` is enough for the
|
|
@@ -32080,7 +32919,7 @@ targets: zod.z.array(zod.z.object({
|
|
|
32080
32919
|
};
|
|
32081
32920
|
/**
|
|
32082
32921
|
* `sso-bridge` — internal hub-only cap that lets SSO-style auth
|
|
32083
|
-
* providers (OIDC, SAML,
|
|
32922
|
+
* providers (OIDC, SAML, …) mint an HMAC-signed token
|
|
32084
32923
|
* carrying the post-callback claims before redirecting back to
|
|
32085
32924
|
* `/api/auth/sso/finish`.
|
|
32086
32925
|
*
|
|
@@ -32143,7 +32982,14 @@ var SsoBridgeClaimsSchema = zod.z.object({
|
|
|
32143
32982
|
* Verified `iss` of the id_token behind an OIDC login bridge (D782). A
|
|
32144
32983
|
* subject is unique only per issuer, so a link is bound to it.
|
|
32145
32984
|
*/
|
|
32146
|
-
issuer: zod.z.string().optional()
|
|
32985
|
+
issuer: zod.z.string().optional(),
|
|
32986
|
+
/**
|
|
32987
|
+
* SHA-256 (hex) of the `camstack_sso_state` cookie the IdP `/start` set in
|
|
32988
|
+
* the browser that began this sign-in. `/api/auth/sso/finish` requires the
|
|
32989
|
+
* request to present the matching cookie — a login bridge is useless in
|
|
32990
|
+
* any other browser.
|
|
32991
|
+
*/
|
|
32992
|
+
browserBinding: zod.z.string().optional()
|
|
32147
32993
|
});
|
|
32148
32994
|
var ssoBridgeCapability = {
|
|
32149
32995
|
name: "sso-bridge",
|
|
@@ -32161,7 +33007,7 @@ var ssoBridgeCapability = {
|
|
|
32161
33007
|
* `/api/auth/sso/finish` to become admin — a full privilege escalation
|
|
32162
33008
|
* through the exact surface this cap exists to close (see the module
|
|
32163
33009
|
* docblock). Every real caller (`oauth-grants.ts`, `main.ts`
|
|
32164
|
-
* registration, the OIDC
|
|
33010
|
+
* registration, the OIDC addon) reaches this through
|
|
32165
33011
|
* `ctx.api` / the in-process registry — never through tRPC — so
|
|
32166
33012
|
* `auth: 'admin'` costs those callers nothing.
|
|
32167
33013
|
*/
|
|
@@ -46957,16 +47803,6 @@ var ApiKeySummarySchema = zod.z.object({
|
|
|
46957
47803
|
createdAt: zod.z.number(),
|
|
46958
47804
|
lastUsedAt: zod.z.number().optional()
|
|
46959
47805
|
});
|
|
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
47806
|
var ScopedTokenSummarySchema = zod.z.object({
|
|
46971
47807
|
id: zod.z.string(),
|
|
46972
47808
|
userId: zod.z.string(),
|
|
@@ -47078,20 +47914,11 @@ var userManagementCapability = {
|
|
|
47078
47914
|
access: "view"
|
|
47079
47915
|
}),
|
|
47080
47916
|
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
47917
|
revokeApiKey: method(zod.z.object({ id: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
|
|
47087
47918
|
kind: "mutation",
|
|
47088
47919
|
auth: "admin",
|
|
47089
47920
|
access: "delete"
|
|
47090
47921
|
}),
|
|
47091
|
-
validateApiKey: method(zod.z.object({ token: zod.z.string() }), ApiKeySummarySchema.nullable(), {
|
|
47092
|
-
kind: "mutation",
|
|
47093
|
-
access: "view"
|
|
47094
|
-
}),
|
|
47095
47922
|
createScopedToken: method(CreateScopedTokenInputSchema, CreateScopedTokenResultSchema, {
|
|
47096
47923
|
kind: "mutation",
|
|
47097
47924
|
auth: "admin",
|
|
@@ -47181,20 +48008,6 @@ var userManagementCapability = {
|
|
|
47181
48008
|
userId: zod.z.string(),
|
|
47182
48009
|
issuer: zod.z.string()
|
|
47183
48010
|
}).nullable(), { auth: "admin" }),
|
|
47184
|
-
/**
|
|
47185
|
-
* Link `(provider, subject)` to `userId`. Idempotent for the same user;
|
|
47186
|
-
* REFUSES (throws) an identity already linked to another user, and a
|
|
47187
|
-
* user that does not exist.
|
|
47188
|
-
*/
|
|
47189
|
-
linkExternalIdentity: method(ExternalIdentityKeySchema.extend({
|
|
47190
|
-
userId: 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
48011
|
/** Remove one link of `userId`. `success: false` when it was not linked to that user. */
|
|
47199
48012
|
unlinkExternalIdentity: method(ExternalIdentityKeySchema.extend({ userId: zod.z.string() }), zod.z.object({ success: zod.z.boolean() }), {
|
|
47200
48013
|
kind: "mutation",
|
|
@@ -47923,6 +48736,531 @@ var zoneRulesCapability = {
|
|
|
47923
48736
|
*/
|
|
47924
48737
|
durability: "restored"
|
|
47925
48738
|
};
|
|
48739
|
+
/**
|
|
48740
|
+
* `training-provider` — ONE cloud that can run the CamStack trainer.
|
|
48741
|
+
*
|
|
48742
|
+
* A thin, mockable translation layer: it knows how to run a container with a
|
|
48743
|
+
* GPU, move a dataset in, read the job's stdout and hand artefacts back. It
|
|
48744
|
+
* knows nothing about datasets, splits, floors, cost policy or Model Studio —
|
|
48745
|
+
* those are the `training` orchestrator's (D805). Every rule written there
|
|
48746
|
+
* once is what makes a second provider (RunPod, a local NVIDIA node) a new
|
|
48747
|
+
* manifest entry and nothing else.
|
|
48748
|
+
*
|
|
48749
|
+
* Collection, routed by the `addonId` in every single-row input (the `llm` /
|
|
48750
|
+
* `notification-output` posture); `describe` concat-fans across providers.
|
|
48751
|
+
* Every method is `admin`: these spend money.
|
|
48752
|
+
*
|
|
48753
|
+
* Every cloud object a provider creates is tagged with the orchestrator's
|
|
48754
|
+
* `jobKey`, so a retried submit finds the job it already started instead of
|
|
48755
|
+
* starting a second GPU, and `listJobs` can find orphans.
|
|
48756
|
+
*
|
|
48757
|
+
* Bytes never ride a cap field: the dataset arrives as a `ctx.peerBytes`
|
|
48758
|
+
* ticket the producer (pipeline-analytics) minted, and artefacts leave as a
|
|
48759
|
+
* ticket the provider mints (D613).
|
|
48760
|
+
*/
|
|
48761
|
+
var TrainingGpuOptionSchema = zod.z.object({
|
|
48762
|
+
id: zod.z.string(),
|
|
48763
|
+
label: zod.z.string(),
|
|
48764
|
+
/** List price, USD per GPU-hour, before any region multiplier. */
|
|
48765
|
+
usdPerHour: zod.z.number().nonnegative(),
|
|
48766
|
+
vramGb: zod.z.number().nonnegative()
|
|
48767
|
+
});
|
|
48768
|
+
var TrainingRegionOptionSchema = zod.z.object({
|
|
48769
|
+
/** `''` = any region (the provider's choice). */
|
|
48770
|
+
id: zod.z.string(),
|
|
48771
|
+
label: zod.z.string(),
|
|
48772
|
+
priceMultiplier: zod.z.number().positive()
|
|
48773
|
+
});
|
|
48774
|
+
/**
|
|
48775
|
+
* The provider's per-account settings, as the orchestrator reads them. They
|
|
48776
|
+
* live in the PROVIDER addon's settings (operator decision 2026-10-08: they
|
|
48777
|
+
* depend on the user's account), never hard-coded in the orchestrator.
|
|
48778
|
+
*/
|
|
48779
|
+
var TrainingProviderDefaultsSchema = zod.z.object({
|
|
48780
|
+
gpuType: zod.z.string(),
|
|
48781
|
+
region: zod.z.string(),
|
|
48782
|
+
costCapUsd: zod.z.number().nonnegative(),
|
|
48783
|
+
maxRuntimeSec: zod.z.number().int().positive(),
|
|
48784
|
+
/** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
|
|
48785
|
+
failedDatasetGraceHours: zod.z.number().nonnegative()
|
|
48786
|
+
});
|
|
48787
|
+
var TrainingProviderDescriptorSchema = zod.z.object({
|
|
48788
|
+
addonId: zod.z.string(),
|
|
48789
|
+
/** `modal`, `runpod`, `fake`, … */
|
|
48790
|
+
kind: zod.z.string(),
|
|
48791
|
+
label: zod.z.string(),
|
|
48792
|
+
gpuTypes: zod.z.array(TrainingGpuOptionSchema).readonly(),
|
|
48793
|
+
regions: zod.z.array(TrainingRegionOptionSchema).readonly(),
|
|
48794
|
+
maxDatasetBytes: zod.z.number().int().positive(),
|
|
48795
|
+
maxRuntimeSec: zod.z.number().int().positive(),
|
|
48796
|
+
freeTierNote: zod.z.string().optional(),
|
|
48797
|
+
/** Where the data goes, in one sentence the confirm dialog repeats. */
|
|
48798
|
+
dataResidencyNote: zod.z.string(),
|
|
48799
|
+
/** Credentials are present — never the values. */
|
|
48800
|
+
configured: zod.z.boolean(),
|
|
48801
|
+
defaults: TrainingProviderDefaultsSchema
|
|
48802
|
+
});
|
|
48803
|
+
var TrainingCredentialCheckSchema = zod.z.object({
|
|
48804
|
+
ok: zod.z.boolean(),
|
|
48805
|
+
/** Workspace / account name, when the provider says. */
|
|
48806
|
+
account: zod.z.string().optional(),
|
|
48807
|
+
reason: zod.z.enum([
|
|
48808
|
+
"missing",
|
|
48809
|
+
"invalid",
|
|
48810
|
+
"forbidden",
|
|
48811
|
+
"network",
|
|
48812
|
+
"quota"
|
|
48813
|
+
]).optional(),
|
|
48814
|
+
detail: zod.z.string().optional()
|
|
48815
|
+
});
|
|
48816
|
+
var ProviderJobStateSchema = zod.z.enum([
|
|
48817
|
+
"pending",
|
|
48818
|
+
"starting",
|
|
48819
|
+
"running",
|
|
48820
|
+
"succeeded",
|
|
48821
|
+
"failed",
|
|
48822
|
+
"cancelled",
|
|
48823
|
+
"lost"
|
|
48824
|
+
]);
|
|
48825
|
+
var ProviderJobStatusSchema = zod.z.object({
|
|
48826
|
+
jobKey: zod.z.string(),
|
|
48827
|
+
providerJobId: zod.z.string().optional(),
|
|
48828
|
+
state: ProviderJobStateSchema,
|
|
48829
|
+
startedAt: zod.z.number().optional(),
|
|
48830
|
+
endedAt: zod.z.number().optional(),
|
|
48831
|
+
exitCode: zod.z.number().int().optional(),
|
|
48832
|
+
failureReason: zod.z.string().optional(),
|
|
48833
|
+
gpuType: zod.z.string(),
|
|
48834
|
+
region: zod.z.string().optional(),
|
|
48835
|
+
billedSeconds: zod.z.number().nonnegative(),
|
|
48836
|
+
costUsd: zod.z.number().nonnegative(),
|
|
48837
|
+
/** True when `costUsd` is runtime × list price, not a billing answer. */
|
|
48838
|
+
costIsEstimate: zod.z.boolean(),
|
|
48839
|
+
lastFetchedAt: zod.z.number()
|
|
48840
|
+
});
|
|
48841
|
+
/**
|
|
48842
|
+
* The trainer the job runs, by SOURCE revision: the provider turns it into an
|
|
48843
|
+
* image its own way (Modal builds the published Dockerfile at that revision;
|
|
48844
|
+
* a registry-pull provider maps it to a tag). Recorded on the job and echoed
|
|
48845
|
+
* in `result.json`.
|
|
48846
|
+
*/
|
|
48847
|
+
var TrainerSourceRefSchema = zod.z.object({
|
|
48848
|
+
/** Hugging Face repo id, e.g. `camstack/camstack-trainer`. */
|
|
48849
|
+
repo: zod.z.string().min(1),
|
|
48850
|
+
/** A commit sha (never a branch) — the job must be reproducible. */
|
|
48851
|
+
revision: zod.z.string().regex(/^[0-9a-f]{7,64}$/)
|
|
48852
|
+
});
|
|
48853
|
+
var JobRef = {
|
|
48854
|
+
addonId: zod.z.string(),
|
|
48855
|
+
jobKey: zod.z.string().uuid()
|
|
48856
|
+
};
|
|
48857
|
+
var TrainingLogLineSchema = zod.z.object({
|
|
48858
|
+
ts: zod.z.number(),
|
|
48859
|
+
text: zod.z.string()
|
|
48860
|
+
});
|
|
48861
|
+
var trainingProviderCapability = {
|
|
48862
|
+
name: "training-provider",
|
|
48863
|
+
scope: "system",
|
|
48864
|
+
mode: "collection",
|
|
48865
|
+
internal: true,
|
|
48866
|
+
methods: {
|
|
48867
|
+
/** Every provider's offer and settings. Concat-fans across providers. */
|
|
48868
|
+
describe: method(zod.z.object({}), zod.z.array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }),
|
|
48869
|
+
/** Call the provider with the stored credentials. Never returns them. */
|
|
48870
|
+
validateCredentials: method(zod.z.object({ addonId: zod.z.string() }), TrainingCredentialCheckSchema, {
|
|
48871
|
+
kind: "mutation",
|
|
48872
|
+
auth: "admin",
|
|
48873
|
+
timeoutMs: 3e4
|
|
48874
|
+
}),
|
|
48875
|
+
/** USD range for a run of `gpuSeconds` on `gpuType` in `region`. */
|
|
48876
|
+
estimateJob: method(zod.z.object({
|
|
48877
|
+
addonId: zod.z.string(),
|
|
48878
|
+
gpuType: zod.z.string(),
|
|
48879
|
+
region: zod.z.string(),
|
|
48880
|
+
datasetBytes: zod.z.number().int().nonnegative(),
|
|
48881
|
+
gpuSecondsLow: zod.z.number().nonnegative(),
|
|
48882
|
+
gpuSecondsHigh: zod.z.number().nonnegative()
|
|
48883
|
+
}), zod.z.object({
|
|
48884
|
+
usdLow: zod.z.number().nonnegative(),
|
|
48885
|
+
usdHigh: zod.z.number().nonnegative(),
|
|
48886
|
+
notes: zod.z.array(zod.z.string()).readonly()
|
|
48887
|
+
}), { auth: "admin" }),
|
|
48888
|
+
/**
|
|
48889
|
+
* Take the dataset archive behind `ticket` (minted by pipeline-analytics,
|
|
48890
|
+
* same host) and land it as the job's dataset in the cloud, streamed —
|
|
48891
|
+
* nothing is staged on the hub's disk. Idempotent per `jobKey`: a second
|
|
48892
|
+
* call replaces a partial upload. Answers what actually landed.
|
|
48893
|
+
*/
|
|
48894
|
+
uploadDataset: method(zod.z.object({
|
|
48895
|
+
...JobRef,
|
|
48896
|
+
ticket: PeerBytesTicketSchema
|
|
48897
|
+
}), zod.z.object({
|
|
48898
|
+
bytes: zod.z.number().int().nonnegative(),
|
|
48899
|
+
sha256: zod.z.string()
|
|
48900
|
+
}), {
|
|
48901
|
+
kind: "mutation",
|
|
48902
|
+
auth: "admin",
|
|
48903
|
+
timeoutMs: 120 * 6e4
|
|
48904
|
+
}),
|
|
48905
|
+
/**
|
|
48906
|
+
* Start the trainer on a GPU. Looks the job up by `jobKey` first, so a
|
|
48907
|
+
* retried submit never starts a second GPU. `jobSpecJson` is written next
|
|
48908
|
+
* to the dataset as `job.json`; `maxRuntimeSec` is enforced by the
|
|
48909
|
+
* provider (a hard timeout), not by the hub.
|
|
48910
|
+
*/
|
|
48911
|
+
submitJob: method(zod.z.object({
|
|
48912
|
+
...JobRef,
|
|
48913
|
+
trainer: TrainerSourceRefSchema,
|
|
48914
|
+
jobSpecJson: zod.z.string().min(2).max(64e3),
|
|
48915
|
+
gpuType: zod.z.string(),
|
|
48916
|
+
region: zod.z.string(),
|
|
48917
|
+
maxRuntimeSec: zod.z.number().int().positive()
|
|
48918
|
+
}), ProviderJobStatusSchema, {
|
|
48919
|
+
kind: "mutation",
|
|
48920
|
+
auth: "admin",
|
|
48921
|
+
timeoutMs: 30 * 6e4
|
|
48922
|
+
}),
|
|
48923
|
+
getJob: method(zod.z.object(JobRef), ProviderJobStatusSchema, {
|
|
48924
|
+
auth: "admin",
|
|
48925
|
+
timeoutMs: 6e4
|
|
48926
|
+
}),
|
|
48927
|
+
/** Stdout/stderr since `cursor` (opaque), oldest first, at most `max` lines. */
|
|
48928
|
+
listJobEvents: method(zod.z.object({
|
|
48929
|
+
...JobRef,
|
|
48930
|
+
cursor: zod.z.string().optional(),
|
|
48931
|
+
max: zod.z.number().int().min(1).max(500)
|
|
48932
|
+
}), zod.z.object({
|
|
48933
|
+
lines: zod.z.array(TrainingLogLineSchema).readonly(),
|
|
48934
|
+
cursor: zod.z.string()
|
|
48935
|
+
}), {
|
|
48936
|
+
auth: "admin",
|
|
48937
|
+
timeoutMs: 6e4
|
|
48938
|
+
}),
|
|
48939
|
+
/** Stop the job. Idempotent: an unknown or finished job is a no-op. */
|
|
48940
|
+
cancelJob: method(zod.z.object(JobRef), zod.z.object({ ok: zod.z.literal(true) }), {
|
|
48941
|
+
kind: "mutation",
|
|
48942
|
+
auth: "admin",
|
|
48943
|
+
timeoutMs: 6e4
|
|
48944
|
+
}),
|
|
48945
|
+
/** The job's `result.json`, as text (small, JSON). `found: false` before it exists. */
|
|
48946
|
+
readResult: method(zod.z.object(JobRef), zod.z.object({
|
|
48947
|
+
found: zod.z.boolean(),
|
|
48948
|
+
text: zod.z.string().optional()
|
|
48949
|
+
}), {
|
|
48950
|
+
kind: "mutation",
|
|
48951
|
+
auth: "admin",
|
|
48952
|
+
timeoutMs: 5 * 6e4
|
|
48953
|
+
}),
|
|
48954
|
+
/**
|
|
48955
|
+
* Offer one output file of the job (`bundle.tar`) to the caller as a
|
|
48956
|
+
* one-shot `ctx.peerBytes` ticket, streamed from the cloud — the provider
|
|
48957
|
+
* stages nothing on disk. The caller dials it at once.
|
|
48958
|
+
*/
|
|
48959
|
+
offerArtifact: method(zod.z.object({
|
|
48960
|
+
...JobRef,
|
|
48961
|
+
path: zod.z.enum(["bundle.tar"])
|
|
48962
|
+
}), zod.z.object({ ticket: PeerBytesTicketSchema }), {
|
|
48963
|
+
kind: "mutation",
|
|
48964
|
+
auth: "admin",
|
|
48965
|
+
timeoutMs: 5 * 6e4
|
|
48966
|
+
}),
|
|
48967
|
+
/** Delete every cloud object tagged with `jobKey` (the dataset first). Idempotent. */
|
|
48968
|
+
cleanupJob: method(zod.z.object(JobRef), zod.z.object({ deleted: zod.z.array(zod.z.string()).readonly() }), {
|
|
48969
|
+
kind: "mutation",
|
|
48970
|
+
auth: "admin",
|
|
48971
|
+
timeoutMs: 5 * 6e4
|
|
48972
|
+
}),
|
|
48973
|
+
/** Every job the provider can still see — the orphan sweep diffs it against our records. */
|
|
48974
|
+
listJobs: method(zod.z.object({ addonId: zod.z.string() }), zod.z.array(ProviderJobStatusSchema).readonly(), {
|
|
48975
|
+
auth: "admin",
|
|
48976
|
+
timeoutMs: 2 * 6e4
|
|
48977
|
+
})
|
|
48978
|
+
}
|
|
48979
|
+
};
|
|
48980
|
+
/**
|
|
48981
|
+
* `training` — the cloud-training orchestrator (D805). Hub-only singleton,
|
|
48982
|
+
* provided by `addon-training`.
|
|
48983
|
+
*
|
|
48984
|
+
* It owns what every provider must agree on, written once:
|
|
48985
|
+
*
|
|
48986
|
+
* - **Dataset versions.** Taken over from pipeline-analytics (D796): the rows
|
|
48987
|
+
* live here (D808); pipeline-analytics still CUTS a version (it owns the corpus and
|
|
48988
|
+
* the export plan) and reaches this store through the five row methods
|
|
48989
|
+
* below, so the Retrain page and the export did not change.
|
|
48990
|
+
* - **Jobs.** A persisted state machine, a reconcile loop against the
|
|
48991
|
+
* provider, a cost guard, a daily orphan sweep and the retention rules.
|
|
48992
|
+
* - **The return flow.** Verified artefacts → Model Studio
|
|
48993
|
+
* (`custom-model-registry.importModelBundle`) → canary on cameras →
|
|
48994
|
+
* rollback. Nothing is ever registered automatically.
|
|
48995
|
+
*
|
|
48996
|
+
* Every method is `admin`.
|
|
48997
|
+
*/
|
|
48998
|
+
/** A version WITH its frame list — what the export and a job read. */
|
|
48999
|
+
var TrainingDatasetVersionRowSchema = RetrainDatasetVersionSchema.omit({ frameCount: true }).extend({ frameIds: zod.z.array(zod.z.string()).max(2e4).readonly() });
|
|
49000
|
+
/** A base model a detector job can fine-tune, and the baseline it is judged against. */
|
|
49001
|
+
var TrainingBaseModelSchema = zod.z.object({
|
|
49002
|
+
modelId: zod.z.string(),
|
|
49003
|
+
label: zod.z.string(),
|
|
49004
|
+
weights: zod.z.string(),
|
|
49005
|
+
imgsz: zod.z.number().int(),
|
|
49006
|
+
baselineOnnxUrl: zod.z.string(),
|
|
49007
|
+
baselineFloors: zod.z.record(zod.z.string(), zod.z.number()),
|
|
49008
|
+
/** The base the fleet runs most — the UI's default. */
|
|
49009
|
+
recommended: zod.z.boolean()
|
|
49010
|
+
});
|
|
49011
|
+
var TrainingPresetSchema = zod.z.enum([
|
|
49012
|
+
"quick",
|
|
49013
|
+
"standard",
|
|
49014
|
+
"thorough"
|
|
49015
|
+
]);
|
|
49016
|
+
var TrainingJobRequestSchema = zod.z.object({
|
|
49017
|
+
datasetVersionId: zod.z.string().min(1),
|
|
49018
|
+
/** The `training-provider` addon that runs it. */
|
|
49019
|
+
addonId: zod.z.string().min(1),
|
|
49020
|
+
gpuType: zod.z.string().min(1),
|
|
49021
|
+
region: zod.z.string(),
|
|
49022
|
+
baseModelId: zod.z.string().min(1),
|
|
49023
|
+
preset: TrainingPresetSchema,
|
|
49024
|
+
/** Freeze the backbone's first N layers; `null` = the preset's choice. */
|
|
49025
|
+
freeze: zod.z.number().int().nonnegative().nullable(),
|
|
49026
|
+
/** Cameras held out whole for evaluation; `null` = the orchestrator's default. */
|
|
49027
|
+
holdoutCameras: zod.z.array(zod.z.number().int()).nullable()
|
|
49028
|
+
});
|
|
49029
|
+
var TrainingSplitPlanSchema = zod.z.object({
|
|
49030
|
+
holdoutCameras: zod.z.array(zod.z.number().int()).readonly(),
|
|
49031
|
+
/** Used when no camera is held out: the last N days of every camera. */
|
|
49032
|
+
holdoutLastDays: zod.z.number().int().nonnegative(),
|
|
49033
|
+
/** Holdout subjects per class (camera holdout only; `null` = known only after the split). */
|
|
49034
|
+
holdoutSupport: zod.z.record(zod.z.string(), zod.z.number().int()).nullable(),
|
|
49035
|
+
trainSubjects: zod.z.number().int()
|
|
49036
|
+
});
|
|
49037
|
+
var TrainingRefusalReasonSchema = zod.z.enum([
|
|
49038
|
+
"unknown-dataset",
|
|
49039
|
+
"unknown-provider",
|
|
49040
|
+
"provider-not-configured",
|
|
49041
|
+
"unknown-base-model",
|
|
49042
|
+
"unknown-gpu",
|
|
49043
|
+
"dataset-too-large",
|
|
49044
|
+
"no-training-subjects",
|
|
49045
|
+
"job-already-running"
|
|
49046
|
+
]);
|
|
49047
|
+
var TrainingJobPlanSchema = zod.z.object({
|
|
49048
|
+
refusal: zod.z.object({
|
|
49049
|
+
reason: TrainingRefusalReasonSchema,
|
|
49050
|
+
detail: zod.z.string()
|
|
49051
|
+
}).nullable(),
|
|
49052
|
+
warnings: zod.z.array(zod.z.string()).readonly(),
|
|
49053
|
+
split: TrainingSplitPlanSchema.nullable(),
|
|
49054
|
+
estimate: zod.z.object({
|
|
49055
|
+
datasetBytes: zod.z.number().int(),
|
|
49056
|
+
uploadMinutesAt20Mbps: zod.z.number(),
|
|
49057
|
+
gpuSecondsLow: zod.z.number(),
|
|
49058
|
+
gpuSecondsHigh: zod.z.number(),
|
|
49059
|
+
usdLow: zod.z.number(),
|
|
49060
|
+
usdHigh: zod.z.number(),
|
|
49061
|
+
notes: zod.z.array(zod.z.string()).readonly()
|
|
49062
|
+
}).nullable(),
|
|
49063
|
+
/** Pre-filled from the provider's settings; the operator may lower or raise them. */
|
|
49064
|
+
defaults: zod.z.object({
|
|
49065
|
+
maxCostUsd: zod.z.number(),
|
|
49066
|
+
maxRuntimeSec: zod.z.number().int()
|
|
49067
|
+
}).nullable(),
|
|
49068
|
+
/** The sentence the confirm dialog repeats: where the frames go and when they are deleted. */
|
|
49069
|
+
dataNotice: zod.z.string()
|
|
49070
|
+
});
|
|
49071
|
+
/**
|
|
49072
|
+
* Orchestrator-owned states, persisted. `uploading` covers exporting the
|
|
49073
|
+
* archive (it is streamed, never staged). Terminal: `failed`, `cancelled`,
|
|
49074
|
+
* `lost`, `expired`, `discarded`. `registered` and `canary` are where a
|
|
49075
|
+
* result lives after the operator acted on it.
|
|
49076
|
+
*/
|
|
49077
|
+
var TrainingJobStateSchema = zod.z.enum([
|
|
49078
|
+
"uploading",
|
|
49079
|
+
"submitted",
|
|
49080
|
+
"starting",
|
|
49081
|
+
"running",
|
|
49082
|
+
"collecting",
|
|
49083
|
+
"ready",
|
|
49084
|
+
"registered",
|
|
49085
|
+
"canary",
|
|
49086
|
+
"failed",
|
|
49087
|
+
"cancelling",
|
|
49088
|
+
"cancelled",
|
|
49089
|
+
"lost",
|
|
49090
|
+
"expired",
|
|
49091
|
+
"discarded"
|
|
49092
|
+
]);
|
|
49093
|
+
var TrainingFailureReasonSchema = zod.z.enum([
|
|
49094
|
+
"cost-cap",
|
|
49095
|
+
"runtime-cap",
|
|
49096
|
+
"upload-failed",
|
|
49097
|
+
"submit-failed",
|
|
49098
|
+
"trainer-failed",
|
|
49099
|
+
"result-invalid",
|
|
49100
|
+
"checksum-mismatch",
|
|
49101
|
+
"fetch-failed",
|
|
49102
|
+
"provider-error",
|
|
49103
|
+
"hub-restart-during-upload"
|
|
49104
|
+
]);
|
|
49105
|
+
var TrainingProgressPointSchema = zod.z.object({
|
|
49106
|
+
epoch: zod.z.number().int(),
|
|
49107
|
+
map50: zod.z.number().nullable(),
|
|
49108
|
+
map50_95: zod.z.number().nullable()
|
|
49109
|
+
});
|
|
49110
|
+
var TrainingCanarySchema = zod.z.object({
|
|
49111
|
+
deviceIds: zod.z.array(zod.z.number().int()).readonly(),
|
|
49112
|
+
since: zod.z.number(),
|
|
49113
|
+
/** Every override the canary replaced, exactly, so rollback restores it. */
|
|
49114
|
+
previous: zod.z.array(zod.z.object({
|
|
49115
|
+
deviceId: zod.z.number().int(),
|
|
49116
|
+
nodeId: zod.z.string(),
|
|
49117
|
+
deviceKey: zod.z.string(),
|
|
49118
|
+
patch: zod.z.object({
|
|
49119
|
+
modelId: zod.z.string().optional(),
|
|
49120
|
+
settings: zod.z.record(zod.z.string(), zod.z.unknown()).readonly().optional()
|
|
49121
|
+
}).nullable()
|
|
49122
|
+
})).readonly()
|
|
49123
|
+
});
|
|
49124
|
+
var TrainingJobSchema = zod.z.object({
|
|
49125
|
+
id: zod.z.string(),
|
|
49126
|
+
idempotencyKey: zod.z.string(),
|
|
49127
|
+
name: zod.z.string(),
|
|
49128
|
+
createdAt: zod.z.number(),
|
|
49129
|
+
updatedAt: zod.z.number(),
|
|
49130
|
+
state: TrainingJobStateSchema,
|
|
49131
|
+
stage: TrainingStageSchema.nullable(),
|
|
49132
|
+
failure: zod.z.object({
|
|
49133
|
+
reason: TrainingFailureReasonSchema,
|
|
49134
|
+
stage: zod.z.string().nullable(),
|
|
49135
|
+
detail: zod.z.string()
|
|
49136
|
+
}).nullable(),
|
|
49137
|
+
request: TrainingJobRequestSchema,
|
|
49138
|
+
maxCostUsd: zod.z.number().nonnegative(),
|
|
49139
|
+
maxRuntimeSec: zod.z.number().int().positive(),
|
|
49140
|
+
trainer: TrainerSourceRefSchema,
|
|
49141
|
+
datasetLayout: RetrainExportLayoutSchema,
|
|
49142
|
+
dataset: zod.z.object({
|
|
49143
|
+
bytes: zod.z.number().int(),
|
|
49144
|
+
sha256: zod.z.string(),
|
|
49145
|
+
uploadedAt: zod.z.number()
|
|
49146
|
+
}).nullable(),
|
|
49147
|
+
split: TrainingSplitPlanSchema,
|
|
49148
|
+
provider: zod.z.object({
|
|
49149
|
+
state: ProviderJobStateSchema.nullable(),
|
|
49150
|
+
providerJobId: zod.z.string().nullable(),
|
|
49151
|
+
startedAt: zod.z.number().nullable(),
|
|
49152
|
+
endedAt: zod.z.number().nullable(),
|
|
49153
|
+
billedSeconds: zod.z.number(),
|
|
49154
|
+
costUsd: zod.z.number(),
|
|
49155
|
+
costIsEstimate: zod.z.boolean(),
|
|
49156
|
+
lastPolledAt: zod.z.number().nullable()
|
|
49157
|
+
}),
|
|
49158
|
+
progress: zod.z.object({
|
|
49159
|
+
epoch: zod.z.number().int().nullable(),
|
|
49160
|
+
epochs: zod.z.number().int().nullable(),
|
|
49161
|
+
etaSec: zod.z.number().nullable(),
|
|
49162
|
+
series: zod.z.array(TrainingProgressPointSchema).readonly()
|
|
49163
|
+
}),
|
|
49164
|
+
/** The last lines of the job's output (≤ 200), newest last. Never frame paths. */
|
|
49165
|
+
logTail: zod.z.array(zod.z.string()).readonly(),
|
|
49166
|
+
logCursor: zod.z.string().nullable(),
|
|
49167
|
+
artifacts: zod.z.object({
|
|
49168
|
+
bundleBytes: zod.z.number().int(),
|
|
49169
|
+
bundleSha256: zod.z.string(),
|
|
49170
|
+
fetchedAt: zod.z.number(),
|
|
49171
|
+
/** When the staged artefacts are deleted from the hub. */
|
|
49172
|
+
expiresAt: zod.z.number()
|
|
49173
|
+
}).nullable(),
|
|
49174
|
+
/** When the provider-side copy of the frames was deleted; null while it exists. */
|
|
49175
|
+
cloudDatasetDeletedAt: zod.z.number().nullable(),
|
|
49176
|
+
/** A failed job's dataset is kept until then (the provider's grace), then deleted. */
|
|
49177
|
+
cloudCleanupDueAt: zod.z.number().nullable(),
|
|
49178
|
+
/** Model ids registered from this job (base, and int8 when kept). */
|
|
49179
|
+
registeredModelIds: zod.z.array(zod.z.string()).readonly(),
|
|
49180
|
+
canary: TrainingCanarySchema.nullable()
|
|
49181
|
+
});
|
|
49182
|
+
var IdInput = zod.z.object({ id: zod.z.string().min(1) });
|
|
49183
|
+
var trainingCapability = {
|
|
49184
|
+
name: "training",
|
|
49185
|
+
scope: "system",
|
|
49186
|
+
mode: "singleton",
|
|
49187
|
+
mount: { kind: "hub-only" },
|
|
49188
|
+
methods: {
|
|
49189
|
+
/** Store a version once. `stored: false` = that id already exists (immutable). */
|
|
49190
|
+
putDatasetVersion: method(TrainingDatasetVersionRowSchema, zod.z.object({ stored: zod.z.boolean() }), {
|
|
49191
|
+
kind: "mutation",
|
|
49192
|
+
auth: "admin"
|
|
49193
|
+
}),
|
|
49194
|
+
getDatasetVersion: method(IdInput, TrainingDatasetVersionRowSchema.nullable(), { auth: "admin" }),
|
|
49195
|
+
/** Every version without its frame list, newest first. */
|
|
49196
|
+
listDatasetVersions: method(zod.z.object({}), zod.z.array(RetrainDatasetVersionSchema).readonly(), { auth: "admin" }),
|
|
49197
|
+
findDatasetVersionIdByName: method(zod.z.object({ name: zod.z.string() }), zod.z.object({ id: zod.z.string().nullable() }), { auth: "admin" }),
|
|
49198
|
+
removeDatasetVersion: method(IdInput, zod.z.object({ removed: zod.z.boolean() }), {
|
|
49199
|
+
kind: "mutation",
|
|
49200
|
+
auth: "admin"
|
|
49201
|
+
}),
|
|
49202
|
+
listBaseModels: method(zod.z.object({}), zod.z.array(TrainingBaseModelSchema).readonly(), { auth: "admin" }),
|
|
49203
|
+
/** Split, estimate, defaults and any named refusal — nothing is started. */
|
|
49204
|
+
planJob: method(TrainingJobRequestSchema, TrainingJobPlanSchema, {
|
|
49205
|
+
auth: "admin",
|
|
49206
|
+
timeoutMs: 6e4
|
|
49207
|
+
}),
|
|
49208
|
+
/**
|
|
49209
|
+
* Start a job. The same `idempotencyKey` returns the job it already
|
|
49210
|
+
* started. Answers at once (`uploading`); the upload and the submit run in
|
|
49211
|
+
* the background and the job record says how far they got.
|
|
49212
|
+
*/
|
|
49213
|
+
startJob: method(TrainingJobRequestSchema.extend({
|
|
49214
|
+
name: zod.z.string().min(1).max(120),
|
|
49215
|
+
maxCostUsd: zod.z.number().positive(),
|
|
49216
|
+
maxRuntimeSec: zod.z.number().int().positive(),
|
|
49217
|
+
idempotencyKey: zod.z.string().min(8).max(80)
|
|
49218
|
+
}), zod.z.discriminatedUnion("kind", [zod.z.object({
|
|
49219
|
+
kind: zod.z.literal("started"),
|
|
49220
|
+
job: TrainingJobSchema
|
|
49221
|
+
}), zod.z.object({
|
|
49222
|
+
kind: zod.z.literal("refused"),
|
|
49223
|
+
reason: TrainingRefusalReasonSchema,
|
|
49224
|
+
detail: zod.z.string()
|
|
49225
|
+
})]), {
|
|
49226
|
+
kind: "mutation",
|
|
49227
|
+
auth: "admin",
|
|
49228
|
+
timeoutMs: 12e4
|
|
49229
|
+
}),
|
|
49230
|
+
listJobs: method(zod.z.object({}), zod.z.array(TrainingJobSchema).readonly(), { auth: "admin" }),
|
|
49231
|
+
getJob: method(IdInput, TrainingJobSchema.nullable(), { auth: "admin" }),
|
|
49232
|
+
cancelJob: method(IdInput, TrainingJobSchema, {
|
|
49233
|
+
kind: "mutation",
|
|
49234
|
+
auth: "admin",
|
|
49235
|
+
timeoutMs: 12e4
|
|
49236
|
+
}),
|
|
49237
|
+
/** The verified `result.json` of a job in `ready` or later; null before. */
|
|
49238
|
+
getJobResult: method(IdInput, TrainingResultSchema.nullable(), { auth: "admin" }),
|
|
49239
|
+
/** Hand the verified bundle to Model Studio; optionally canary right after. */
|
|
49240
|
+
registerResult: method(IdInput.extend({ canaryDeviceIds: zod.z.array(zod.z.number().int()).readonly() }), TrainingJobSchema, {
|
|
49241
|
+
kind: "mutation",
|
|
49242
|
+
auth: "admin",
|
|
49243
|
+
timeoutMs: 30 * 6e4
|
|
49244
|
+
}),
|
|
49245
|
+
/** Pin the registered model on these cameras, remembering what it replaced. */
|
|
49246
|
+
canaryResult: method(IdInput.extend({ deviceIds: zod.z.array(zod.z.number().int()).min(1).readonly() }), TrainingJobSchema, {
|
|
49247
|
+
kind: "mutation",
|
|
49248
|
+
auth: "admin",
|
|
49249
|
+
timeoutMs: 12e4
|
|
49250
|
+
}),
|
|
49251
|
+
/** Put every canary camera back exactly as it was. */
|
|
49252
|
+
rollbackCanary: method(IdInput, TrainingJobSchema, {
|
|
49253
|
+
kind: "mutation",
|
|
49254
|
+
auth: "admin",
|
|
49255
|
+
timeoutMs: 12e4
|
|
49256
|
+
}),
|
|
49257
|
+
/** Delete the staged artefacts of an unregistered result. */
|
|
49258
|
+
discardResult: method(IdInput, TrainingJobSchema, {
|
|
49259
|
+
kind: "mutation",
|
|
49260
|
+
auth: "admin"
|
|
49261
|
+
})
|
|
49262
|
+
}
|
|
49263
|
+
};
|
|
47926
49264
|
function isValueOf(slot, value) {
|
|
47927
49265
|
return value.slot === slot;
|
|
47928
49266
|
}
|
|
@@ -50371,6 +51709,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
50371
51709
|
addonId: null,
|
|
50372
51710
|
access: "create"
|
|
50373
51711
|
},
|
|
51712
|
+
"backup.checkBackupKey": {
|
|
51713
|
+
capName: "backup",
|
|
51714
|
+
capScope: "system",
|
|
51715
|
+
addonId: null,
|
|
51716
|
+
access: "create"
|
|
51717
|
+
},
|
|
50374
51718
|
"backup.delete": {
|
|
50375
51719
|
capName: "backup",
|
|
50376
51720
|
capScope: "system",
|
|
@@ -50383,6 +51727,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
50383
51727
|
addonId: null,
|
|
50384
51728
|
access: "delete"
|
|
50385
51729
|
},
|
|
51730
|
+
"backup.disableEncryption": {
|
|
51731
|
+
capName: "backup",
|
|
51732
|
+
capScope: "system",
|
|
51733
|
+
addonId: null,
|
|
51734
|
+
access: "create"
|
|
51735
|
+
},
|
|
51736
|
+
"backup.getEncryption": {
|
|
51737
|
+
capName: "backup",
|
|
51738
|
+
capScope: "system",
|
|
51739
|
+
addonId: null,
|
|
51740
|
+
access: "view"
|
|
51741
|
+
},
|
|
50386
51742
|
"backup.getEntries": {
|
|
50387
51743
|
capName: "backup",
|
|
50388
51744
|
capScope: "system",
|
|
@@ -50437,6 +51793,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
50437
51793
|
addonId: null,
|
|
50438
51794
|
access: "create"
|
|
50439
51795
|
},
|
|
51796
|
+
"backup.setupEncryption": {
|
|
51797
|
+
capName: "backup",
|
|
51798
|
+
capScope: "system",
|
|
51799
|
+
addonId: null,
|
|
51800
|
+
access: "create"
|
|
51801
|
+
},
|
|
50440
51802
|
"backup.trigger": {
|
|
50441
51803
|
capName: "backup",
|
|
50442
51804
|
capScope: "system",
|
|
@@ -50989,6 +52351,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
50989
52351
|
addonId: null,
|
|
50990
52352
|
access: "create"
|
|
50991
52353
|
},
|
|
52354
|
+
"customModelRegistry.importModelBundle": {
|
|
52355
|
+
capName: "custom-model-registry",
|
|
52356
|
+
capScope: "system",
|
|
52357
|
+
addonId: null,
|
|
52358
|
+
access: "create"
|
|
52359
|
+
},
|
|
50992
52360
|
"customModelRegistry.listModels": {
|
|
50993
52361
|
capName: "custom-model-registry",
|
|
50994
52362
|
capScope: "system",
|
|
@@ -52429,6 +53797,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
52429
53797
|
addonId: null,
|
|
52430
53798
|
access: "view"
|
|
52431
53799
|
},
|
|
53800
|
+
"keychainAdmin.acknowledgeReset": {
|
|
53801
|
+
capName: "keychain-admin",
|
|
53802
|
+
capScope: "system",
|
|
53803
|
+
addonId: null,
|
|
53804
|
+
access: "create"
|
|
53805
|
+
},
|
|
52432
53806
|
"keychainAdmin.deleteOrphans": {
|
|
52433
53807
|
capName: "keychain-admin",
|
|
52434
53808
|
capScope: "system",
|
|
@@ -52465,6 +53839,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
52465
53839
|
addonId: null,
|
|
52466
53840
|
access: "delete"
|
|
52467
53841
|
},
|
|
53842
|
+
"keychainAdmin.resetReport": {
|
|
53843
|
+
capName: "keychain-admin",
|
|
53844
|
+
capScope: "system",
|
|
53845
|
+
addonId: null,
|
|
53846
|
+
access: "delete"
|
|
53847
|
+
},
|
|
52468
53848
|
"keychainAdmin.reveal": {
|
|
52469
53849
|
capName: "keychain-admin",
|
|
52470
53850
|
capScope: "system",
|
|
@@ -54211,6 +55591,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
54211
55591
|
addonId: null,
|
|
54212
55592
|
access: "view"
|
|
54213
55593
|
},
|
|
55594
|
+
"pipelineAnalytics.offerRetrainArchive": {
|
|
55595
|
+
capName: "pipeline-analytics",
|
|
55596
|
+
capScope: "device",
|
|
55597
|
+
addonId: null,
|
|
55598
|
+
access: "create"
|
|
55599
|
+
},
|
|
54214
55600
|
"pipelineAnalytics.ownersWithMedia": {
|
|
54215
55601
|
capName: "pipeline-analytics",
|
|
54216
55602
|
capScope: "device",
|
|
@@ -56749,6 +58135,174 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
56749
58135
|
addonId: null,
|
|
56750
58136
|
access: "view"
|
|
56751
58137
|
},
|
|
58138
|
+
"training.canaryResult": {
|
|
58139
|
+
capName: "training",
|
|
58140
|
+
capScope: "system",
|
|
58141
|
+
addonId: null,
|
|
58142
|
+
access: "create"
|
|
58143
|
+
},
|
|
58144
|
+
"training.cancelJob": {
|
|
58145
|
+
capName: "training",
|
|
58146
|
+
capScope: "system",
|
|
58147
|
+
addonId: null,
|
|
58148
|
+
access: "create"
|
|
58149
|
+
},
|
|
58150
|
+
"training.discardResult": {
|
|
58151
|
+
capName: "training",
|
|
58152
|
+
capScope: "system",
|
|
58153
|
+
addonId: null,
|
|
58154
|
+
access: "create"
|
|
58155
|
+
},
|
|
58156
|
+
"training.findDatasetVersionIdByName": {
|
|
58157
|
+
capName: "training",
|
|
58158
|
+
capScope: "system",
|
|
58159
|
+
addonId: null,
|
|
58160
|
+
access: "view"
|
|
58161
|
+
},
|
|
58162
|
+
"training.getDatasetVersion": {
|
|
58163
|
+
capName: "training",
|
|
58164
|
+
capScope: "system",
|
|
58165
|
+
addonId: null,
|
|
58166
|
+
access: "view"
|
|
58167
|
+
},
|
|
58168
|
+
"training.getJob": {
|
|
58169
|
+
capName: "training",
|
|
58170
|
+
capScope: "system",
|
|
58171
|
+
addonId: null,
|
|
58172
|
+
access: "view"
|
|
58173
|
+
},
|
|
58174
|
+
"training.getJobResult": {
|
|
58175
|
+
capName: "training",
|
|
58176
|
+
capScope: "system",
|
|
58177
|
+
addonId: null,
|
|
58178
|
+
access: "view"
|
|
58179
|
+
},
|
|
58180
|
+
"training.listBaseModels": {
|
|
58181
|
+
capName: "training",
|
|
58182
|
+
capScope: "system",
|
|
58183
|
+
addonId: null,
|
|
58184
|
+
access: "view"
|
|
58185
|
+
},
|
|
58186
|
+
"training.listDatasetVersions": {
|
|
58187
|
+
capName: "training",
|
|
58188
|
+
capScope: "system",
|
|
58189
|
+
addonId: null,
|
|
58190
|
+
access: "view"
|
|
58191
|
+
},
|
|
58192
|
+
"training.listJobs": {
|
|
58193
|
+
capName: "training",
|
|
58194
|
+
capScope: "system",
|
|
58195
|
+
addonId: null,
|
|
58196
|
+
access: "view"
|
|
58197
|
+
},
|
|
58198
|
+
"training.planJob": {
|
|
58199
|
+
capName: "training",
|
|
58200
|
+
capScope: "system",
|
|
58201
|
+
addonId: null,
|
|
58202
|
+
access: "view"
|
|
58203
|
+
},
|
|
58204
|
+
"training.putDatasetVersion": {
|
|
58205
|
+
capName: "training",
|
|
58206
|
+
capScope: "system",
|
|
58207
|
+
addonId: null,
|
|
58208
|
+
access: "create"
|
|
58209
|
+
},
|
|
58210
|
+
"training.registerResult": {
|
|
58211
|
+
capName: "training",
|
|
58212
|
+
capScope: "system",
|
|
58213
|
+
addonId: null,
|
|
58214
|
+
access: "create"
|
|
58215
|
+
},
|
|
58216
|
+
"training.removeDatasetVersion": {
|
|
58217
|
+
capName: "training",
|
|
58218
|
+
capScope: "system",
|
|
58219
|
+
addonId: null,
|
|
58220
|
+
access: "delete"
|
|
58221
|
+
},
|
|
58222
|
+
"training.rollbackCanary": {
|
|
58223
|
+
capName: "training",
|
|
58224
|
+
capScope: "system",
|
|
58225
|
+
addonId: null,
|
|
58226
|
+
access: "create"
|
|
58227
|
+
},
|
|
58228
|
+
"training.startJob": {
|
|
58229
|
+
capName: "training",
|
|
58230
|
+
capScope: "system",
|
|
58231
|
+
addonId: null,
|
|
58232
|
+
access: "create"
|
|
58233
|
+
},
|
|
58234
|
+
"trainingProvider.cancelJob": {
|
|
58235
|
+
capName: "training-provider",
|
|
58236
|
+
capScope: "system",
|
|
58237
|
+
addonId: null,
|
|
58238
|
+
access: "create"
|
|
58239
|
+
},
|
|
58240
|
+
"trainingProvider.cleanupJob": {
|
|
58241
|
+
capName: "training-provider",
|
|
58242
|
+
capScope: "system",
|
|
58243
|
+
addonId: null,
|
|
58244
|
+
access: "create"
|
|
58245
|
+
},
|
|
58246
|
+
"trainingProvider.describe": {
|
|
58247
|
+
capName: "training-provider",
|
|
58248
|
+
capScope: "system",
|
|
58249
|
+
addonId: null,
|
|
58250
|
+
access: "view"
|
|
58251
|
+
},
|
|
58252
|
+
"trainingProvider.estimateJob": {
|
|
58253
|
+
capName: "training-provider",
|
|
58254
|
+
capScope: "system",
|
|
58255
|
+
addonId: null,
|
|
58256
|
+
access: "view"
|
|
58257
|
+
},
|
|
58258
|
+
"trainingProvider.getJob": {
|
|
58259
|
+
capName: "training-provider",
|
|
58260
|
+
capScope: "system",
|
|
58261
|
+
addonId: null,
|
|
58262
|
+
access: "view"
|
|
58263
|
+
},
|
|
58264
|
+
"trainingProvider.listJobEvents": {
|
|
58265
|
+
capName: "training-provider",
|
|
58266
|
+
capScope: "system",
|
|
58267
|
+
addonId: null,
|
|
58268
|
+
access: "view"
|
|
58269
|
+
},
|
|
58270
|
+
"trainingProvider.listJobs": {
|
|
58271
|
+
capName: "training-provider",
|
|
58272
|
+
capScope: "system",
|
|
58273
|
+
addonId: null,
|
|
58274
|
+
access: "view"
|
|
58275
|
+
},
|
|
58276
|
+
"trainingProvider.offerArtifact": {
|
|
58277
|
+
capName: "training-provider",
|
|
58278
|
+
capScope: "system",
|
|
58279
|
+
addonId: null,
|
|
58280
|
+
access: "create"
|
|
58281
|
+
},
|
|
58282
|
+
"trainingProvider.readResult": {
|
|
58283
|
+
capName: "training-provider",
|
|
58284
|
+
capScope: "system",
|
|
58285
|
+
addonId: null,
|
|
58286
|
+
access: "create"
|
|
58287
|
+
},
|
|
58288
|
+
"trainingProvider.submitJob": {
|
|
58289
|
+
capName: "training-provider",
|
|
58290
|
+
capScope: "system",
|
|
58291
|
+
addonId: null,
|
|
58292
|
+
access: "create"
|
|
58293
|
+
},
|
|
58294
|
+
"trainingProvider.uploadDataset": {
|
|
58295
|
+
capName: "training-provider",
|
|
58296
|
+
capScope: "system",
|
|
58297
|
+
addonId: null,
|
|
58298
|
+
access: "create"
|
|
58299
|
+
},
|
|
58300
|
+
"trainingProvider.validateCredentials": {
|
|
58301
|
+
capName: "training-provider",
|
|
58302
|
+
capScope: "system",
|
|
58303
|
+
addonId: null,
|
|
58304
|
+
access: "create"
|
|
58305
|
+
},
|
|
56752
58306
|
"turnProvider.getTurnServers": {
|
|
56753
58307
|
capName: "turn-provider",
|
|
56754
58308
|
capScope: "system",
|
|
@@ -56773,12 +58327,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
56773
58327
|
addonId: null,
|
|
56774
58328
|
access: "create"
|
|
56775
58329
|
},
|
|
56776
|
-
"userManagement.createApiKey": {
|
|
56777
|
-
capName: "user-management",
|
|
56778
|
-
capScope: "system",
|
|
56779
|
-
addonId: null,
|
|
56780
|
-
access: "create"
|
|
56781
|
-
},
|
|
56782
58330
|
"userManagement.createScopedToken": {
|
|
56783
58331
|
capName: "user-management",
|
|
56784
58332
|
capScope: "system",
|
|
@@ -56815,12 +58363,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
56815
58363
|
addonId: null,
|
|
56816
58364
|
access: "view"
|
|
56817
58365
|
},
|
|
56818
|
-
"userManagement.linkExternalIdentity": {
|
|
56819
|
-
capName: "user-management",
|
|
56820
|
-
capScope: "system",
|
|
56821
|
-
addonId: null,
|
|
56822
|
-
access: "create"
|
|
56823
|
-
},
|
|
56824
58366
|
"userManagement.listApiKeys": {
|
|
56825
58367
|
capName: "user-management",
|
|
56826
58368
|
capScope: "system",
|
|
@@ -56923,12 +58465,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
56923
58465
|
addonId: null,
|
|
56924
58466
|
access: "create"
|
|
56925
58467
|
},
|
|
56926
|
-
"userManagement.validateApiKey": {
|
|
56927
|
-
capName: "user-management",
|
|
56928
|
-
capScope: "system",
|
|
56929
|
-
addonId: null,
|
|
56930
|
-
access: "view"
|
|
56931
|
-
},
|
|
56932
58468
|
"userManagement.validateCredentials": {
|
|
56933
58469
|
capName: "user-management",
|
|
56934
58470
|
capScope: "system",
|
|
@@ -58921,6 +60457,8 @@ var ALL_CAPABILITY_DEFINITIONS = [
|
|
|
58921
60457
|
temperatureSensorCapability,
|
|
58922
60458
|
terminalSessionCapability,
|
|
58923
60459
|
toastCapability,
|
|
60460
|
+
trainingCapability,
|
|
60461
|
+
trainingProviderCapability,
|
|
58924
60462
|
turnProviderCapability,
|
|
58925
60463
|
updateCapability,
|
|
58926
60464
|
userManagementCapability,
|
|
@@ -61299,6 +62837,11 @@ Object.freeze({
|
|
|
61299
62837
|
form: "single",
|
|
61300
62838
|
optional: false
|
|
61301
62839
|
}],
|
|
62840
|
+
"training.canaryResult": [{
|
|
62841
|
+
name: "deviceIds",
|
|
62842
|
+
form: "array",
|
|
62843
|
+
optional: false
|
|
62844
|
+
}],
|
|
61302
62845
|
"update.getStatus": [{
|
|
61303
62846
|
name: "deviceId",
|
|
61304
62847
|
form: "single",
|
|
@@ -62936,7 +64479,12 @@ var CREDENTIAL_PARAM_PATTERNS = [
|
|
|
62936
64479
|
/^bearer$/i,
|
|
62937
64480
|
/^api[_-]?key$/i,
|
|
62938
64481
|
/^access[_-]?token$/i,
|
|
62939
|
-
/^refresh[_-]?token$/i
|
|
64482
|
+
/^refresh[_-]?token$/i,
|
|
64483
|
+
/^bridge$/i,
|
|
64484
|
+
/^code$/i,
|
|
64485
|
+
/^state$/i,
|
|
64486
|
+
/^challenge$/i,
|
|
64487
|
+
/^link$/i
|
|
62940
64488
|
];
|
|
62941
64489
|
function isCredentialParam(name) {
|
|
62942
64490
|
return CREDENTIAL_PARAM_PATTERNS.some((re) => re.test(name));
|
|
@@ -62958,6 +64506,10 @@ function isCredentialParam(name) {
|
|
|
62958
64506
|
* are NOT masked (those are entity references, not credentials).
|
|
62959
64507
|
*/
|
|
62960
64508
|
function maskUrlCredentials(rawUrl) {
|
|
64509
|
+
if (rawUrl.startsWith("/") && !rawUrl.startsWith("//")) {
|
|
64510
|
+
const masked = maskUrlCredentials(`http://relative.invalid${rawUrl}`);
|
|
64511
|
+
return masked.startsWith("http://relative.invalid") ? masked.slice(23) : rawUrl;
|
|
64512
|
+
}
|
|
62961
64513
|
try {
|
|
62962
64514
|
const u = new URL(rawUrl);
|
|
62963
64515
|
let touched = false;
|
|
@@ -63313,6 +64865,12 @@ Object.defineProperty(exports, "HF_REPO", {
|
|
|
63313
64865
|
return HF_REPO;
|
|
63314
64866
|
}
|
|
63315
64867
|
});
|
|
64868
|
+
Object.defineProperty(exports, "KEYCHAIN_RESET_TRIGGER_ACTOR", {
|
|
64869
|
+
enumerable: true,
|
|
64870
|
+
get: function() {
|
|
64871
|
+
return KEYCHAIN_RESET_TRIGGER_ACTOR;
|
|
64872
|
+
}
|
|
64873
|
+
});
|
|
63316
64874
|
Object.defineProperty(exports, "LOG_LEVEL_RANK", {
|
|
63317
64875
|
enumerable: true,
|
|
63318
64876
|
get: function() {
|
|
@@ -64009,6 +65567,12 @@ Object.defineProperty(exports, "looseSchema", {
|
|
|
64009
65567
|
return looseSchema;
|
|
64010
65568
|
}
|
|
64011
65569
|
});
|
|
65570
|
+
Object.defineProperty(exports, "makeSecretMarker", {
|
|
65571
|
+
enumerable: true,
|
|
65572
|
+
get: function() {
|
|
65573
|
+
return makeSecretMarker;
|
|
65574
|
+
}
|
|
65575
|
+
});
|
|
64012
65576
|
Object.defineProperty(exports, "maskUrlCredentials", {
|
|
64013
65577
|
enumerable: true,
|
|
64014
65578
|
get: function() {
|