@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
@@ -4,6 +4,7 @@ import type { CustomActionCaller, CustomActionsSpec } from '../capabilities/cust
4
4
  import type { PipelineSlot } from '../types/pipeline.js';
5
5
  import type { AddonDataPlane } from './addon-data-plane.js';
6
6
  import type { AddonPeerBytes } from './addon-peer-bytes.js';
7
+ import type { SecretsAccess } from '../keychain/secrets-access.js';
7
8
  import type { CapabilityDeclaration } from './capability.js';
8
9
  import type { ConfigUISchemaWithValues, FieldProbeResult } from './config-ui.js';
9
10
  import type { IEventBus, ReadinessScope } from './event-bus.js';
@@ -202,6 +203,14 @@ export interface IKernelServices {
202
203
  * dispatch from remote dispatch without a direct Moleculer dependency.
203
204
  */
204
205
  readonly localNodeId?: string;
206
+ /**
207
+ * The runner id hosting an addon — its `execution.group`, else itself
208
+ * (`resolveRunnerId`). Hub in-process contexts only: the keychain uses it to
209
+ * accept a grouped runner's transport stamp (the group id) for the refs of
210
+ * the addons that runner hosts. Absent elsewhere; callers fall back to
211
+ * identity.
212
+ */
213
+ readonly runnerIdForAddon?: (addonId: string) => string;
205
214
  /**
206
215
  * Local storage provider — direct filesystem access with zero serialization.
207
216
  * Use for file I/O (write/read with Buffer data) that can't go through tRPC.
@@ -695,6 +704,17 @@ export interface AddonContext<TConfig = Record<string, unknown>> {
695
704
  * (tests, tooling), which is why the field is optional.
696
705
  */
697
706
  readonly peerBytes?: AddonPeerBytes;
707
+ /**
708
+ * This addon's keychain values (D787). The owner is the addon's own id,
709
+ * closed over by the kernel; nothing passed to it can change that.
710
+ * `BaseAddon` uses it for every secret field of its schema (a marker in the
711
+ * blob, the value here) — reach for it directly only for values that are
712
+ * not settings (a business collection's credentials, a minted token).
713
+ *
714
+ * Absent when the addon is loaded in isolation (tests, tooling) or on a
715
+ * kernel that predates the keychain.
716
+ */
717
+ readonly secrets?: SecretsAccess;
698
718
  /**
699
719
  * Runtime accessor for the capability registry — exposed to addons that
700
720
  * need to enumerate peers of a collection cap (e.g. `turn-provider`) or
@@ -829,6 +849,11 @@ export interface ICamstackAddon {
829
849
  * persisted store. */
830
850
  getGlobalSettings?(overlay?: Record<string, unknown>, cap?: string, nodeId?: string): Promise<ConfigUISchemaWithValues | null>;
831
851
  updateGlobalSettings?(patch: Record<string, unknown>, nodeId?: string): Promise<void>;
852
+ /**
853
+ * The same write, made by the OPERATOR (D787): the kernel's settings
854
+ * dispatch prefers it when the addon has it.
855
+ */
856
+ updateGlobalSettingsAsOperator?(patch: Record<string, unknown>, nodeId?: string): Promise<void>;
832
857
  /**
833
858
  * Integration-level settings — the subset of the global schema the addon
834
859
  * declared as its INTEGRATION's configuration (`BaseAddon.
@@ -35,11 +35,23 @@ import type { ConfigField } from './config-ui.js';
35
35
  export declare const REDACTED_SECRET = "__camstack_redacted__";
36
36
  /** Is this field's stored value a credential? */
37
37
  export declare function isSecretConfigField(field: ConfigField): boolean;
38
+ /**
39
+ * Is this field's value stored in the KEYCHAIN (D787)? Every secret field is,
40
+ * by its TYPE, unless it opts out with `storage: 'inline'` — never by its name.
41
+ */
42
+ export declare function isKeychainStoredField(field: ConfigField): boolean;
38
43
  /**
39
44
  * Every config key in `schema` whose value is a secret, including keys nested
40
45
  * inside `group` / `sub-tabs` containers.
41
46
  */
42
47
  export declare function collectSecretConfigKeys(schema: unknown): ReadonlySet<string>;
48
+ /**
49
+ * Every top-level config key whose value the KEYCHAIN stores (D787) —
50
+ * {@link collectSecretConfigKeys} minus `storage: 'inline'` and minus JSON
51
+ * blobs (see {@link isKeychainStoredField}). What a form sends as
52
+ * `secretKeys` so the owner's door knows which values to keep out of the blob.
53
+ */
54
+ export declare function collectKeychainStoredKeys(schema: unknown): ReadonlySet<string>;
43
55
  /**
44
56
  * True when the value is a readable schema declaring at least one field of any
45
57
  * kind. Lets a caller tell "this provider has no secrets" from "this provider's
@@ -80,7 +80,7 @@ export interface ConfigSection {
80
80
  readonly immediate?: boolean;
81
81
  readonly fields: readonly ConfigField[];
82
82
  }
83
- export type ConfigField = ConfigTextField | ConfigNumberField | ConfigBooleanField | ConfigSelectField | ConfigMultiSelectField | ConfigTimezoneField | ConfigDateTimeField | ConfigColorField | ConfigPasswordField | ConfigTextAreaField | ConfigSliderField | ConfigTagsField | ConfigScheduleField | ConfigGroupField | ConfigSubTabsField | ConfigSeparatorField | ConfigInfoField | ConfigQrCodeField | ConfigObjectArrayField | ConfigEditableArrayField | ConfigModelSelectorField | ConfigStorageLocationField | ConfigProbeField | ConfigButtonField | ConfigNodeSelectField | ConfigNodeMultiSelectField | ConfigDeviceSelectField | ConfigDeviceMultiSelectField | ConfigNotificationTargetSelectField | ConfigNotificationTargetMultiSelectField | ConfigWidgetField | ConfigAddonActionSelectField | ConfigAddonActionButtonField | ConfigDeviceActionButtonField;
83
+ export type ConfigField = ConfigTextField | ConfigNumberField | ConfigBooleanField | ConfigSelectField | ConfigMultiSelectField | ConfigTimezoneField | ConfigDateTimeField | ConfigColorField | ConfigIconField | ConfigPasswordField | ConfigTextAreaField | ConfigSliderField | ConfigTagsField | ConfigScheduleField | ConfigGroupField | ConfigSubTabsField | ConfigSeparatorField | ConfigInfoField | ConfigQrCodeField | ConfigObjectArrayField | ConfigEditableArrayField | ConfigModelSelectorField | ConfigStorageLocationField | ConfigProbeField | ConfigButtonField | ConfigNodeSelectField | ConfigNodeMultiSelectField | ConfigDeviceSelectField | ConfigDeviceMultiSelectField | ConfigNotificationTargetSelectField | ConfigNotificationTargetMultiSelectField | ConfigWidgetField | ConfigAddonActionSelectField | ConfigAddonActionButtonField | ConfigDeviceActionButtonField;
84
84
  /**
85
85
  * Per-MODEL applicability of a pipeline-step setting (D214).
86
86
  *
@@ -148,6 +148,15 @@ export interface ConfigFieldBase {
148
148
  * non-admin session. It costs nothing when the field is already a password.
149
149
  */
150
150
  readonly secret?: boolean;
151
+ /**
152
+ * Where a SECRET field's value is stored (D787). Default for every field
153
+ * {@link isSecretConfigField} selects (`type: 'password'` or `secret: true`):
154
+ * `'keychain'` — the settings blob holds a `{"$secret": ref}` marker and the
155
+ * value lives in the encrypted keychain; the form sees "set / not set",
156
+ * never the value. `'inline'` opts out, for a NON-secret value that is merely
157
+ * drawn masked. Decided by the field's TYPE and this flag — never its name.
158
+ */
159
+ readonly storage?: 'keychain' | 'inline';
151
160
  /** Per-model knob applicability — see {@link ConfigFieldModelApplicability}. */
152
161
  readonly modelApplicability?: ConfigFieldModelApplicability;
153
162
  /**
@@ -389,6 +398,22 @@ export interface ConfigColorField extends ConfigFieldBase {
389
398
  type: 'color';
390
399
  presets?: string[];
391
400
  }
401
+ /**
402
+ * An icon chosen from the shared display-icon vocabulary (D780) —
403
+ * `DISPLAY_ICON_VOCABULARY` in `device/display-icon-vocabulary.ts`, the same
404
+ * keys the device and room pickers store. The form renders the shared
405
+ * `<IconPicker>`; the stored value is a vocabulary KEY, never a free-text
406
+ * lucide name. `default` must itself be a vocabulary key: clearing the picker
407
+ * writes it back, and a stored value outside the vocabulary (a pre-D780
408
+ * free-text name) is drawn as the default — readers resolve it with
409
+ * `resolveDisplayIconKey(value, default)`.
410
+ *
411
+ * COMPILE-TIME ONLY on the wire (`z.custom<ConfigField>()`): a form that
412
+ * predates this type renders it through its default branch.
413
+ */
414
+ export interface ConfigIconField extends ConfigFieldBase {
415
+ readonly type: 'icon';
416
+ }
392
417
  /** Select a single cluster node from the live topology. Options are
393
418
  * populated dynamically by the UI via useClusterNodes — no server-side
394
419
  * option list needed. */
@@ -700,7 +725,7 @@ export interface ConfigOption {
700
725
  *
701
726
  * Constraints inside `itemFields`:
702
727
  * - Only scalar field types are supported (text/number/select/
703
- * multiselect/password/boolean/textarea/color/tags/slider). Nested
728
+ * multiselect/password/boolean/textarea/color/icon/tags/slider). Nested
704
729
  * groups, sub-tabs, probes, button actions, and other arrays are
705
730
  * intentionally rejected — they conflate row-state with section-
706
731
  * state and the FormBuilder's flat onChange contract can't carry
@@ -729,6 +754,14 @@ export interface ConfigEditableArrayField extends ConfigFieldBase {
729
754
  * collapse to an em-dash. Falls back to `Row N` when not provided.
730
755
  */
731
756
  readonly rowTitleTemplate?: string;
757
+ /**
758
+ * The row field that IDENTIFIES a row (default `'id'`). A secret inside a row
759
+ * is stored under a ref addressed by this id — never by index — so a reorder
760
+ * keeps every ref, a removed row's refs are deleted, and a duplicated row's
761
+ * secret is "not set" rather than copied (D787). A row saved without one has
762
+ * an id minted for it.
763
+ */
764
+ readonly itemIdKey?: string;
732
765
  }
733
766
  export interface ConfigModelSelectorField extends ConfigFieldBase {
734
767
  type: 'model-selector';
@@ -876,6 +909,17 @@ export interface ConfigAddonActionButtonField extends ConfigFieldBase {
876
909
  * No-op on a form with no device.
877
910
  */
878
911
  includeDeviceId?: boolean;
912
+ /**
913
+ * Ask the operator for one value when the button is pressed and send it as
914
+ * `input[param]`. For a one-shot argument that must not live in the saved
915
+ * settings (ids to delete, a code to send). Cancelling runs nothing.
916
+ */
917
+ prompt?: {
918
+ readonly param: string;
919
+ readonly title?: string;
920
+ readonly message?: string;
921
+ readonly placeholder?: string;
922
+ };
879
923
  /** Toast/inline success message template. `{key}` placeholders are
880
924
  * resolved against the action result. */
881
925
  successMessage?: string;
@@ -943,6 +987,8 @@ export type ConfigFieldWithValue = (ConfigTextField & {
943
987
  readonly value: unknown;
944
988
  }) | (ConfigColorField & {
945
989
  readonly value: unknown;
990
+ }) | (ConfigIconField & {
991
+ readonly value: unknown;
946
992
  }) | (ConfigPasswordField & {
947
993
  readonly value: unknown;
948
994
  }) | (ConfigTextAreaField & {
@@ -1067,4 +1113,15 @@ export declare function resolveHydratedFieldValue(schema: unknown, key: string):
1067
1113
  * the schema default or `null`. This mirrors the old backend behaviour where
1068
1114
  * `FormBuilder` rendered `values[key] ?? field.default`.
1069
1115
  */
1070
- export declare function hydrateSchema(schema: ConfigUISchema, values: Record<string, unknown>): ConfigUISchemaWithValues;
1116
+ /** How `hydrateSchema` treats keychain-stored secret fields (D789). */
1117
+ export interface HydrateSchemaOptions {
1118
+ /**
1119
+ * `true` — the form is the addon's OWN settings form, saved through the
1120
+ * keychain-aware `BaseAddon` path: a pre-keychain inline value is shown as
1121
+ * "set" too, never as text. Omitted — only a keychain MARKER becomes a view;
1122
+ * an inline value hydrates as before, because forms saved through other
1123
+ * paths (a camera driver's settings) need the value they were given.
1124
+ */
1125
+ readonly inlineSecretsAsViews?: boolean;
1126
+ }
1127
+ export declare function hydrateSchema(schema: ConfigUISchema, values: Record<string, unknown>, options?: HydrateSchemaOptions): ConfigUISchemaWithValues;
@@ -743,6 +743,16 @@ export interface TopologyNode {
743
743
  */
744
744
  export type StreamHealthReason = 'stale-timeout' | 'recovered' | 'first-packet' | 'suspended' | 'resumed' | 'broker-destroyed';
745
745
  /** All known event categories with their typed payloads */
746
+ /**
747
+ * `keychain.changed` — a value was put or deleted. Never carries the value.
748
+ */
749
+ export type KeychainChangedPayload = {
750
+ readonly ref: string;
751
+ readonly owner: string;
752
+ /** The version now stored; `null` after a delete. */
753
+ readonly version: number | null;
754
+ readonly change: 'put' | 'delete';
755
+ };
746
756
  export interface EventCatalog {
747
757
  'system.boot': {
748
758
  mode: string;
@@ -753,6 +763,7 @@ export interface EventCatalog {
753
763
  'system.restarting': PendingRestartMarkerPayload;
754
764
  'system.restart-completed': PendingRestartMarkerPayload;
755
765
  'system.tls-cert-changed': TlsCertChangedPayload;
766
+ 'keychain.changed': KeychainChangedPayload;
756
767
  'update.available': {
757
768
  /**
758
769
  * `addon` and `server` are npm packages a node can install for itself.
@@ -305,4 +305,22 @@ export interface IPipelineRunnerProvider {
305
305
  }): Promise<{
306
306
  released: number;
307
307
  }>;
308
+ /**
309
+ * Node benchmark measurement on THIS node (D791). See
310
+ * `pipelineRunnerCapability.startNodeBench` for the contract.
311
+ */
312
+ startNodeBench(input: import('../pipeline/node-benchmark/schemas.js').NodeBenchRunnerInputRaw): Promise<{
313
+ accepted: true;
314
+ runId: string;
315
+ }>;
316
+ /** The run's status; `null` when this runner does not know `runId`. */
317
+ getNodeBenchStatus(input: {
318
+ readonly runId: string;
319
+ }): Promise<import('../pipeline/node-benchmark/schemas.js').NodeBenchRunnerStatus | null>;
320
+ /** Stop the run; what it finished is kept. */
321
+ abortNodeBench(input: {
322
+ readonly runId: string;
323
+ }): Promise<{
324
+ aborted: boolean;
325
+ }>;
308
326
  }
@@ -0,0 +1,5 @@
1
+ export { clearKnownSecrets, forgetKnownSecret, knownSecretCount, MIN_SCRUBBED_LENGTH, registerKnownSecret, scrubKnownSecrets, shortSecretRefHash, } from './known-secrets.js';
2
+ export { classifySecretWrite, isSecretClear, isSecretFieldView, isSecretMarker, isSecretMarkerLike, makeSecretMarker, SECRET_CLEAR, type SecretClearInstruction, type SecretFieldView, type SecretMarker, type SecretWriteIntent, toSecretFieldView, } from './secret-marker.js';
3
+ export { arrayItemFieldPath, buildSecretRef, formatSecretScope, globalSecretScope, isSecretRef, normalizeSecretOwner, parseSecretRef, SECRET_REF_SCHEME, type SecretRefParts, type SecretScope, } from './secret-ref.js';
4
+ export { createSecretsAccess, type KeychainTransport, REVALIDATE_AFTER_MS, type SecretsAccess, type SecretsAccessOptions, } from './secrets-access.js';
5
+ export { resolveSecretViewsIn } from './resolve-secret-views.js';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The per-process set of secret VALUES this process has resolved, so the log
3
+ * pipeline can scrub them wherever they turn up — a library's error message,
4
+ * an argv a spawn logged, a URL someone interpolated.
5
+ *
6
+ * Key-name redaction cannot catch `Command failed: cloudflared --token eyJ…`:
7
+ * the value is in a message, not under a named key. The only thing that can is
8
+ * knowing the value. `ctx.secrets` registers every value it resolves (and every
9
+ * value it stores), and `scrubKnownSecrets` replaces each occurrence with
10
+ * `‹secret:<hash of its ref>›` — enough to say WHICH secret leaked, nothing of
11
+ * what it is.
12
+ *
13
+ * Process-global on purpose: a forked runner scrubs its own logs before they
14
+ * leave over UDS, and hub-main scrubs what it resolved. Values shorter than
15
+ * {@link MIN_SCRUBBED_LENGTH} are not registered — a 4-digit PIN would turn
16
+ * every timestamp containing it into a redaction.
17
+ */
18
+ /** Below this length a value is too likely to collide with ordinary text. */
19
+ export declare const MIN_SCRUBBED_LENGTH = 8;
20
+ /** FNV-1a, 32-bit, as 8 hex chars — a label, not a security property. */
21
+ export declare function shortSecretRefHash(ref: string): string;
22
+ /** Remember a value this process holds. A no-op for short or empty values. */
23
+ export declare function registerKnownSecret(value: string, ref: string): void;
24
+ /** Forget a value (it was replaced or deleted). */
25
+ export declare function forgetKnownSecret(value: string): void;
26
+ /** How many values are registered — for tests and the status page. */
27
+ export declare function knownSecretCount(): number;
28
+ /** Test seam: drop every registered value. */
29
+ export declare function clearKnownSecrets(): void;
30
+ /** Replace every registered value inside `text` with its label. */
31
+ export declare function scrubKnownSecrets(text: string): string;
@@ -0,0 +1,2 @@
1
+ import type { SecretsAccess } from './secrets-access.js';
2
+ export declare function resolveSecretViewsIn(args: unknown, secrets: SecretsAccess | undefined): Promise<unknown>;
@@ -0,0 +1,52 @@
1
+ /** The marker a settings blob stores in place of a secret value. */
2
+ export interface SecretMarker {
3
+ readonly $secret: string;
4
+ readonly v: 1;
5
+ }
6
+ /**
7
+ * What a read surface hands the UI for a secret field that holds something.
8
+ * `$secret` is `null` when the stored value predates the keychain (it is still
9
+ * plaintext in the blob until the operator's one-off migration moves it): the
10
+ * UI draws "set" either way and the value never leaves the hub.
11
+ */
12
+ export interface SecretFieldView {
13
+ readonly $secret: string | null;
14
+ readonly v: 1;
15
+ readonly set: true;
16
+ /** The stored value is still inline plaintext (pre-keychain). */
17
+ readonly inline?: true;
18
+ }
19
+ /** The explicit "remove this secret" instruction a form sends. */
20
+ export interface SecretClearInstruction {
21
+ readonly $secretClear: true;
22
+ }
23
+ export declare const SECRET_CLEAR: SecretClearInstruction;
24
+ export declare function makeSecretMarker(ref: string): SecretMarker;
25
+ /** A stored marker: an object whose `$secret` is a non-empty string. */
26
+ export declare function isSecretMarker(value: unknown): value is SecretMarker;
27
+ /** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
28
+ export declare function isSecretMarkerLike(value: unknown): boolean;
29
+ export declare function isSecretClear(value: unknown): value is SecretClearInstruction;
30
+ export declare function isSecretFieldView(value: unknown): value is SecretFieldView;
31
+ /**
32
+ * The UI view of a stored secret field value: a marker becomes "set", inline
33
+ * plaintext becomes "set (inline)", nothing becomes `null`. A value never
34
+ * survives this function.
35
+ */
36
+ export declare function toSecretFieldView(stored: unknown): SecretFieldView | null;
37
+ /** How an incoming value for a secret path is to be treated. */
38
+ export type SecretWriteIntent = {
39
+ readonly kind: 'keep';
40
+ } | {
41
+ readonly kind: 'clear';
42
+ } | {
43
+ readonly kind: 'set';
44
+ readonly value: string;
45
+ };
46
+ /**
47
+ * Classify an incoming value for a secret path. Only a non-empty string that is
48
+ * not a redaction token sets a value; only {@link SECRET_CLEAR} clears.
49
+ * Everything else — a marker (ours or anyone's), a UI view, `''`, `null`,
50
+ * `undefined`, a redaction token, a non-string — keeps what is stored.
51
+ */
52
+ export declare function classifySecretWrite(incoming: unknown): SecretWriteIntent;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
3
+ *
4
+ * A ref is DETERMINISTIC from where the value lives — the owner addon, the
5
+ * scope (global, one node, one device, one integration, one row of a business
6
+ * collection) and the field path inside that scope — so saving the same field
7
+ * twice updates the same ref, and the ref itself says which settings blob
8
+ * points at it. It is also the AAD of the value's ciphertext: a ciphertext
9
+ * copied onto another ref does not decrypt.
10
+ *
11
+ * Array items are addressed by ITEM ID, never by index
12
+ * (`providers[authentik].clientSecret`), so reordering a list keeps every ref.
13
+ *
14
+ * Pure and browser-safe: no node builtins, no I/O.
15
+ */
16
+ /** The URI scheme of every keychain ref. */
17
+ export declare const SECRET_REF_SCHEME = "secret://";
18
+ /** Where, inside its owner, a secret lives. */
19
+ export type SecretScope = {
20
+ readonly kind: 'global';
21
+ } | {
22
+ readonly kind: 'node';
23
+ readonly nodeId: string;
24
+ } | {
25
+ readonly kind: 'device';
26
+ readonly deviceId: number;
27
+ } | {
28
+ readonly kind: 'integration';
29
+ readonly integrationId: string;
30
+ } | {
31
+ readonly kind: 'row';
32
+ readonly collection: string;
33
+ readonly rowId: string;
34
+ };
35
+ /** A ref, taken apart. */
36
+ export interface SecretRefParts {
37
+ readonly owner: string;
38
+ readonly scope: SecretScope;
39
+ readonly fieldPath: string;
40
+ }
41
+ /** The scope every addon-wide (not per-node, not per-device) setting lives in. */
42
+ export declare function globalSecretScope(): SecretScope;
43
+ /**
44
+ * An addon id as a ref owner. A hub in-process builtin's context id is
45
+ * `addon:<id>`; the owner of its secrets is `<id>`, exactly as for the same
46
+ * addon running forked.
47
+ */
48
+ export declare function normalizeSecretOwner(addonId: string): string;
49
+ /** The scope as it appears inside a ref (`global`, `node/<id>`, …). */
50
+ export declare function formatSecretScope(scope: SecretScope): string;
51
+ /**
52
+ * The field path of a value inside an array item, addressed by the item's id:
53
+ * `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
54
+ * `providers[authentik].clientSecret`.
55
+ */
56
+ export declare function arrayItemFieldPath(arrayKey: string, itemId: string, fieldKey: string): string;
57
+ /** Build a ref. Throws on an empty owner or field path — both are bugs. */
58
+ export declare function buildSecretRef(parts: SecretRefParts): string;
59
+ /** True when `value` is shaped like a keychain ref (not that it exists). */
60
+ export declare function isSecretRef(value: unknown): value is string;
61
+ /** Take a ref apart; `null` when it is not a well-formed ref. */
62
+ export declare function parseSecretRef(ref: string): SecretRefParts | null;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * `ctx.secrets` — an addon's handle on its OWN keychain values.
3
+ *
4
+ * The owner is closed over at construction (the addon's id); nothing a caller
5
+ * passes can change it. How the call travels is the transport's business:
6
+ *
7
+ * - hub in-process builtin: the provider is in the same process, so the
8
+ * transport states the owner explicitly (`callerAddonId`) — the one place a
9
+ * claim is trusted, because no wire is involved;
10
+ * - forked runner / agent addon: the transport sends the bare input and the
11
+ * parent (or the agent, through `$hub-cap-fwd`) stamps who is calling.
12
+ *
13
+ * The cache holds `ref → {value, version}` with no expiry while the version is
14
+ * current. `keychain.changed` drops an entry; an entry older than
15
+ * {@link REVALIDATE_AFTER_MS} is re-fetched on its next use (the safety net
16
+ * for a dropped event, D8). A failed fetch answers the last good value when
17
+ * there is one — a locked keychain never turns a working credential into an
18
+ * empty one — and `unavailable` otherwise, never `missing`.
19
+ */
20
+ import type { KeychainResolveResult, KeychainState } from '../capabilities/keychain.cap.js';
21
+ import type { IEventBus } from '../interfaces/event-bus.js';
22
+ import { type SecretScope } from './secret-ref.js';
23
+ /** Re-fetch a cached value after this long, even with no change event. */
24
+ export declare const REVALIDATE_AFTER_MS: number;
25
+ /** The four calls a {@link SecretsAccess} makes. `callerAddonId` is set only in-process. */
26
+ export interface KeychainTransport {
27
+ put(input: {
28
+ readonly ref: string;
29
+ readonly value: string;
30
+ }): Promise<{
31
+ readonly ref: string;
32
+ readonly version: number;
33
+ }>;
34
+ resolve(input: {
35
+ readonly refs: readonly string[];
36
+ }): Promise<KeychainResolveResult>;
37
+ delete(input: {
38
+ readonly refs: readonly string[];
39
+ }): Promise<{
40
+ readonly deleted: number;
41
+ }>;
42
+ getState(): Promise<KeychainState>;
43
+ }
44
+ export interface SecretsAccess {
45
+ /** The owner every ref of this handle belongs to. */
46
+ readonly owner: string;
47
+ /** Build this owner's ref for a field. */
48
+ ref(scope: SecretScope, fieldPath: string): string;
49
+ /** Resolve refs in one call; cached values are served without one. */
50
+ resolve(refs: readonly string[]): Promise<KeychainResolveResult>;
51
+ /** Store a value. Throws while the keychain is locked. */
52
+ put(ref: string, value: string): Promise<number>;
53
+ /** Remove refs. Unknown refs are ignored. */
54
+ delete(refs: readonly string[]): Promise<number>;
55
+ /** Whether values can be stored right now. */
56
+ state(): Promise<KeychainState>;
57
+ /**
58
+ * Subscribe to changes of refs this handle has served. The handler runs
59
+ * after the cache dropped the ref. Returns the unsubscribe.
60
+ */
61
+ onChange(handler: (ref: string) => void): () => void;
62
+ /**
63
+ * In-process SYSTEM builtins only (device-manager, storage-orchestrator):
64
+ * act as a door for another owner's values. `null` everywhere else — a
65
+ * forked addon has no way to state an identity the transport would accept.
66
+ */
67
+ readonly forOwner: ((owner: string) => SecretsAccess) | null;
68
+ }
69
+ export interface SecretsAccessOptions {
70
+ readonly owner: string;
71
+ readonly transport: KeychainTransport;
72
+ /** Builds the transport for another owner — in-process system builtins only. */
73
+ readonly transportForOwner?: (owner: string) => KeychainTransport;
74
+ readonly eventBus?: Pick<IEventBus, 'subscribe'>;
75
+ readonly now?: () => number;
76
+ }
77
+ export declare function createSecretsAccess(opts: SecretsAccessOptions): SecretsAccess;
@@ -56,8 +56,8 @@ import type { IScopedLogger, LogExtras } from '../interfaces/logging.js';
56
56
  * to read it later.
57
57
  */
58
58
  export declare const LogChannelLevelSchema: z.ZodEnum<{
59
- error: "error";
60
59
  info: "info";
60
+ error: "error";
61
61
  warn: "warn";
62
62
  }>;
63
63
  /**
@@ -68,8 +68,8 @@ export declare const LogChannelDescriptorSchema: z.ZodObject<{
68
68
  name: z.ZodString;
69
69
  description: z.ZodString;
70
70
  defaultLevel: z.ZodEnum<{
71
- error: "error";
72
71
  info: "info";
72
+ error: "error";
73
73
  warn: "warn";
74
74
  }>;
75
75
  perDevice: z.ZodBoolean;