@camstack/types 1.2.326 → 1.2.328

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/addon/base-addon.d.ts +58 -0
  2. package/dist/addon/secret-aware-context.d.ts +27 -0
  3. package/dist/addon/secret-paths.d.ts +48 -0
  4. package/dist/addon/secret-settings-view.d.ts +80 -0
  5. package/dist/addon.d.ts +2 -0
  6. package/dist/addon.js +54 -4
  7. package/dist/addon.mjs +27 -5
  8. package/dist/capabilities/addon-routes.cap.d.ts +2 -2
  9. package/dist/capabilities/addons.cap.d.ts +4 -4
  10. package/dist/capabilities/alerts.cap.d.ts +5 -5
  11. package/dist/capabilities/control.cap.d.ts +11 -11
  12. package/dist/capabilities/day-night.cap.d.ts +8 -8
  13. package/dist/capabilities/device-manager.cap.d.ts +9 -0
  14. package/dist/capabilities/enum-sensor.cap.d.ts +4 -4
  15. package/dist/capabilities/index.d.ts +7 -3
  16. package/dist/capabilities/integrations.cap.d.ts +6 -3
  17. package/dist/capabilities/keychain-admin.cap.d.ts +312 -0
  18. package/dist/capabilities/keychain.cap.d.ts +180 -0
  19. package/dist/capabilities/lawn-mower-control.cap.d.ts +4 -4
  20. package/dist/capabilities/local-network.cap.d.ts +2 -2
  21. package/dist/capabilities/log-channels.cap.d.ts +1 -1
  22. package/dist/capabilities/log-destination.cap.d.ts +5 -5
  23. package/dist/capabilities/login-method.cap.d.ts +3 -0
  24. package/dist/capabilities/mesh-network.cap.d.ts +3 -3
  25. package/dist/capabilities/network-access.cap.d.ts +7 -7
  26. package/dist/capabilities/notification-rules.cap.d.ts +30 -30
  27. package/dist/capabilities/osd-manager.cap.d.ts +11 -11
  28. package/dist/capabilities/osd.cap.d.ts +5 -5
  29. package/dist/capabilities/pipeline-analytics.cap.d.ts +1127 -104
  30. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +384 -0
  31. package/dist/capabilities/pipeline-runner.cap.d.ts +145 -2
  32. package/dist/capabilities/sso-bridge.cap.d.ts +6 -0
  33. package/dist/capabilities/storage.cap.d.ts +1 -1
  34. package/dist/capabilities/system.cap.d.ts +21 -21
  35. package/dist/capabilities/user-management.cap.d.ts +53 -1
  36. package/dist/capabilities/webrtc-session.cap.d.ts +3 -3
  37. package/dist/client.js +38 -1
  38. package/dist/client.mjs +38 -1
  39. package/dist/device/device-context.d.ts +11 -1
  40. package/dist/device/display-icon-vocabulary.d.ts +7 -0
  41. package/dist/device/index.d.ts +2 -1
  42. package/dist/device/zod-secret.d.ts +35 -0
  43. package/dist/enums/event-category.d.ts +7 -0
  44. package/dist/enums.js +1 -1
  45. package/dist/enums.mjs +1 -1
  46. package/dist/{event-category-CtBAl88K.js → event-category-CHduMGvg.js} +7 -0
  47. package/dist/{event-category-B_0XEcLF.mjs → event-category-DaIZi00R.mjs} +7 -0
  48. package/dist/generated/addon-api.d.ts +376 -0
  49. package/dist/generated/cap-input-defaults.d.ts +1 -1
  50. package/dist/generated/capability-router-map.d.ts +8 -2
  51. package/dist/generated/method-access-map.d.ts +1 -1
  52. package/dist/generated/method-device-selectors.d.ts +1 -1
  53. package/dist/generated/system-proxy.d.ts +5 -3
  54. package/dist/health/wiring-health.d.ts +8 -8
  55. package/dist/index.d.ts +8 -3
  56. package/dist/index.js +1970 -110
  57. package/dist/index.mjs +1849 -107
  58. package/dist/interfaces/addon.d.ts +25 -0
  59. package/dist/interfaces/config-ui-secrets.d.ts +12 -0
  60. package/dist/interfaces/config-ui.d.ts +60 -3
  61. package/dist/interfaces/event-bus.d.ts +11 -0
  62. package/dist/interfaces/pipeline-runner-capability.d.ts +18 -0
  63. package/dist/keychain/index.d.ts +5 -0
  64. package/dist/keychain/known-secrets.d.ts +31 -0
  65. package/dist/keychain/resolve-secret-views.d.ts +2 -0
  66. package/dist/keychain/secret-marker.d.ts +52 -0
  67. package/dist/keychain/secret-ref.d.ts +62 -0
  68. package/dist/keychain/secrets-access.d.ts +77 -0
  69. package/dist/logging/log-channel.d.ts +2 -2
  70. package/dist/pipeline/auto-tune/plan-schemas.d.ts +123 -0
  71. package/dist/pipeline/auto-tune/recommend.d.ts +26 -1
  72. package/dist/pipeline/load-balancing/schemas.d.ts +8 -7
  73. package/dist/pipeline/node-benchmark/benchmark-mode.d.ts +57 -0
  74. package/dist/pipeline/node-benchmark/index.d.ts +9 -0
  75. package/dist/pipeline/node-benchmark/results.d.ts +52 -0
  76. package/dist/pipeline/node-benchmark/schemas.d.ts +848 -0
  77. package/dist/pipeline/node-benchmark/step-tree.d.ts +23 -0
  78. package/dist/{sleep-BNeXenvr.mjs → sleep-Cr5YOk3m.mjs} +1337 -33
  79. package/dist/{sleep-DPar9AHl.js → sleep-i3kl-0Na.js} +1628 -66
  80. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- const require_event_category = require("./event-category-CtBAl88K.js");
1
+ const require_event_category = require("./event-category-CHduMGvg.js");
2
2
  let zod = require("zod");
3
3
  //#region src/audio/audio-chunk-format.ts
4
4
  /**
@@ -116,6 +116,192 @@ function buildAlawTable() {
116
116
  var ULAW_TABLE = buildUlawTable();
117
117
  var ALAW_TABLE = buildAlawTable();
118
118
  //#endregion
119
+ //#region src/interfaces/config-ui-secrets.ts
120
+ /**
121
+ * The value a redacted secret is replaced with on every read surface.
122
+ *
123
+ * It is also the WRITE-BACK token: an `upsertLocation` that sends this value
124
+ * back for a key means "keep what is stored", which is what lets an operator
125
+ * rename a location without retyping its password. A literal an operator could
126
+ * plausibly choose as a real password would turn that convenience into a way
127
+ * to lock yourself out, hence the sentinel shape.
128
+ */
129
+ var REDACTED_SECRET = "__camstack_redacted__";
130
+ /** Is this field's stored value a credential? */
131
+ function isSecretConfigField(field) {
132
+ if (field.type === "password") return true;
133
+ return "secret" in field && field.secret === true;
134
+ }
135
+ /**
136
+ * Is this field's value stored in the KEYCHAIN (D787)? Every secret field is,
137
+ * by its TYPE, unless it opts out with `storage: 'inline'` — never by its name.
138
+ */
139
+ function isKeychainStoredField(field) {
140
+ if (!isSecretConfigField(field)) return false;
141
+ if (field.type === "textarea" && field.isJson === true) return false;
142
+ return !("storage" in field && field.storage === "inline");
143
+ }
144
+ /**
145
+ * Every config key in `schema` whose value is a secret, including keys nested
146
+ * inside `group` / `sub-tabs` containers.
147
+ */
148
+ function collectSecretConfigKeys(schema) {
149
+ const keys = /* @__PURE__ */ new Set();
150
+ for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkField(field, keys);
151
+ return keys;
152
+ }
153
+ /**
154
+ * Every top-level config key whose value the KEYCHAIN stores (D787) —
155
+ * {@link collectSecretConfigKeys} minus `storage: 'inline'` and minus JSON
156
+ * blobs (see {@link isKeychainStoredField}). What a form sends as
157
+ * `secretKeys` so the owner's door knows which values to keep out of the blob.
158
+ */
159
+ function collectKeychainStoredKeys(schema) {
160
+ const keys = /* @__PURE__ */ new Set();
161
+ for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkKeychainField(field, keys);
162
+ return keys;
163
+ }
164
+ function walkKeychainField(field, out) {
165
+ if (!isRecord$2(field)) return;
166
+ const type = field["type"];
167
+ const key = field["key"];
168
+ const isSecret = type === "password" || field["secret"] === true;
169
+ const isJsonBlob = type === "textarea" && field["isJson"] === true;
170
+ if (isSecret && !isJsonBlob && field["storage"] !== "inline" && typeof key === "string" && key.length > 0) out.add(key);
171
+ if (type === "group") {
172
+ for (const child of fieldsOf(field)) walkKeychainField(child, out);
173
+ return;
174
+ }
175
+ if (type === "sub-tabs") {
176
+ const tabs = field["tabs"];
177
+ if (!Array.isArray(tabs)) return;
178
+ for (const tab of tabs) for (const child of fieldsOf(tab)) walkKeychainField(child, out);
179
+ }
180
+ }
181
+ /**
182
+ * True when the value is a readable schema declaring at least one field of any
183
+ * kind. Lets a caller tell "this provider has no secrets" from "this provider's
184
+ * schema could not be read", which are the same empty set otherwise.
185
+ */
186
+ function schemaDeclaresAnyField(schema) {
187
+ for (const section of sectionsOf(schema)) if (fieldsOf(section).length > 0) return true;
188
+ return false;
189
+ }
190
+ function isRecord$2(value) {
191
+ return typeof value === "object" && value !== null && !Array.isArray(value);
192
+ }
193
+ function sectionsOf(schema) {
194
+ if (!isRecord$2(schema)) return [];
195
+ const sections = schema["sections"];
196
+ return Array.isArray(sections) ? sections : [];
197
+ }
198
+ function fieldsOf(node) {
199
+ if (!isRecord$2(node)) return [];
200
+ const fields = node["fields"];
201
+ return Array.isArray(fields) ? fields : [];
202
+ }
203
+ function walkField(field, out) {
204
+ if (!isRecord$2(field)) return;
205
+ const type = field["type"];
206
+ const key = field["key"];
207
+ if ((type === "password" || field["secret"] === true) && typeof key === "string" && key.length > 0) out.add(key);
208
+ if (type === "group") {
209
+ for (const child of fieldsOf(field)) walkField(child, out);
210
+ return;
211
+ }
212
+ if (type === "sub-tabs") {
213
+ const tabs = field["tabs"];
214
+ if (!Array.isArray(tabs)) return;
215
+ for (const tab of tabs) for (const child of fieldsOf(tab)) walkField(child, out);
216
+ }
217
+ }
218
+ //#endregion
219
+ //#region src/keychain/secret-marker.ts
220
+ /**
221
+ * What a settings blob holds where a secret used to be.
222
+ *
223
+ * A keychain-stored field never holds its value. It holds a self-describing
224
+ * MARKER, `{"$secret":"secret://…","v":1}` — an object, never a string, so no
225
+ * code can mistake it for a credential and no form can send it to a camera as
226
+ * a password. The empty state is `null` / absent, never `''`.
227
+ *
228
+ * The same shapes travel to and from the admin UI:
229
+ *
230
+ * - read: `hydrateSchema` hands a secret field a {@link SecretFieldView}
231
+ * (`{ $secret, v, set: true }`) or `null` — the browser learns whether a
232
+ * value is set, never the value.
233
+ * - write: an untouched field submits the view back unchanged (KEEP); a typed
234
+ * value is a plain non-empty string (CHANGE); the Clear button submits
235
+ * {@link SECRET_CLEAR} (CLEAR). `''`, `null` and the legacy redaction tokens
236
+ * are KEEP — an empty string is what a defaults-fallback pushes back, and
237
+ * treating it as "clear" is how a save destroyed a stored credential before.
238
+ *
239
+ * Pure and browser-safe.
240
+ */
241
+ var SECRET_CLEAR = { $secretClear: true };
242
+ /** The `'***'` token D634 redaction writes for non-admins. */
243
+ var LEGACY_DEVICE_REDACTION = "***";
244
+ function isRecord$1(value) {
245
+ return typeof value === "object" && value !== null && !Array.isArray(value);
246
+ }
247
+ function makeSecretMarker(ref) {
248
+ return {
249
+ $secret: ref,
250
+ v: 1
251
+ };
252
+ }
253
+ /** A stored marker: an object whose `$secret` is a non-empty string. */
254
+ function isSecretMarker(value) {
255
+ if (!isRecord$1(value)) return false;
256
+ const ref = value["$secret"];
257
+ return typeof ref === "string" && ref.length > 0;
258
+ }
259
+ /** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
260
+ function isSecretMarkerLike(value) {
261
+ return isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
262
+ }
263
+ function isSecretClear(value) {
264
+ return isRecord$1(value) && value["$secretClear"] === true;
265
+ }
266
+ function isSecretFieldView(value) {
267
+ return isRecord$1(value) && value["set"] === true && isSecretMarkerLike(value);
268
+ }
269
+ /**
270
+ * The UI view of a stored secret field value: a marker becomes "set", inline
271
+ * plaintext becomes "set (inline)", nothing becomes `null`. A value never
272
+ * survives this function.
273
+ */
274
+ function toSecretFieldView(stored) {
275
+ if (isSecretMarker(stored)) return {
276
+ $secret: stored.$secret,
277
+ v: 1,
278
+ set: true
279
+ };
280
+ if (typeof stored === "string" && stored.length > 0) return {
281
+ $secret: null,
282
+ v: 1,
283
+ set: true,
284
+ inline: true
285
+ };
286
+ return null;
287
+ }
288
+ /**
289
+ * Classify an incoming value for a secret path. Only a non-empty string that is
290
+ * not a redaction token sets a value; only {@link SECRET_CLEAR} clears.
291
+ * Everything else — a marker (ours or anyone's), a UI view, `''`, `null`,
292
+ * `undefined`, a redaction token, a non-string — keeps what is stored.
293
+ */
294
+ function classifySecretWrite(incoming) {
295
+ if (isSecretClear(incoming)) return { kind: "clear" };
296
+ if (typeof incoming !== "string") return { kind: "keep" };
297
+ if (incoming.length === 0) return { kind: "keep" };
298
+ if (incoming === "__camstack_redacted__" || incoming === LEGACY_DEVICE_REDACTION) return { kind: "keep" };
299
+ return {
300
+ kind: "set",
301
+ value: incoming
302
+ };
303
+ }
304
+ //#endregion
119
305
  //#region src/interfaces/config-ui.ts
120
306
  /** Predefined tabs with standard label, icon, and sort order.
121
307
  *
@@ -358,33 +544,19 @@ function resolveHydratedFieldValue(schema, key) {
358
544
  const entry = collectHydratedFieldEntries(schema).get(key);
359
545
  return entry === void 0 ? void 0 : entry.value ?? entry.default;
360
546
  }
361
- /**
362
- * Merge a `ConfigUISchema` with a raw `values` record into a
363
- * `ConfigUISchemaWithValues`. Used by every `get*Settings` backend endpoint
364
- * before returning to the admin UI. Groups are walked recursively.
365
- *
366
- * For each leaf field:
367
- * - structural fields (`separator`, `info`, `button`) pass through unchanged
368
- * - other fields get `value = values[key] ?? field.default ?? null`
369
- *
370
- * Unknown keys in `values` (keys not declared in the schema) are silently
371
- * ignored — they don't contribute to the output. Missing keys fall back to
372
- * the schema default or `null`. This mirrors the old backend behaviour where
373
- * `FormBuilder` rendered `values[key] ?? field.default`.
374
- */
375
- function hydrateSchema(schema, values) {
547
+ function hydrateSchema(schema, values, options = {}) {
376
548
  return {
377
549
  ...schema.tabs ? { tabs: [...schema.tabs] } : {},
378
550
  sections: schema.sections.map((section) => ({
379
551
  ...section,
380
- fields: section.fields.map((field) => hydrateField(field, values))
552
+ fields: section.fields.map((field) => hydrateField(field, values, options))
381
553
  }))
382
554
  };
383
555
  }
384
- function hydrateField(field, values) {
556
+ function hydrateField(field, values, options) {
385
557
  if (isValuelessField(field)) return field;
386
558
  if (field.type === "group") {
387
- const hydratedChildren = field.fields.map((child) => hydrateField(child, values));
559
+ const hydratedChildren = field.fields.map((child) => hydrateField(child, values, options));
388
560
  return {
389
561
  ...field,
390
562
  fields: hydratedChildren
@@ -393,7 +565,7 @@ function hydrateField(field, values) {
393
565
  if (field.type === "sub-tabs") {
394
566
  const hydratedTabs = field.tabs.map((tab) => ({
395
567
  ...tab,
396
- fields: tab.fields.map((child) => hydrateField(child, values))
568
+ fields: tab.fields.map((child) => hydrateField(child, values, options))
397
569
  }));
398
570
  return {
399
571
  ...field,
@@ -403,6 +575,33 @@ function hydrateField(field, values) {
403
575
  const key = field.key;
404
576
  const storedValue = Object.prototype.hasOwnProperty.call(values, key) ? values[key] : void 0;
405
577
  const defaultValue = field.default;
578
+ const asView = (v) => isSecretMarker(v) || options.inlineSecretsAsViews === true && v !== void 0;
579
+ if (isKeychainStoredField(field) && asView(storedValue)) return {
580
+ ...field,
581
+ value: toSecretFieldView(storedValue)
582
+ };
583
+ if (isKeychainStoredField(field) && options.inlineSecretsAsViews === true) return {
584
+ ...field,
585
+ value: null
586
+ };
587
+ if (field.type === "editable-array" && Array.isArray(storedValue)) {
588
+ const secretItemKeys = field.itemFields.filter((item) => "key" in item && isKeychainStoredField(item)).map((item) => "key" in item ? item.key : "");
589
+ if (secretItemKeys.length > 0) {
590
+ const rows = storedValue.map((row) => {
591
+ if (row === null || typeof row !== "object" || Array.isArray(row)) return row;
592
+ const out = { ...row };
593
+ for (const k of secretItemKeys) {
594
+ const v = Reflect.get(row, k);
595
+ if (asView(v)) out[k] = toSecretFieldView(v);
596
+ }
597
+ return out;
598
+ });
599
+ return {
600
+ ...field,
601
+ value: rows
602
+ };
603
+ }
604
+ }
406
605
  if (field.multiple) {
407
606
  const stored = Array.isArray(storedValue) ? storedValue : storedValue !== void 0 && storedValue !== null ? [storedValue] : [];
408
607
  const itemFallback = field.multiple.itemDefault !== void 0 ? field.multiple.itemDefault : defaultValue !== void 0 ? defaultValue : typeOf(field) === "string" ? "" : null;
@@ -668,7 +867,575 @@ function projectStore(store, perNodeKeys, nodeId) {
668
867
  return out;
669
868
  }
670
869
  //#endregion
870
+ //#region src/keychain/secret-ref.ts
871
+ /**
872
+ * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
873
+ *
874
+ * A ref is DETERMINISTIC from where the value lives — the owner addon, the
875
+ * scope (global, one node, one device, one integration, one row of a business
876
+ * collection) and the field path inside that scope — so saving the same field
877
+ * twice updates the same ref, and the ref itself says which settings blob
878
+ * points at it. It is also the AAD of the value's ciphertext: a ciphertext
879
+ * copied onto another ref does not decrypt.
880
+ *
881
+ * Array items are addressed by ITEM ID, never by index
882
+ * (`providers[authentik].clientSecret`), so reordering a list keeps every ref.
883
+ *
884
+ * Pure and browser-safe: no node builtins, no I/O.
885
+ */
886
+ /** The URI scheme of every keychain ref. */
887
+ var SECRET_REF_SCHEME = "secret://";
888
+ var GLOBAL_SCOPE = { kind: "global" };
889
+ /** The scope every addon-wide (not per-node, not per-device) setting lives in. */
890
+ function globalSecretScope() {
891
+ return GLOBAL_SCOPE;
892
+ }
893
+ /**
894
+ * An addon id as a ref owner. A hub in-process builtin's context id is
895
+ * `addon:<id>`; the owner of its secrets is `<id>`, exactly as for the same
896
+ * addon running forked.
897
+ */
898
+ function normalizeSecretOwner(addonId) {
899
+ return addonId.startsWith("addon:") ? addonId.slice(6) : addonId;
900
+ }
901
+ function encodeSegment(value) {
902
+ return encodeURIComponent(value);
903
+ }
904
+ function decodeSegment(value) {
905
+ try {
906
+ return decodeURIComponent(value);
907
+ } catch {
908
+ return null;
909
+ }
910
+ }
911
+ /** The scope as it appears inside a ref (`global`, `node/<id>`, …). */
912
+ function formatSecretScope(scope) {
913
+ switch (scope.kind) {
914
+ case "global": return "global";
915
+ case "node": return `node/${encodeSegment(scope.nodeId)}`;
916
+ case "device": return `device/${scope.deviceId}`;
917
+ case "integration": return `integration/${encodeSegment(scope.integrationId)}`;
918
+ case "row": return `row/${encodeSegment(scope.collection)}/${encodeSegment(scope.rowId)}`;
919
+ }
920
+ }
921
+ /**
922
+ * The field path of a value inside an array item, addressed by the item's id:
923
+ * `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
924
+ * `providers[authentik].clientSecret`.
925
+ */
926
+ function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
927
+ return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
928
+ }
929
+ /** Build a ref. Throws on an empty owner or field path — both are bugs. */
930
+ function buildSecretRef(parts) {
931
+ const owner = normalizeSecretOwner(parts.owner);
932
+ if (owner.length === 0) throw new Error("buildSecretRef: owner is empty");
933
+ if (parts.fieldPath.length === 0) throw new Error("buildSecretRef: fieldPath is empty");
934
+ return `${SECRET_REF_SCHEME}${encodeSegment(owner)}/${formatSecretScope(parts.scope)}/${parts.fieldPath}`;
935
+ }
936
+ /** True when `value` is shaped like a keychain ref (not that it exists). */
937
+ function isSecretRef(value) {
938
+ return typeof value === "string" && parseSecretRef(value) !== null;
939
+ }
940
+ function parseScope(segments) {
941
+ switch (segments[0]) {
942
+ case "global": return {
943
+ scope: GLOBAL_SCOPE,
944
+ used: 1
945
+ };
946
+ case "node": {
947
+ const nodeId = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
948
+ return nodeId === null || nodeId.length === 0 ? null : {
949
+ scope: {
950
+ kind: "node",
951
+ nodeId
952
+ },
953
+ used: 2
954
+ };
955
+ }
956
+ case "device": {
957
+ const raw = segments[1];
958
+ if (raw === void 0 || !/^\d+$/.test(raw)) return null;
959
+ return {
960
+ scope: {
961
+ kind: "device",
962
+ deviceId: Number(raw)
963
+ },
964
+ used: 2
965
+ };
966
+ }
967
+ case "integration": {
968
+ const id = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
969
+ return id === null || id.length === 0 ? null : {
970
+ scope: {
971
+ kind: "integration",
972
+ integrationId: id
973
+ },
974
+ used: 2
975
+ };
976
+ }
977
+ case "row": {
978
+ const collection = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
979
+ const rowId = segments[2] !== void 0 ? decodeSegment(segments[2]) : null;
980
+ if (collection === null || rowId === null || collection.length === 0 || rowId.length === 0) return null;
981
+ return {
982
+ scope: {
983
+ kind: "row",
984
+ collection,
985
+ rowId
986
+ },
987
+ used: 3
988
+ };
989
+ }
990
+ default: return null;
991
+ }
992
+ }
993
+ /** Take a ref apart; `null` when it is not a well-formed ref. */
994
+ function parseSecretRef(ref) {
995
+ if (!ref.startsWith("secret://")) return null;
996
+ const segments = ref.slice(9).split("/");
997
+ const owner = segments[0] !== void 0 ? decodeSegment(segments[0]) : null;
998
+ if (owner === null || owner.length === 0) return null;
999
+ const parsed = parseScope(segments.slice(1));
1000
+ if (parsed === null) return null;
1001
+ const fieldPath = segments.slice(1 + parsed.used).join("/");
1002
+ if (fieldPath.length === 0) return null;
1003
+ return {
1004
+ owner,
1005
+ scope: parsed.scope,
1006
+ fieldPath
1007
+ };
1008
+ }
1009
+ //#endregion
1010
+ //#region src/addon/secret-settings-view.ts
1011
+ /** A write to a secret path could not be stored in the keychain, so it was refused. */
1012
+ var KeychainWriteRefusedError = class extends Error {
1013
+ fieldPath;
1014
+ constructor(fieldPath, cause) {
1015
+ super(`secret "${fieldPath}" was not saved: the keychain could not store it (${cause instanceof Error ? cause.message : String(cause)}). Nothing was written.`);
1016
+ this.fieldPath = fieldPath;
1017
+ this.name = "KeychainWriteRefusedError";
1018
+ }
1019
+ };
1020
+ var KEEP = { kind: "keep" };
1021
+ function isRecord(value) {
1022
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1023
+ }
1024
+ function defaultMintId() {
1025
+ return globalThis.crypto.randomUUID();
1026
+ }
1027
+ /** Every marker ref anywhere under `value`. */
1028
+ function collectSecretMarkerRefs(value, out = []) {
1029
+ if (isSecretMarker(value)) {
1030
+ out.push(value.$secret);
1031
+ return out;
1032
+ }
1033
+ if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
1034
+ else if (isRecord(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
1035
+ return out;
1036
+ }
1037
+ async function storedValueEquals(secrets, marker, candidate) {
1038
+ const resolved = (await secrets.resolve([marker.$secret]))[marker.$secret];
1039
+ return resolved?.kind === "value" && resolved.value === candidate;
1040
+ }
1041
+ async function writeLeaf(ctx, incoming, stored, fieldPath) {
1042
+ const intent = classifySecretWrite(incoming);
1043
+ const ref = ctx.secrets.ref(ctx.scope, fieldPath);
1044
+ const storedIsOurs = isSecretMarker(stored) && stored.$secret === ref;
1045
+ if (intent.kind === "keep") return KEEP;
1046
+ if (intent.kind === "set" && !ctx.operatorWrite && typeof stored === "string" && stored === intent.value) return KEEP;
1047
+ if (intent.kind === "clear") {
1048
+ if (storedIsOurs) ctx.deletes.push(ref);
1049
+ return {
1050
+ kind: "value",
1051
+ value: null
1052
+ };
1053
+ }
1054
+ if (storedIsOurs && await storedValueEquals(ctx.secrets, stored, intent.value)) return {
1055
+ kind: "value",
1056
+ value: stored
1057
+ };
1058
+ try {
1059
+ await ctx.secrets.put(ref, intent.value);
1060
+ } catch (err) {
1061
+ throw new KeychainWriteRefusedError(fieldPath, err);
1062
+ }
1063
+ return {
1064
+ kind: "value",
1065
+ value: makeSecretMarker(ref)
1066
+ };
1067
+ }
1068
+ /** Apply a member result onto `out`: KEEP restores the stored member, or removes it. */
1069
+ function applyMember(out, key, result, storedParent) {
1070
+ if (result.kind === "value") {
1071
+ out[key] = result.value;
1072
+ return;
1073
+ }
1074
+ if (Object.prototype.hasOwnProperty.call(storedParent, key)) out[key] = storedParent[key];
1075
+ else delete out[key];
1076
+ }
1077
+ async function writeMembers(ctx, children, incoming, stored, pathOf) {
1078
+ const out = { ...incoming };
1079
+ for (const [member, node] of children) applyMember(out, member, await writeNode(ctx, node, incoming[member], stored[member], pathOf(member)), stored);
1080
+ return out;
1081
+ }
1082
+ function rowId(row, idKey) {
1083
+ const raw = row[idKey];
1084
+ if (typeof raw === "string" && raw.length > 0) return raw;
1085
+ if (typeof raw === "number" && Number.isFinite(raw)) return String(raw);
1086
+ return null;
1087
+ }
1088
+ async function writeArray(ctx, node, incoming, stored, fieldPath) {
1089
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord) : [];
1090
+ if (!Array.isArray(incoming)) {
1091
+ ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
1092
+ return {
1093
+ kind: "value",
1094
+ value: incoming
1095
+ };
1096
+ }
1097
+ const storedById = /* @__PURE__ */ new Map();
1098
+ for (const row of storedRows) {
1099
+ const id = rowId(row, node.idKey);
1100
+ if (id !== null) storedById.set(id, row);
1101
+ }
1102
+ const kept = /* @__PURE__ */ new Set();
1103
+ const rows = [];
1104
+ for (const row of incoming) {
1105
+ if (!isRecord(row)) {
1106
+ rows.push(row);
1107
+ continue;
1108
+ }
1109
+ const id = rowId(row, node.idKey) ?? ctx.mintId();
1110
+ kept.add(id);
1111
+ const withId = {
1112
+ ...row,
1113
+ [node.idKey]: row[node.idKey] ?? id
1114
+ };
1115
+ if (rowId(withId, node.idKey) === null) withId[node.idKey] = id;
1116
+ const storedRow = storedById.get(id) ?? {};
1117
+ rows.push(await writeMembers(ctx, node.item, withId, storedRow, (member) => arrayItemFieldPath(fieldPath, id, member)));
1118
+ }
1119
+ for (const [id, row] of storedById) if (!kept.has(id)) ctx.deletes.push(...collectSecretMarkerRefs(row));
1120
+ return {
1121
+ kind: "value",
1122
+ value: rows
1123
+ };
1124
+ }
1125
+ async function writeNode(ctx, node, incoming, stored, fieldPath) {
1126
+ switch (node.kind) {
1127
+ case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
1128
+ case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
1129
+ case "object": {
1130
+ if (!isRecord(incoming)) {
1131
+ if (incoming === void 0) return KEEP;
1132
+ ctx.deletes.push(...collectSecretMarkerRefs(stored));
1133
+ return {
1134
+ kind: "value",
1135
+ value: incoming
1136
+ };
1137
+ }
1138
+ const storedObj = isRecord(stored) ? stored : {};
1139
+ return {
1140
+ kind: "value",
1141
+ value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
1142
+ };
1143
+ }
1144
+ }
1145
+ }
1146
+ /** Split a perNode-scoped key (`key@nodeId`). */
1147
+ function splitNodeKey(key) {
1148
+ const at = key.indexOf("@");
1149
+ return at <= 0 ? {
1150
+ base: key,
1151
+ nodeId: null
1152
+ } : {
1153
+ base: key.slice(0, at),
1154
+ nodeId: key.slice(at + 1)
1155
+ };
1156
+ }
1157
+ /** Rewrite a settings patch so no secret value reaches the blob. See the file doc. */
1158
+ async function transformSecretWrite(input) {
1159
+ const deletes = [];
1160
+ const base = {
1161
+ secrets: input.secrets,
1162
+ mintId: input.mintId ?? defaultMintId,
1163
+ deletes,
1164
+ operatorWrite: input.operatorWrite === true
1165
+ };
1166
+ const out = {};
1167
+ for (const [key, incoming] of Object.entries(input.patch)) {
1168
+ const { base: baseKey, nodeId } = splitNodeKey(key);
1169
+ const node = input.tree.get(baseKey);
1170
+ if (node === void 0) {
1171
+ out[key] = incoming;
1172
+ continue;
1173
+ }
1174
+ const scope = nodeId !== null && nodeId.length > 0 ? {
1175
+ kind: "node",
1176
+ nodeId
1177
+ } : input.scope ?? globalSecretScope();
1178
+ const result = await writeNode({
1179
+ ...base,
1180
+ scope
1181
+ }, node, incoming, input.stored[key], baseKey);
1182
+ if (result.kind === "value") out[key] = result.value;
1183
+ }
1184
+ return {
1185
+ patch: out,
1186
+ afterWrite: async () => {
1187
+ if (deletes.length === 0) return { orphaned: [] };
1188
+ try {
1189
+ await input.secrets.delete([...new Set(deletes)]);
1190
+ return { orphaned: [] };
1191
+ } catch {
1192
+ return { orphaned: [...new Set(deletes)] };
1193
+ }
1194
+ }
1195
+ };
1196
+ }
1197
+ /**
1198
+ * Replace every marker in `value` with its keychain value. Returns the SAME
1199
+ * reference when there is no marker (no allocation, no keychain call).
1200
+ */
1201
+ async function resolveSecretMarkers(value, secrets) {
1202
+ const refs = collectSecretMarkerRefs(value);
1203
+ if (refs.length === 0) return {
1204
+ value,
1205
+ unresolved: []
1206
+ };
1207
+ const answers = await secrets.resolve(refs);
1208
+ const unresolved = [];
1209
+ for (const ref of new Set(refs)) {
1210
+ const answer = answers[ref];
1211
+ if (answer === void 0) unresolved.push({
1212
+ ref,
1213
+ reason: "not-initialized"
1214
+ });
1215
+ else if (answer.kind === "unavailable") unresolved.push({
1216
+ ref,
1217
+ reason: answer.reason
1218
+ });
1219
+ else if (answer.kind === "missing" || answer.kind === "forbidden") unresolved.push({
1220
+ ref,
1221
+ reason: answer.kind
1222
+ });
1223
+ }
1224
+ const substitute = (v) => {
1225
+ if (isSecretMarker(v)) {
1226
+ const answer = answers[v.$secret];
1227
+ return answer?.kind === "value" ? {
1228
+ keep: true,
1229
+ value: answer.value
1230
+ } : {
1231
+ keep: false,
1232
+ value: void 0
1233
+ };
1234
+ }
1235
+ if (Array.isArray(v)) {
1236
+ const items = [];
1237
+ for (const item of v) {
1238
+ const s = substitute(item);
1239
+ if (s.keep) items.push(s.value);
1240
+ }
1241
+ return {
1242
+ keep: true,
1243
+ value: items
1244
+ };
1245
+ }
1246
+ if (isRecord(v)) {
1247
+ const out = {};
1248
+ for (const [k, child] of Object.entries(v)) {
1249
+ const s = substitute(child);
1250
+ if (s.keep) out[k] = s.value;
1251
+ }
1252
+ return {
1253
+ keep: true,
1254
+ value: out
1255
+ };
1256
+ }
1257
+ return {
1258
+ keep: true,
1259
+ value: v
1260
+ };
1261
+ };
1262
+ return {
1263
+ value: substitute(value).value,
1264
+ unresolved
1265
+ };
1266
+ }
1267
+ //#endregion
1268
+ //#region src/addon/secret-aware-context.ts
1269
+ function touchesTree(tree, patch) {
1270
+ if (tree.size === 0) return false;
1271
+ for (const key of Object.keys(patch)) {
1272
+ const at = key.indexOf("@");
1273
+ if (tree.has(at > 0 ? key.slice(0, at) : key)) return true;
1274
+ }
1275
+ return false;
1276
+ }
1277
+ /** The blob as stored now; a failed read REFUSES the write instead of guessing. */
1278
+ async function readStoredAddonBlob(raw) {
1279
+ const threeWay = raw.readAddonStoreResult;
1280
+ if (threeWay === void 0) return raw.readAddonStore();
1281
+ const result = await threeWay.call(raw);
1282
+ if (result.kind === "failed") throw new Error(`settings write refused: the stored blob could not be read (${result.error})`);
1283
+ return result.kind === "missing" ? {} : result.value;
1284
+ }
1285
+ function createSecretAwareSettings(raw, opts) {
1286
+ const chains = /* @__PURE__ */ new Map();
1287
+ const serialize = (key, fn) => {
1288
+ const next = (chains.get(key) ?? Promise.resolve()).then(fn, fn);
1289
+ const tail = next.catch(() => void 0);
1290
+ chains.set(key, tail);
1291
+ tail.then(() => {
1292
+ if (chains.get(key) === tail) chains.delete(key);
1293
+ });
1294
+ return next;
1295
+ };
1296
+ const write = async (chainKey, tree, scope, patch, readStored, rawWrite) => {
1297
+ if (!touchesTree(tree, patch)) return rawWrite(patch);
1298
+ return serialize(chainKey, async () => {
1299
+ const stored = await readStored();
1300
+ const result = await transformSecretWrite({
1301
+ tree,
1302
+ secrets: opts.secrets,
1303
+ patch,
1304
+ stored,
1305
+ ...scope !== void 0 ? { scope } : {},
1306
+ operatorWrite: opts.isOperatorWrite?.() === true
1307
+ });
1308
+ if (Object.keys(result.patch).length > 0) await rawWrite(result.patch);
1309
+ const { orphaned } = await result.afterWrite();
1310
+ if (orphaned.length > 0) opts.onOrphaned(orphaned);
1311
+ });
1312
+ };
1313
+ const wrapped = {
1314
+ writeAddonStore: (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p)),
1315
+ writeDeviceStore: (deviceId, patch) => write(`device:${deviceId}`, opts.deviceTree(), {
1316
+ kind: "device",
1317
+ deviceId
1318
+ }, patch, () => raw.readDeviceStore(deviceId), (p) => raw.writeDeviceStore(deviceId, p))
1319
+ };
1320
+ return new Proxy(raw, { get(target, prop, receiver) {
1321
+ if (prop === "writeAddonStore") return wrapped.writeAddonStore;
1322
+ if (prop === "writeDeviceStore") return wrapped.writeDeviceStore;
1323
+ const value = Reflect.get(target, prop, receiver);
1324
+ return typeof value === "function" ? value.bind(target) : value;
1325
+ } });
1326
+ }
1327
+ /** The context the addon sees: identical, except that its settings writes are secret-aware. */
1328
+ function withSecretAwareSettings(context, opts) {
1329
+ const raw = context.settings;
1330
+ if (raw === void 0) return context;
1331
+ const settings = createSecretAwareSettings(raw, opts);
1332
+ return new Proxy(context, { get(target, prop, receiver) {
1333
+ if (prop === "settings") return settings;
1334
+ return Reflect.get(target, prop, receiver);
1335
+ } });
1336
+ }
1337
+ //#endregion
1338
+ //#region src/addon/secret-paths.ts
1339
+ var LEAF = { kind: "leaf" };
1340
+ var DEFAULT_ITEM_ID_KEY = "id";
1341
+ function collectFields(fields, out) {
1342
+ for (const field of fields) {
1343
+ if (field.type === "group") {
1344
+ collectFields(field.fields, out);
1345
+ continue;
1346
+ }
1347
+ if (field.type === "sub-tabs") {
1348
+ for (const tab of field.tabs) collectFields(tab.fields, out);
1349
+ continue;
1350
+ }
1351
+ if (field.type === "editable-array") {
1352
+ const item = /* @__PURE__ */ new Map();
1353
+ for (const itemField of field.itemFields) if ("key" in itemField && isKeychainStoredField(itemField)) item.set(itemField.key, LEAF);
1354
+ if (item.size > 0) out.set(field.key, {
1355
+ kind: "array",
1356
+ idKey: field.itemIdKey ?? DEFAULT_ITEM_ID_KEY,
1357
+ item
1358
+ });
1359
+ continue;
1360
+ }
1361
+ if (!("key" in field) || typeof field.key !== "string") continue;
1362
+ if (isKeychainStoredField(field)) out.set(field.key, LEAF);
1363
+ }
1364
+ }
1365
+ /** The secret tree a settings schema declares. */
1366
+ function secretTreeFromSchema(schema) {
1367
+ const out = /* @__PURE__ */ new Map();
1368
+ if (schema === null) return out;
1369
+ for (const section of schema.sections) collectFields(section.fields, out);
1370
+ return out;
1371
+ }
1372
+ /** Parse `a.b[id].c` into segments; throws on a malformed path (a declaration bug). */
1373
+ function parseSecretStatePath(path) {
1374
+ const segments = [];
1375
+ for (const raw of path.split(".")) {
1376
+ const m = /^([^[\]]+)(?:\[([^[\]]+)\])?$/.exec(raw);
1377
+ if (m === null || m[1] === void 0) throw new Error(`secretStatePaths: malformed path "${path}"`);
1378
+ segments.push(m[2] !== void 0 ? {
1379
+ key: m[1],
1380
+ idKey: m[2]
1381
+ } : { key: m[1] });
1382
+ }
1383
+ if (segments.length === 0) throw new Error(`secretStatePaths: empty path`);
1384
+ if (segments[segments.length - 1]?.idKey !== void 0) throw new Error(`secretStatePaths: "${path}" ends on an array — name the member that is secret`);
1385
+ return segments;
1386
+ }
1387
+ function insert(into, segments) {
1388
+ const [head, ...rest] = segments;
1389
+ if (head === void 0) return;
1390
+ if (rest.length === 0) {
1391
+ into.set(head.key, LEAF);
1392
+ return;
1393
+ }
1394
+ const existing = into.get(head.key);
1395
+ if (head.idKey !== void 0) {
1396
+ const item = new Map(existing?.kind === "array" ? existing.item : []);
1397
+ insert(item, rest);
1398
+ into.set(head.key, {
1399
+ kind: "array",
1400
+ idKey: head.idKey,
1401
+ item
1402
+ });
1403
+ return;
1404
+ }
1405
+ const children = new Map(existing?.kind === "object" ? existing.children : []);
1406
+ insert(children, rest);
1407
+ into.set(head.key, {
1408
+ kind: "object",
1409
+ children
1410
+ });
1411
+ }
1412
+ /** Add hidden-state paths (`secretStatePaths`) to a schema-derived tree. */
1413
+ function mergeSecretStatePaths(tree, paths) {
1414
+ if (paths.length === 0) return tree;
1415
+ const out = new Map(tree);
1416
+ for (const path of paths) insert(out, parseSecretStatePath(path));
1417
+ return out;
1418
+ }
1419
+ /**
1420
+ * The tree of a DECLARED path list — a device's `secretConfigPaths`
1421
+ * (`collectZodSecretPaths`: dotted, `[]` for array items). An array segment
1422
+ * without a named id field is addressed by the item's `id`.
1423
+ */
1424
+ function secretTreeFromPaths(paths) {
1425
+ const out = /* @__PURE__ */ new Map();
1426
+ for (const path of paths) {
1427
+ if (path.length === 0) continue;
1428
+ insert(out, parseSecretStatePath(path.replace(/\[\]/g, `[${DEFAULT_ITEM_ID_KEY}]`)));
1429
+ }
1430
+ return out;
1431
+ }
1432
+ //#endregion
671
1433
  //#region src/addon/base-addon.ts
1434
+ /** First retry of a secret the keychain could not serve; doubles to {@link SECRET_RETRY_MAX_MS}. */
1435
+ var SECRET_RETRY_FIRST_MS = 5e3;
1436
+ var SECRET_RETRY_MAX_MS = 5 * 6e4;
1437
+ /** Coalesces a burst of `keychain.changed` events into one re-resolve. */
1438
+ var SECRET_CHANGE_DEBOUNCE_MS = 250;
672
1439
  /**
673
1440
  * Base class for CamStack addons. Eliminates settings boilerplate:
674
1441
  *
@@ -738,6 +1505,25 @@ var BaseAddon = class {
738
1505
  settingsStoreReady = false;
739
1506
  /** Default config values. Provided via constructor. */
740
1507
  defaults;
1508
+ /**
1509
+ * Hidden-state paths that hold secrets (D787) — values the addon writes
1510
+ * itself (`updateGlobalSettings`, `state()`) that no form field declares.
1511
+ * Stored in the keychain exactly like a `password` field. Syntax:
1512
+ * `'snapshotSigningKey'`, `'identity.pincode'`, `'tokens[userId].accessToken'`
1513
+ * (`key[<id field>]` addresses array items by id, never by index).
1514
+ */
1515
+ secretStatePaths = [];
1516
+ _addonSecretTree = null;
1517
+ _deviceSecretTree = null;
1518
+ /** Refs the last resolved config pointed at — a change to one re-resolves. */
1519
+ _configSecretRefs = /* @__PURE__ */ new Set();
1520
+ _unresolvedSecrets = [];
1521
+ _secretRetryTimer = null;
1522
+ _secretRetryDelayMs = SECRET_RETRY_FIRST_MS;
1523
+ _secretChangeTimer = null;
1524
+ _secretChangeUnsub = null;
1525
+ /** >0 while an OPERATOR's settings write is being applied. */
1526
+ _operatorWriteDepth = 0;
741
1527
  constructor(defaults) {
742
1528
  this.defaults = defaults;
743
1529
  this._config = { ...defaults };
@@ -773,8 +1559,18 @@ var BaseAddon = class {
773
1559
  return this._config;
774
1560
  }
775
1561
  async initialize(context) {
776
- this._ctx = context;
1562
+ const secrets = context.secrets;
1563
+ this._ctx = secrets !== void 0 ? withSecretAwareSettings(context, {
1564
+ secrets,
1565
+ addonTree: () => this.addonSecretTree(),
1566
+ deviceTree: () => this.deviceSecretTree(),
1567
+ onOrphaned: (refs) => context.logger.warn("keychain refs dropped by a settings write could not be deleted", { meta: { refs: [...refs] } }),
1568
+ isOperatorWrite: () => this._operatorWriteDepth > 0
1569
+ }) : context;
777
1570
  await this.resolveConfig();
1571
+ if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
1572
+ if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
1573
+ });
778
1574
  const result = await this.onInitialize();
779
1575
  this.emitLifecycle(require_event_category.EventCategory.AddonStarted);
780
1576
  const normalized = normalizeAddonInitResult(result);
@@ -807,6 +1603,12 @@ var BaseAddon = class {
807
1603
  */
808
1604
  async onHubReachable() {}
809
1605
  async shutdown() {
1606
+ this._secretChangeUnsub?.();
1607
+ this._secretChangeUnsub = null;
1608
+ if (this._secretRetryTimer !== null) clearTimeout(this._secretRetryTimer);
1609
+ if (this._secretChangeTimer !== null) clearTimeout(this._secretChangeTimer);
1610
+ this._secretRetryTimer = null;
1611
+ this._secretChangeTimer = null;
810
1612
  this.emitLifecycle(require_event_category.EventCategory.AddonStopped);
811
1613
  if (this.autoEmitReadiness) this.emitReadinessForProviders("down");
812
1614
  await this.onShutdown();
@@ -885,7 +1687,7 @@ var BaseAddon = class {
885
1687
  return hydrateSchema(schema, overlay ? {
886
1688
  ...projected,
887
1689
  ...overlay
888
- } : projected);
1690
+ } : projected, { inlineSecretsAsViews: true });
889
1691
  }
890
1692
  /**
891
1693
  * The integration-level view of this addon's settings: exactly the sections
@@ -934,7 +1736,7 @@ var BaseAddon = class {
934
1736
  return hydrateSchema({
935
1737
  ...schema,
936
1738
  sections
937
- }, projected);
1739
+ }, projected, { inlineSecretsAsViews: true });
938
1740
  }
939
1741
  /**
940
1742
  * The raw addon store PROJECTED onto the target node's bare per-node keys:
@@ -988,18 +1790,50 @@ var BaseAddon = class {
988
1790
  value: result.value[key]
989
1791
  };
990
1792
  }
1793
+ /**
1794
+ * The OPERATOR's write — a settings form or an admin API call, dispatched by
1795
+ * the kernel. Identical to {@link updateGlobalSettings}, except that a secret
1796
+ * the operator sends is stored as sent even when it equals a pre-keychain
1797
+ * inline value: the operator re-entering a password is a change, an addon
1798
+ * writing back what it read is not (D787).
1799
+ */
1800
+ async updateGlobalSettingsAsOperator(patch, nodeId) {
1801
+ this._operatorWriteDepth++;
1802
+ try {
1803
+ await this.applyGlobalPatch(patch, nodeId);
1804
+ } finally {
1805
+ this._operatorWriteDepth--;
1806
+ }
1807
+ }
991
1808
  async updateGlobalSettings(patch, nodeId) {
1809
+ await this.applyGlobalPatch(patch, nodeId);
1810
+ }
1811
+ /**
1812
+ * REMOVE stored secrets — their keychain values and their markers (D787).
1813
+ *
1814
+ * Writing `''` to a secret field KEEPS what is stored (an empty string is
1815
+ * what a defaults fallback writes back, and it must never destroy a
1816
+ * credential), so code that means "forget this token" says so here. Without
1817
+ * a keychain (an older kernel) it writes `''`, as such code always did.
1818
+ */
1819
+ async clearSecrets(keys, nodeId) {
1820
+ const tree = this.addonSecretTree();
1821
+ const keychain = this._ctx?.secrets !== void 0;
1822
+ const patch = {};
1823
+ for (const key of keys) patch[key] = keychain && tree.has(key) ? SECRET_CLEAR : "";
1824
+ await this.applyGlobalPatch(patch, nodeId);
1825
+ }
1826
+ async applyGlobalPatch(barePatch, nodeId) {
992
1827
  const keys = this.perNodeKeys();
993
1828
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
994
1829
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
995
- const barePatch = patch;
996
1830
  const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
997
1831
  await this._ctx?.settings?.writeAddonStore(scoped);
998
1832
  if (target !== localNode) return;
999
1833
  await this.resolveConfig();
1000
1834
  await this.onConfigChanged();
1001
1835
  this.emitLifecycle(require_event_category.EventCategory.AddonUpdated, { level: "global" });
1002
- this.maybeAutoRestart(patch, this.globalSettingsSchema());
1836
+ this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
1003
1837
  }
1004
1838
  /**
1005
1839
  * The set of field keys the global settings schema declares `perNode: true`
@@ -1024,7 +1858,7 @@ var BaseAddon = class {
1024
1858
  * time to return its response before the addon is torn down and
1025
1859
  * re-initialised by `AddonRegistryService.restartAddon`.
1026
1860
  */
1027
- maybeAutoRestart(patch, schema) {
1861
+ maybeAutoRestart(patchKeys, schema) {
1028
1862
  if (!schema) return;
1029
1863
  const restartKeys = /* @__PURE__ */ new Set();
1030
1864
  for (const section of schema.sections) for (const field of section.fields) {
@@ -1032,13 +1866,13 @@ var BaseAddon = class {
1032
1866
  if (field.requiresRestart) restartKeys.add(field.key);
1033
1867
  }
1034
1868
  if (restartKeys.size === 0) return;
1035
- if (!Object.keys(patch).some((k) => restartKeys.has(k))) return;
1869
+ if (!patchKeys.some((k) => restartKeys.has(k))) return;
1036
1870
  const ctx = this._ctx;
1037
1871
  if (!ctx) return;
1038
1872
  const addonId = ctx.id;
1039
1873
  setImmediate(() => {
1040
1874
  ctx.api.addons?.restartAddon?.mutate({ addonId }).then(() => {
1041
- ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: Object.keys(patch).filter((k) => restartKeys.has(k)) } });
1875
+ ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: patchKeys.filter((k) => restartKeys.has(k)) } });
1042
1876
  }).catch((err) => {
1043
1877
  ctx.logger.error("addon auto-restart failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
1044
1878
  });
@@ -1047,7 +1881,7 @@ var BaseAddon = class {
1047
1881
  async getDeviceSettings(deviceId) {
1048
1882
  const schema = this.deviceSettingsSchema();
1049
1883
  if (!schema) return { sections: [] };
1050
- return hydrateSchema(schema, await this._ctx?.settings?.readDeviceStore(deviceId) ?? {});
1884
+ return hydrateSchema(schema, await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, { inlineSecretsAsViews: true });
1051
1885
  }
1052
1886
  async updateDeviceSettings(deviceId, patch) {
1053
1887
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
@@ -1177,7 +2011,7 @@ var BaseAddon = class {
1177
2011
  * selection at boot (absent scoped key ⇒ the constructor default wins).
1178
2012
  */
1179
2013
  async resolveConfig() {
1180
- const stored = await this.readAddonStoreWithRetry();
2014
+ const stored = await this.resolveStoredSecrets(await this.readAddonStoreWithRetry());
1181
2015
  const perNode = this.perNodeKeys();
1182
2016
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
1183
2017
  const resolved = { ...this.defaults };
@@ -1202,7 +2036,7 @@ var BaseAddon = class {
1202
2036
  key,
1203
2037
  schema,
1204
2038
  fallback,
1205
- read: () => this.readAddonStoreWithRetry(),
2039
+ read: async () => this.resolveStoredSecrets(await this.readAddonStoreWithRetry()),
1206
2040
  write: async (patch) => {
1207
2041
  await this._ctx?.settings?.writeAddonStore(patch);
1208
2042
  },
@@ -1219,7 +2053,7 @@ var BaseAddon = class {
1219
2053
  key,
1220
2054
  schema,
1221
2055
  fallback,
1222
- read: async () => await this._ctx?.settings?.readDeviceStore(deviceId) ?? {},
2056
+ read: async () => this.resolveStoredSecrets(await this._ctx?.settings?.readDeviceStore(deviceId) ?? {}, false),
1223
2057
  write: async (patch) => {
1224
2058
  await this._ctx?.settings?.writeDeviceStore(deviceId, patch);
1225
2059
  },
@@ -1231,6 +2065,70 @@ var BaseAddon = class {
1231
2065
  } })
1232
2066
  });
1233
2067
  }
2068
+ /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
2069
+ addonSecretTree() {
2070
+ this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
2071
+ return this._addonSecretTree;
2072
+ }
2073
+ deviceSecretTree() {
2074
+ this._deviceSecretTree ??= secretTreeFromSchema(this.deviceSettingsSchema());
2075
+ return this._deviceSecretTree;
2076
+ }
2077
+ /**
2078
+ * Secrets of this addon's stored config that could not be resolved on the
2079
+ * last read, with why. Non-empty means the addon is running WITHOUT them —
2080
+ * an addon whose function needs one should report itself degraded rather
2081
+ * than start on an empty credential.
2082
+ */
2083
+ get unresolvedSecrets() {
2084
+ return this._unresolvedSecrets;
2085
+ }
2086
+ /**
2087
+ * Substitute every keychain marker in a stored blob with its value. With no
2088
+ * keychain (no `ctx.secrets`) or no marker, the blob comes back unchanged.
2089
+ * An unresolvable value is LEFT OUT (never `''`), logged by ref — never by
2090
+ * value — and, when the cause can pass, retried with backoff. Nothing is
2091
+ * ever written back from here.
2092
+ */
2093
+ async resolveStoredSecrets(blob, trackForConfig = true) {
2094
+ const secrets = this._ctx?.secrets;
2095
+ if (secrets === void 0) return blob;
2096
+ const refs = collectSecretMarkerRefs(blob);
2097
+ if (trackForConfig) this._configSecretRefs = new Set(refs);
2098
+ if (refs.length === 0) {
2099
+ if (trackForConfig) this._unresolvedSecrets = [];
2100
+ return blob;
2101
+ }
2102
+ const { value, unresolved } = await resolveSecretMarkers(blob, secrets);
2103
+ if (trackForConfig) {
2104
+ this._unresolvedSecrets = unresolved;
2105
+ if (unresolved.length > 0) {
2106
+ this._ctx?.logger?.error?.("secret unavailable — running without it, will retry", { meta: { secrets: unresolved.map((u) => ({
2107
+ ref: u.ref,
2108
+ reason: u.reason
2109
+ })) } });
2110
+ if (unresolved.some((u) => u.reason !== "missing" && u.reason !== "forbidden")) {
2111
+ this.scheduleSecretReresolve(this._secretRetryDelayMs);
2112
+ this._secretRetryDelayMs = Math.min(this._secretRetryDelayMs * 2, SECRET_RETRY_MAX_MS);
2113
+ }
2114
+ } else this._secretRetryDelayMs = SECRET_RETRY_FIRST_MS;
2115
+ }
2116
+ return value;
2117
+ }
2118
+ /** Re-resolve the config (a keychain-only change, or a retry) and apply it. */
2119
+ scheduleSecretReresolve(delayMs) {
2120
+ const timerOf = delayMs === SECRET_CHANGE_DEBOUNCE_MS ? "_secretChangeTimer" : "_secretRetryTimer";
2121
+ if (this[timerOf] !== null) return;
2122
+ const timer = setTimeout(() => {
2123
+ this[timerOf] = null;
2124
+ if (this._ctx === null) return;
2125
+ this.resolveConfig().then(() => this.onConfigChanged()).catch((err) => {
2126
+ this._ctx?.logger?.error?.("re-resolving secrets failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
2127
+ });
2128
+ }, delayMs);
2129
+ timer.unref?.();
2130
+ this[timerOf] = timer;
2131
+ }
1234
2132
  /**
1235
2133
  * Wrap `ctx.settings.readAddonStore()` with a short retry budget so a
1236
2134
  * transient settings-store outage (mid-restart of sqlite-settings, tsx-watch
@@ -1432,43 +2330,349 @@ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
1432
2330
  */
1433
2331
  var PEER_BYTES_TICKET_TTL_MS = 3e4;
1434
2332
  /**
1435
- * Tickets minted and not yet taken or expired, per addon process.
2333
+ * Tickets minted and not yet taken or expired, per addon process.
2334
+ *
2335
+ * A bound, not a budget: it exists so a caller that mints and never dials
2336
+ * cannot grow a map without limit, and so a per-frame misuse of this facility
2337
+ * is refused by name rather than working slowly.
2338
+ */
2339
+ var PEER_BYTES_MAX_OUTSTANDING = 64;
2340
+ /**
2341
+ * How long the producer waits on a consumer that has stopped reading before it
2342
+ * cuts the body.
2343
+ *
2344
+ * D447: a raw HTTP stream reads no chunk the socket has not taken. The producer
2345
+ * honours `write()`'s return and awaits `drain`; a peer that never drains is
2346
+ * a peer that is gone, and holding the body for it is the unbounded buffer this
2347
+ * whole facility exists to avoid.
2348
+ */
2349
+ var PEER_BYTES_STALL_MS = 15e3;
2350
+ /** Bytes handed to the socket per `write()` when the producer holds a buffer. */
2351
+ var PEER_BYTES_CHUNK_BYTES = 256 * 1024;
2352
+ /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
2353
+ var PeerBytesTicketSchema = zod.z.object({
2354
+ /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
2355
+ url: zod.z.string().min(1),
2356
+ /**
2357
+ * The HOST node this URL means something on — the hub or a named agent,
2358
+ * never a runner. {@link AddonPeerBytes.open} compares it to its own and
2359
+ * refuses `cross-node` by name when they differ, without dialling.
2360
+ */
2361
+ hostNodeId: zod.z.string().min(1),
2362
+ expiresAtMs: zod.z.number().int().nonnegative(),
2363
+ /**
2364
+ * What the producer DECLARED the body to be, when it knows — `null` when it
2365
+ * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
2366
+ * must be able to tell "the producer did not say" from "the body is empty".
2367
+ */
2368
+ declaredBytes: zod.z.number().int().nonnegative().nullable()
2369
+ });
2370
+ //#endregion
2371
+ //#region src/keychain/known-secrets.ts
2372
+ /**
2373
+ * The per-process set of secret VALUES this process has resolved, so the log
2374
+ * pipeline can scrub them wherever they turn up — a library's error message,
2375
+ * an argv a spawn logged, a URL someone interpolated.
2376
+ *
2377
+ * Key-name redaction cannot catch `Command failed: cloudflared --token eyJ…`:
2378
+ * the value is in a message, not under a named key. The only thing that can is
2379
+ * knowing the value. `ctx.secrets` registers every value it resolves (and every
2380
+ * value it stores), and `scrubKnownSecrets` replaces each occurrence with
2381
+ * `‹secret:<hash of its ref>›` — enough to say WHICH secret leaked, nothing of
2382
+ * what it is.
2383
+ *
2384
+ * Process-global on purpose: a forked runner scrubs its own logs before they
2385
+ * leave over UDS, and hub-main scrubs what it resolved. Values shorter than
2386
+ * {@link MIN_SCRUBBED_LENGTH} are not registered — a 4-digit PIN would turn
2387
+ * every timestamp containing it into a redaction.
2388
+ */
2389
+ /** Below this length a value is too likely to collide with ordinary text. */
2390
+ var MIN_SCRUBBED_LENGTH = 8;
2391
+ /**
2392
+ * One store per PROCESS, even when two copies of this module are loaded (an
2393
+ * addon bundle next to the framework's): keyed on a registered symbol.
2394
+ */
2395
+ var STORE_KEY = Symbol.for("camstack.keychain.known-secrets");
2396
+ function store() {
2397
+ const existing = Reflect.get(globalThis, STORE_KEY);
2398
+ if (existing !== void 0 && existing !== null) return existing;
2399
+ const created = {
2400
+ values: /* @__PURE__ */ new Map(),
2401
+ compiled: null
2402
+ };
2403
+ Reflect.set(globalThis, STORE_KEY, created);
2404
+ return created;
2405
+ }
2406
+ /** FNV-1a, 32-bit, as 8 hex chars — a label, not a security property. */
2407
+ function shortSecretRefHash(ref) {
2408
+ let hash = 2166136261;
2409
+ for (let i = 0; i < ref.length; i++) {
2410
+ hash ^= ref.charCodeAt(i);
2411
+ hash = Math.imul(hash, 16777619) >>> 0;
2412
+ }
2413
+ return hash.toString(16).padStart(8, "0");
2414
+ }
2415
+ function escapeRegExp(value) {
2416
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2417
+ }
2418
+ function recompile() {
2419
+ const s = store();
2420
+ if (s.values.size === 0) {
2421
+ s.compiled = null;
2422
+ return;
2423
+ }
2424
+ const values = [...s.values.keys()].toSorted((a, b) => b.length - a.length);
2425
+ s.compiled = new RegExp(values.map(escapeRegExp).join("|"), "g");
2426
+ }
2427
+ /** Remember a value this process holds. A no-op for short or empty values. */
2428
+ function registerKnownSecret(value, ref) {
2429
+ if (value.length < 8) return;
2430
+ const label = `‹secret:${shortSecretRefHash(ref)}›`;
2431
+ const values = store().values;
2432
+ if (values.get(value) === label) return;
2433
+ values.set(value, label);
2434
+ recompile();
2435
+ }
2436
+ /** Forget a value (it was replaced or deleted). */
2437
+ function forgetKnownSecret(value) {
2438
+ if (!store().values.delete(value)) return;
2439
+ recompile();
2440
+ }
2441
+ /** How many values are registered — for tests and the status page. */
2442
+ function knownSecretCount() {
2443
+ return store().values.size;
2444
+ }
2445
+ /** Test seam: drop every registered value. */
2446
+ function clearKnownSecrets() {
2447
+ const s = store();
2448
+ s.values.clear();
2449
+ s.compiled = null;
2450
+ }
2451
+ /** Replace every registered value inside `text` with its label. */
2452
+ function scrubKnownSecrets(text) {
2453
+ const s = store();
2454
+ const re = s.compiled;
2455
+ if (re === null || text.length < 8) return text;
2456
+ re.lastIndex = 0;
2457
+ if (!re.test(text)) return text;
2458
+ re.lastIndex = 0;
2459
+ return text.replace(re, (match) => s.values.get(match) ?? "‹secret›");
2460
+ }
2461
+ //#endregion
2462
+ //#region src/keychain/secrets-access.ts
2463
+ /** Re-fetch a cached value after this long, even with no change event. */
2464
+ var REVALIDATE_AFTER_MS = 15 * 6e4;
2465
+ function unavailableAll(refs) {
2466
+ const out = {};
2467
+ for (const ref of refs) out[ref] = {
2468
+ kind: "unavailable",
2469
+ reason: "not-initialized"
2470
+ };
2471
+ return out;
2472
+ }
2473
+ function createSecretsAccess(opts) {
2474
+ const owner = normalizeSecretOwner(opts.owner);
2475
+ const now = opts.now ?? Date.now;
2476
+ const cache = /* @__PURE__ */ new Map();
2477
+ const handlers = /* @__PURE__ */ new Set();
2478
+ /** One delegate handle per owner — each holds a cache and a subscription. */
2479
+ const delegates = /* @__PURE__ */ new Map();
2480
+ const drop = (ref) => {
2481
+ const entry = cache.get(ref);
2482
+ if (entry === void 0) return;
2483
+ cache.delete(ref);
2484
+ forgetKnownSecret(entry.value);
2485
+ for (const handler of handlers) try {
2486
+ handler(ref);
2487
+ } catch {}
2488
+ };
2489
+ opts.eventBus?.subscribe({ category: require_event_category.EventCategory.KeychainChanged }, (event) => {
2490
+ const data = event.data;
2491
+ if (data === null || typeof data !== "object") return;
2492
+ const ref = Reflect.get(data, "ref");
2493
+ const version = Reflect.get(data, "version");
2494
+ if (typeof ref !== "string") return;
2495
+ const entry = cache.get(ref);
2496
+ if (entry === void 0) return;
2497
+ if (typeof version === "number" && version === entry.version) return;
2498
+ drop(ref);
2499
+ });
2500
+ const remember = (ref, value, version) => {
2501
+ const previous = cache.get(ref);
2502
+ if (previous !== void 0 && previous.value !== value) forgetKnownSecret(previous.value);
2503
+ cache.set(ref, {
2504
+ value,
2505
+ version,
2506
+ fetchedAt: now()
2507
+ });
2508
+ registerKnownSecret(value, ref);
2509
+ };
2510
+ return {
2511
+ owner,
2512
+ ref: (scope, fieldPath) => buildSecretRef({
2513
+ owner,
2514
+ scope,
2515
+ fieldPath
2516
+ }),
2517
+ async resolve(refs) {
2518
+ const out = {};
2519
+ const toFetch = [];
2520
+ const t = now();
2521
+ for (const ref of new Set(refs)) {
2522
+ const hit = cache.get(ref);
2523
+ if (hit !== void 0 && t - hit.fetchedAt < 9e5) out[ref] = {
2524
+ kind: "value",
2525
+ value: hit.value,
2526
+ version: hit.version
2527
+ };
2528
+ else toFetch.push(ref);
2529
+ }
2530
+ if (toFetch.length === 0) return out;
2531
+ let fetched;
2532
+ try {
2533
+ fetched = await opts.transport.resolve({ refs: toFetch });
2534
+ } catch {
2535
+ fetched = unavailableAll(toFetch);
2536
+ }
2537
+ for (const ref of toFetch) {
2538
+ const entry = fetched[ref] ?? {
2539
+ kind: "unavailable",
2540
+ reason: "not-initialized"
2541
+ };
2542
+ if (entry.kind === "value") {
2543
+ remember(ref, entry.value, entry.version);
2544
+ out[ref] = entry;
2545
+ continue;
2546
+ }
2547
+ if (entry.kind === "unavailable") {
2548
+ const stale = cache.get(ref);
2549
+ out[ref] = stale !== void 0 ? {
2550
+ kind: "value",
2551
+ value: stale.value,
2552
+ version: stale.version
2553
+ } : entry;
2554
+ continue;
2555
+ }
2556
+ if (cache.has(ref)) drop(ref);
2557
+ out[ref] = entry;
2558
+ }
2559
+ return out;
2560
+ },
2561
+ async put(ref, value) {
2562
+ const result = await opts.transport.put({
2563
+ ref,
2564
+ value
2565
+ });
2566
+ remember(ref, value, result.version);
2567
+ return result.version;
2568
+ },
2569
+ async delete(refs) {
2570
+ if (refs.length === 0) return 0;
2571
+ const result = await opts.transport.delete({ refs });
2572
+ for (const ref of refs) drop(ref);
2573
+ return result.deleted;
2574
+ },
2575
+ state: () => opts.transport.getState(),
2576
+ onChange(handler) {
2577
+ handlers.add(handler);
2578
+ return () => {
2579
+ handlers.delete(handler);
2580
+ };
2581
+ },
2582
+ forOwner: opts.transportForOwner === void 0 ? null : (other) => {
2583
+ const target = normalizeSecretOwner(other);
2584
+ const existing = delegates.get(target);
2585
+ if (existing !== void 0) return existing;
2586
+ const build = opts.transportForOwner;
2587
+ if (build === void 0) throw new Error("forOwner: no delegate transport");
2588
+ const delegate = createSecretsAccess({
2589
+ owner: target,
2590
+ transport: build(target),
2591
+ ...opts.eventBus !== void 0 ? { eventBus: opts.eventBus } : {},
2592
+ now
2593
+ });
2594
+ delegates.set(target, delegate);
2595
+ return delegate;
2596
+ }
2597
+ };
2598
+ }
2599
+ //#endregion
2600
+ //#region src/keychain/resolve-secret-views.ts
2601
+ /**
2602
+ * Resolve the secret VIEWS a form sends as action input (D789).
1436
2603
  *
1437
- * A bound, not a budget: it exists so a caller that mints and never dials
1438
- * cannot grow a map without limit, and so a per-frame misuse of this facility
1439
- * is refused by name rather than working slowly.
1440
- */
1441
- var PEER_BYTES_MAX_OUTSTANDING = 64;
1442
- /**
1443
- * How long the producer waits on a consumer that has stopped reading before it
1444
- * cuts the body.
2604
+ * A settings form knows a stored secret only as `{ $secret: ref, set: true }`.
2605
+ * An action that reads one from the form (`paramsFromForm`, e.g. "list my
2606
+ * Cloudflare zones with the stored token") therefore receives the view. The
2607
+ * owner's process swaps each view for its value before the action's input
2608
+ * schema runs — through the owner's own `ctx.secrets`, so a view naming
2609
+ * another addon's ref resolves to nothing (owner-checked by the keychain).
1445
2610
  *
1446
- * D447: a raw HTTP stream reads no chunk the socket has not taken. The producer
1447
- * honours `write()`'s return and awaits `drain`; a peer that never drains is
1448
- * a peer that is gone, and holding the body for it is the unbounded buffer this
1449
- * whole facility exists to avoid.
2611
+ * A view that cannot be resolved (pre-keychain inline value, missing,
2612
+ * unavailable, forbidden) and an explicit clear instruction are DROPPED: the
2613
+ * action sees the parameter as absent, never as an object it would mis-read.
1450
2614
  */
1451
- var PEER_BYTES_STALL_MS = 15e3;
1452
- /** Bytes handed to the socket per `write()` when the producer holds a buffer. */
1453
- var PEER_BYTES_CHUNK_BYTES = 256 * 1024;
1454
- /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
1455
- var PeerBytesTicketSchema = zod.z.object({
1456
- /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
1457
- url: zod.z.string().min(1),
1458
- /**
1459
- * The HOST node this URL means something on — the hub or a named agent,
1460
- * never a runner. {@link AddonPeerBytes.open} compares it to its own and
1461
- * refuses `cross-node` by name when they differ, without dialling.
1462
- */
1463
- hostNodeId: zod.z.string().min(1),
1464
- expiresAtMs: zod.z.number().int().nonnegative(),
1465
- /**
1466
- * What the producer DECLARED the body to be, when it knows — `null` when it
1467
- * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
1468
- * must be able to tell "the producer did not say" from "the body is empty".
1469
- */
1470
- declaredBytes: zod.z.number().int().nonnegative().nullable()
1471
- });
2615
+ function viewRef(value) {
2616
+ if (!isSecretMarkerLike(value)) return null;
2617
+ const ref = Reflect.get(value, "$secret");
2618
+ return typeof ref === "string" && ref.length > 0 ? ref : null;
2619
+ }
2620
+ function collect(value, out) {
2621
+ if (isSecretClear(value) || isSecretMarkerLike(value)) {
2622
+ const ref = viewRef(value);
2623
+ if (ref !== null) out.add(ref);
2624
+ return true;
2625
+ }
2626
+ let found = false;
2627
+ if (Array.isArray(value)) for (const item of value) found = collect(item, out) || found;
2628
+ else if (value !== null && typeof value === "object") for (const child of Object.values(value)) found = collect(child, out) || found;
2629
+ return found;
2630
+ }
2631
+ async function resolveSecretViewsIn(args, secrets) {
2632
+ const refs = /* @__PURE__ */ new Set();
2633
+ if (!collect(args, refs)) return args;
2634
+ const answers = secrets !== void 0 && refs.size > 0 ? await secrets.resolve([...refs]) : {};
2635
+ const swap = (value) => {
2636
+ if (isSecretClear(value) || isSecretMarkerLike(value)) {
2637
+ const ref = viewRef(value);
2638
+ const answer = ref !== null ? answers[ref] : void 0;
2639
+ return answer?.kind === "value" ? {
2640
+ keep: true,
2641
+ value: answer.value
2642
+ } : {
2643
+ keep: false,
2644
+ value: void 0
2645
+ };
2646
+ }
2647
+ if (Array.isArray(value)) {
2648
+ const items = [];
2649
+ for (const item of value) {
2650
+ const s = swap(item);
2651
+ if (s.keep) items.push(s.value);
2652
+ }
2653
+ return {
2654
+ keep: true,
2655
+ value: items
2656
+ };
2657
+ }
2658
+ if (value !== null && typeof value === "object") {
2659
+ const out = {};
2660
+ for (const [k, child] of Object.entries(value)) {
2661
+ const s = swap(child);
2662
+ if (s.keep) out[k] = s.value;
2663
+ }
2664
+ return {
2665
+ keep: true,
2666
+ value: out
2667
+ };
2668
+ }
2669
+ return {
2670
+ keep: true,
2671
+ value
2672
+ };
2673
+ };
2674
+ return swap(args).value;
2675
+ }
1472
2676
  //#endregion
1473
2677
  //#region src/capabilities/schemas/streaming-shared.ts
1474
2678
  /** Shared Zod schemas used across streaming capabilities. */
@@ -2784,6 +3988,94 @@ var deviceOpsCapability = {
2784
3988
  }
2785
3989
  };
2786
3990
  //#endregion
3991
+ //#region src/device/zod-secret.ts
3992
+ /**
3993
+ * Secret declaration on a Zod config schema (D783).
3994
+ *
3995
+ * A field is a secret because its schema SAYS so — `.meta({ secret: true })`,
3996
+ * spelled {@link SECRET_META} — never because its key looks like a credential.
3997
+ * It is the Zod twin of `secret: true` / `type: 'password'` on a config-UI
3998
+ * field (`isSecretConfigField`), and every consumer that treats secrets
3999
+ * specially reads it: the settings form (`zodEntriesToConfigUI`), the paths a
4000
+ * device advertises at `registerDevice` (`secretConfigPaths`, which the
4001
+ * non-admin redaction withholds at any depth), and the keychain later.
4002
+ *
4003
+ * Zod keeps `.meta()` in `z.globalRegistry`, which lives on `globalThis`, so a
4004
+ * declaration made by an addon's own copy of zod is visible here.
4005
+ */
4006
+ /**
4007
+ * `{ secret: true }` — the one spelling of "this field holds a credential".
4008
+ *
4009
+ * Addons write the LITERAL `.meta({ secret: true })` rather than importing
4010
+ * this constant: an addon running against an older `@camstack/types` would
4011
+ * otherwise call `.meta(undefined)`, which in zod v4 is the metadata GETTER
4012
+ * and returns no schema at all.
4013
+ */
4014
+ var SECRET_META = { secret: true };
4015
+ function defOf(schema) {
4016
+ return schema._zod?.def ?? {};
4017
+ }
4018
+ function declaredHere(schema) {
4019
+ return zod.z.globalRegistry.get(schema)?.secret === true;
4020
+ }
4021
+ /** Wrapper kinds whose `innerType` is the field itself. */
4022
+ var WRAPPERS = new Set([
4023
+ "optional",
4024
+ "nullable",
4025
+ "default",
4026
+ "prefault",
4027
+ "catch",
4028
+ "readonly",
4029
+ "nonoptional"
4030
+ ]);
4031
+ /**
4032
+ * The schema layers that make up ONE field: the schema itself and every
4033
+ * wrapper below it (optional / default / nullable / readonly / catch), plus
4034
+ * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
4035
+ * `.transform` on `in`).
4036
+ */
4037
+ function layersOf(schema) {
4038
+ const out = [];
4039
+ const queue = [schema];
4040
+ while (queue.length > 0) {
4041
+ const s = queue.shift();
4042
+ if (!s || out.includes(s)) continue;
4043
+ out.push(s);
4044
+ const def = defOf(s);
4045
+ if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
4046
+ if (def.type === "pipe") {
4047
+ if (def.in) queue.push(def.in);
4048
+ if (def.out) queue.push(def.out);
4049
+ }
4050
+ }
4051
+ return out;
4052
+ }
4053
+ /** True when any layer of this field declares `.meta({ secret: true })`. */
4054
+ function isZodSecret(schema) {
4055
+ return layersOf(schema).some(declaredHere);
4056
+ }
4057
+ /**
4058
+ * Every declared secret path in a config schema: dotted keys, `[]` for "each
4059
+ * element of this array" (`connection.password`, `rows[].token`). A declared
4060
+ * object or array is reported as one path and not descended into — everything
4061
+ * under it is secret.
4062
+ */
4063
+ function collectZodSecretPaths(schema, prefix = "") {
4064
+ if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
4065
+ const paths = [];
4066
+ for (const layer of layersOf(schema)) {
4067
+ const def = defOf(layer);
4068
+ if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
4069
+ else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
4070
+ else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
4071
+ else if (def.type === "intersection") {
4072
+ if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
4073
+ if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
4074
+ }
4075
+ }
4076
+ return [...new Set(paths)];
4077
+ }
4078
+ //#endregion
2787
4079
  //#region src/device/device-state-handle.ts
2788
4080
  var DEVICE_STATE_EVENT_CATEGORY = "device.state-changed";
2789
4081
  /**
@@ -3415,6 +4707,18 @@ function createDeviceProxy(api, binding, opts) {
3415
4707
  completeRetrainTrack: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "completeRetrainTrack", "mutation", input),
3416
4708
  restageRetrainTrack: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "restageRetrainTrack", "mutation", input),
3417
4709
  getRetrainExportUrl: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getRetrainExportUrl", "query", input),
4710
+ setRetrainFrameComplete: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "setRetrainFrameComplete", "mutation", input),
4711
+ writeRetrainArchive: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "writeRetrainArchive", "mutation", input),
4712
+ listRetrainAssistProfiles: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRetrainAssistProfiles", "query", input),
4713
+ proposeRetrainAnnotationsWithLlm: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "proposeRetrainAnnotationsWithLlm", "mutation", input),
4714
+ createRetrainCollection: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "createRetrainCollection", "mutation", input),
4715
+ listRetrainCollections: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRetrainCollections", "query", input),
4716
+ renameRetrainCollection: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "renameRetrainCollection", "mutation", input),
4717
+ deleteRetrainCollection: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "deleteRetrainCollection", "mutation", input),
4718
+ addRetrainFrameFromRecording: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "addRetrainFrameFromRecording", "mutation", input),
4719
+ addRetrainFramesToCollection: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "addRetrainFramesToCollection", "mutation", input),
4720
+ removeRetrainFrameFromCollection: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "removeRetrainFrameFromCollection", "mutation", input),
4721
+ listRetrainCollectionFrames: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRetrainCollectionFrames", "query", input),
3418
4722
  getEventMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventMedia", "query", input),
3419
4723
  getTrackMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrackMedia", "query", input),
3420
4724
  listTrackMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listTrackMedia", "query", input),
@@ -4315,6 +5619,18 @@ Object.defineProperty(exports, "FrameHandleSchema", {
4315
5619
  return FrameHandleSchema;
4316
5620
  }
4317
5621
  });
5622
+ Object.defineProperty(exports, "KeychainWriteRefusedError", {
5623
+ enumerable: true,
5624
+ get: function() {
5625
+ return KeychainWriteRefusedError;
5626
+ }
5627
+ });
5628
+ Object.defineProperty(exports, "MIN_SCRUBBED_LENGTH", {
5629
+ enumerable: true,
5630
+ get: function() {
5631
+ return MIN_SCRUBBED_LENGTH;
5632
+ }
5633
+ });
4318
5634
  Object.defineProperty(exports, "PEER_BYTES_CHUNK_BYTES", {
4319
5635
  enumerable: true,
4320
5636
  get: function() {
@@ -4369,6 +5685,18 @@ Object.defineProperty(exports, "ProfileSlotStatusSchema", {
4369
5685
  return ProfileSlotStatusSchema;
4370
5686
  }
4371
5687
  });
5688
+ Object.defineProperty(exports, "REDACTED_SECRET", {
5689
+ enumerable: true,
5690
+ get: function() {
5691
+ return REDACTED_SECRET;
5692
+ }
5693
+ });
5694
+ Object.defineProperty(exports, "REVALIDATE_AFTER_MS", {
5695
+ enumerable: true,
5696
+ get: function() {
5697
+ return REVALIDATE_AFTER_MS;
5698
+ }
5699
+ });
4372
5700
  Object.defineProperty(exports, "RawStateResultSchema", {
4373
5701
  enumerable: true,
4374
5702
  get: function() {
@@ -4387,6 +5715,24 @@ Object.defineProperty(exports, "ReadinessTimeoutError", {
4387
5715
  return ReadinessTimeoutError;
4388
5716
  }
4389
5717
  });
5718
+ Object.defineProperty(exports, "SECRET_CLEAR", {
5719
+ enumerable: true,
5720
+ get: function() {
5721
+ return SECRET_CLEAR;
5722
+ }
5723
+ });
5724
+ Object.defineProperty(exports, "SECRET_META", {
5725
+ enumerable: true,
5726
+ get: function() {
5727
+ return SECRET_META;
5728
+ }
5729
+ });
5730
+ Object.defineProperty(exports, "SECRET_REF_SCHEME", {
5731
+ enumerable: true,
5732
+ get: function() {
5733
+ return SECRET_REF_SCHEME;
5734
+ }
5735
+ });
4390
5736
  Object.defineProperty(exports, "SHARE_VIEW_KINDS", {
4391
5737
  enumerable: true,
4392
5738
  get: function() {
@@ -4435,6 +5781,12 @@ Object.defineProperty(exports, "adminUiCapability", {
4435
5781
  return adminUiCapability;
4436
5782
  }
4437
5783
  });
5784
+ Object.defineProperty(exports, "arrayItemFieldPath", {
5785
+ enumerable: true,
5786
+ get: function() {
5787
+ return arrayItemFieldPath;
5788
+ }
5789
+ });
4438
5790
  Object.defineProperty(exports, "asBoolean", {
4439
5791
  enumerable: true,
4440
5792
  get: function() {
@@ -4471,6 +5823,24 @@ Object.defineProperty(exports, "audioChunkBytesPerSample", {
4471
5823
  return audioChunkBytesPerSample;
4472
5824
  }
4473
5825
  });
5826
+ Object.defineProperty(exports, "buildSecretRef", {
5827
+ enumerable: true,
5828
+ get: function() {
5829
+ return buildSecretRef;
5830
+ }
5831
+ });
5832
+ Object.defineProperty(exports, "classifySecretWrite", {
5833
+ enumerable: true,
5834
+ get: function() {
5835
+ return classifySecretWrite;
5836
+ }
5837
+ });
5838
+ Object.defineProperty(exports, "clearKnownSecrets", {
5839
+ enumerable: true,
5840
+ get: function() {
5841
+ return clearKnownSecrets;
5842
+ }
5843
+ });
4474
5844
  Object.defineProperty(exports, "collectHydratedFieldEntries", {
4475
5845
  enumerable: true,
4476
5846
  get: function() {
@@ -4483,6 +5853,30 @@ Object.defineProperty(exports, "collectHydratedFieldValues", {
4483
5853
  return collectHydratedFieldValues;
4484
5854
  }
4485
5855
  });
5856
+ Object.defineProperty(exports, "collectKeychainStoredKeys", {
5857
+ enumerable: true,
5858
+ get: function() {
5859
+ return collectKeychainStoredKeys;
5860
+ }
5861
+ });
5862
+ Object.defineProperty(exports, "collectSecretConfigKeys", {
5863
+ enumerable: true,
5864
+ get: function() {
5865
+ return collectSecretConfigKeys;
5866
+ }
5867
+ });
5868
+ Object.defineProperty(exports, "collectSecretMarkerRefs", {
5869
+ enumerable: true,
5870
+ get: function() {
5871
+ return collectSecretMarkerRefs;
5872
+ }
5873
+ });
5874
+ Object.defineProperty(exports, "collectZodSecretPaths", {
5875
+ enumerable: true,
5876
+ get: function() {
5877
+ return collectZodSecretPaths;
5878
+ }
5879
+ });
4486
5880
  Object.defineProperty(exports, "createDeviceProxy", {
4487
5881
  enumerable: true,
4488
5882
  get: function() {
@@ -4519,6 +5913,12 @@ Object.defineProperty(exports, "createMirrorSource", {
4519
5913
  return createMirrorSource;
4520
5914
  }
4521
5915
  });
5916
+ Object.defineProperty(exports, "createSecretsAccess", {
5917
+ enumerable: true,
5918
+ get: function() {
5919
+ return createSecretsAccess;
5920
+ }
5921
+ });
4522
5922
  Object.defineProperty(exports, "createSliceHandle", {
4523
5923
  enumerable: true,
4524
5924
  get: function() {
@@ -4585,6 +5985,24 @@ Object.defineProperty(exports, "expandCapMethods", {
4585
5985
  return expandCapMethods;
4586
5986
  }
4587
5987
  });
5988
+ Object.defineProperty(exports, "forgetKnownSecret", {
5989
+ enumerable: true,
5990
+ get: function() {
5991
+ return forgetKnownSecret;
5992
+ }
5993
+ });
5994
+ Object.defineProperty(exports, "formatSecretScope", {
5995
+ enumerable: true,
5996
+ get: function() {
5997
+ return formatSecretScope;
5998
+ }
5999
+ });
6000
+ Object.defineProperty(exports, "globalSecretScope", {
6001
+ enumerable: true,
6002
+ get: function() {
6003
+ return globalSecretScope;
6004
+ }
6005
+ });
4588
6006
  Object.defineProperty(exports, "hydrateSchema", {
4589
6007
  enumerable: true,
4590
6008
  get: function() {
@@ -4615,18 +6033,84 @@ Object.defineProperty(exports, "isEvent", {
4615
6033
  return isEvent;
4616
6034
  }
4617
6035
  });
6036
+ Object.defineProperty(exports, "isKeychainStoredField", {
6037
+ enumerable: true,
6038
+ get: function() {
6039
+ return isKeychainStoredField;
6040
+ }
6041
+ });
6042
+ Object.defineProperty(exports, "isSecretClear", {
6043
+ enumerable: true,
6044
+ get: function() {
6045
+ return isSecretClear;
6046
+ }
6047
+ });
6048
+ Object.defineProperty(exports, "isSecretConfigField", {
6049
+ enumerable: true,
6050
+ get: function() {
6051
+ return isSecretConfigField;
6052
+ }
6053
+ });
6054
+ Object.defineProperty(exports, "isSecretFieldView", {
6055
+ enumerable: true,
6056
+ get: function() {
6057
+ return isSecretFieldView;
6058
+ }
6059
+ });
6060
+ Object.defineProperty(exports, "isSecretMarker", {
6061
+ enumerable: true,
6062
+ get: function() {
6063
+ return isSecretMarker;
6064
+ }
6065
+ });
6066
+ Object.defineProperty(exports, "isSecretMarkerLike", {
6067
+ enumerable: true,
6068
+ get: function() {
6069
+ return isSecretMarkerLike;
6070
+ }
6071
+ });
6072
+ Object.defineProperty(exports, "isSecretRef", {
6073
+ enumerable: true,
6074
+ get: function() {
6075
+ return isSecretRef;
6076
+ }
6077
+ });
6078
+ Object.defineProperty(exports, "isZodSecret", {
6079
+ enumerable: true,
6080
+ get: function() {
6081
+ return isZodSecret;
6082
+ }
6083
+ });
6084
+ Object.defineProperty(exports, "knownSecretCount", {
6085
+ enumerable: true,
6086
+ get: function() {
6087
+ return knownSecretCount;
6088
+ }
6089
+ });
4618
6090
  Object.defineProperty(exports, "makeProfileBrokerId", {
4619
6091
  enumerable: true,
4620
6092
  get: function() {
4621
6093
  return makeProfileBrokerId;
4622
6094
  }
4623
6095
  });
6096
+ Object.defineProperty(exports, "makeSecretMarker", {
6097
+ enumerable: true,
6098
+ get: function() {
6099
+ return makeSecretMarker;
6100
+ }
6101
+ });
4624
6102
  Object.defineProperty(exports, "makeSourceBrokerId", {
4625
6103
  enumerable: true,
4626
6104
  get: function() {
4627
6105
  return makeSourceBrokerId;
4628
6106
  }
4629
6107
  });
6108
+ Object.defineProperty(exports, "mergeSecretStatePaths", {
6109
+ enumerable: true,
6110
+ get: function() {
6111
+ return mergeSecretStatePaths;
6112
+ }
6113
+ });
4630
6114
  Object.defineProperty(exports, "method", {
4631
6115
  enumerable: true,
4632
6116
  get: function() {
@@ -4651,6 +6135,12 @@ Object.defineProperty(exports, "normalizeNodeId", {
4651
6135
  return normalizeNodeId;
4652
6136
  }
4653
6137
  });
6138
+ Object.defineProperty(exports, "normalizeSecretOwner", {
6139
+ enumerable: true,
6140
+ get: function() {
6141
+ return normalizeSecretOwner;
6142
+ }
6143
+ });
4654
6144
  Object.defineProperty(exports, "optionalMethod", {
4655
6145
  enumerable: true,
4656
6146
  get: function() {
@@ -4681,6 +6171,18 @@ Object.defineProperty(exports, "parseProfileBrokerId", {
4681
6171
  return parseProfileBrokerId;
4682
6172
  }
4683
6173
  });
6174
+ Object.defineProperty(exports, "parseSecretRef", {
6175
+ enumerable: true,
6176
+ get: function() {
6177
+ return parseSecretRef;
6178
+ }
6179
+ });
6180
+ Object.defineProperty(exports, "parseSecretStatePath", {
6181
+ enumerable: true,
6182
+ get: function() {
6183
+ return parseSecretStatePath;
6184
+ }
6185
+ });
4684
6186
  Object.defineProperty(exports, "readNodePin", {
4685
6187
  enumerable: true,
4686
6188
  get: function() {
@@ -4693,6 +6195,12 @@ Object.defineProperty(exports, "readinessKey", {
4693
6195
  return readinessKey;
4694
6196
  }
4695
6197
  });
6198
+ Object.defineProperty(exports, "registerKnownSecret", {
6199
+ enumerable: true,
6200
+ get: function() {
6201
+ return registerKnownSecret;
6202
+ }
6203
+ });
4696
6204
  Object.defineProperty(exports, "resolveCapMount", {
4697
6205
  enumerable: true,
4698
6206
  get: function() {
@@ -4705,18 +6213,60 @@ Object.defineProperty(exports, "resolveHydratedFieldValue", {
4705
6213
  return resolveHydratedFieldValue;
4706
6214
  }
4707
6215
  });
6216
+ Object.defineProperty(exports, "resolveSecretMarkers", {
6217
+ enumerable: true,
6218
+ get: function() {
6219
+ return resolveSecretMarkers;
6220
+ }
6221
+ });
6222
+ Object.defineProperty(exports, "resolveSecretViewsIn", {
6223
+ enumerable: true,
6224
+ get: function() {
6225
+ return resolveSecretViewsIn;
6226
+ }
6227
+ });
6228
+ Object.defineProperty(exports, "schemaDeclaresAnyField", {
6229
+ enumerable: true,
6230
+ get: function() {
6231
+ return schemaDeclaresAnyField;
6232
+ }
6233
+ });
4708
6234
  Object.defineProperty(exports, "scopeKey", {
4709
6235
  enumerable: true,
4710
6236
  get: function() {
4711
6237
  return scopeKey;
4712
6238
  }
4713
6239
  });
6240
+ Object.defineProperty(exports, "scrubKnownSecrets", {
6241
+ enumerable: true,
6242
+ get: function() {
6243
+ return scrubKnownSecrets;
6244
+ }
6245
+ });
6246
+ Object.defineProperty(exports, "secretTreeFromPaths", {
6247
+ enumerable: true,
6248
+ get: function() {
6249
+ return secretTreeFromPaths;
6250
+ }
6251
+ });
6252
+ Object.defineProperty(exports, "secretTreeFromSchema", {
6253
+ enumerable: true,
6254
+ get: function() {
6255
+ return secretTreeFromSchema;
6256
+ }
6257
+ });
4714
6258
  Object.defineProperty(exports, "selectAssignedProfileSlots", {
4715
6259
  enumerable: true,
4716
6260
  get: function() {
4717
6261
  return selectAssignedProfileSlots;
4718
6262
  }
4719
6263
  });
6264
+ Object.defineProperty(exports, "shortSecretRefHash", {
6265
+ enumerable: true,
6266
+ get: function() {
6267
+ return shortSecretRefHash;
6268
+ }
6269
+ });
4720
6270
  Object.defineProperty(exports, "sleep", {
4721
6271
  enumerable: true,
4722
6272
  get: function() {
@@ -4741,6 +6291,18 @@ Object.defineProperty(exports, "toNodeId", {
4741
6291
  return toNodeId;
4742
6292
  }
4743
6293
  });
6294
+ Object.defineProperty(exports, "toSecretFieldView", {
6295
+ enumerable: true,
6296
+ get: function() {
6297
+ return toSecretFieldView;
6298
+ }
6299
+ });
6300
+ Object.defineProperty(exports, "transformSecretWrite", {
6301
+ enumerable: true,
6302
+ get: function() {
6303
+ return transformSecretWrite;
6304
+ }
6305
+ });
4744
6306
  Object.defineProperty(exports, "viewerUiCapability", {
4745
6307
  enumerable: true,
4746
6308
  get: function() {