@camstack/system 1.2.201 → 1.2.203

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 (62) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +12 -0
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +37 -3
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +37 -3
  10. package/dist/builtins/console-logging/index.js +1 -1
  11. package/dist/builtins/console-logging/index.mjs +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  15. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  18. package/dist/builtins/hub-forwarder/index.js +1 -1
  19. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  24. package/dist/builtins/local-network/local-network.addon.js +1 -1
  25. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  26. package/dist/builtins/loki-logging/index.js +1 -1
  27. package/dist/builtins/loki-logging/index.mjs +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  30. package/dist/builtins/platform-probe/index.js +1 -1
  31. package/dist/builtins/platform-probe/index.mjs +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  34. package/dist/builtins/snapshot/index.js +1 -1
  35. package/dist/builtins/snapshot/index.mjs +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  38. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  40. package/dist/builtins/storage-orchestrator/drain-ratchet.d.ts +104 -0
  41. package/dist/builtins/storage-orchestrator/drain-state-store.d.ts +13 -0
  42. package/dist/builtins/storage-orchestrator/location-occupancy-slice.d.ts +47 -0
  43. package/dist/builtins/storage-orchestrator/location-protection.d.ts +20 -0
  44. package/dist/builtins/storage-orchestrator/location-root-collision.d.ts +44 -0
  45. package/dist/builtins/storage-orchestrator/location-store.d.ts +12 -3
  46. package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +9 -9
  47. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +47 -0
  48. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +865 -157
  49. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +865 -157
  50. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +75 -31
  51. package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +45 -0
  52. package/dist/builtins/system-config/system-config.addon.js +1 -1
  53. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  54. package/dist/builtins/winston-logging/index.js +1 -1
  55. package/dist/builtins/winston-logging/index.mjs +1 -1
  56. package/dist/{dist-CcV3k-hD.js → dist-BFvWv_bz.js} +933 -546
  57. package/dist/{dist-BmJNnb_s.mjs → dist-DcMUcdeq.mjs} +862 -547
  58. package/dist/index.js +1 -1
  59. package/dist/index.mjs +1 -1
  60. package/dist/{retired-settings-keys-nLVKlhe-.mjs → retired-settings-keys-CJfgnUSb.mjs} +1 -1
  61. package/dist/{retired-settings-keys-Bd8fuwO1.js → retired-settings-keys-Dd7K3K3r.js} +1 -1
  62. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ import { IScopedLogger, IStorageOccupancyProvider } from '@camstack/types';
2
+ /** What one location's owners hold there, merged. */
3
+ export interface LocationOwnedBytes {
4
+ readonly bytes: number;
5
+ /** The OLDEST contributing measurement — honest about the whole figure
6
+ * rather than about its freshest part. */
7
+ readonly measuredAtMs: number;
8
+ }
9
+ export interface OccupancySliceDeps {
10
+ readonly logger: IScopedLogger;
11
+ /** Registered `storage-occupancy` providers (cross-process proxies). */
12
+ readonly getProviders: () => readonly IStorageOccupancyProvider[];
13
+ /** Every location worth asking about, at refresh time. */
14
+ readonly locationIds: () => readonly string[];
15
+ readonly now: () => number;
16
+ /** How long a figure stays servable before a refresh is kicked. */
17
+ readonly staleAfterMs?: number;
18
+ }
19
+ export declare class LocationOccupancySlice {
20
+ private readonly deps;
21
+ private slice;
22
+ private refreshing;
23
+ private lastRefreshMs;
24
+ constructor(deps: OccupancySliceDeps);
25
+ /**
26
+ * What the last completed refresh saw for this location, or `undefined` when
27
+ * nothing has reported for it. There is deliberately no fallback: unknown is
28
+ * the truth about a location nobody owns bytes on.
29
+ */
30
+ ownedFor(locationId: string): LocationOwnedBytes | undefined;
31
+ /**
32
+ * Serve-and-revalidate. Called by every read: it returns immediately, and
33
+ * kicks a background refresh when the slice is older than the window. A read
34
+ * never awaits, and never joins a refresh already in flight (D224).
35
+ */
36
+ serveAndRevalidate(): void;
37
+ /**
38
+ * Ask every provider once and replace the slice.
39
+ *
40
+ * A provider that throws contributes NOTHING and is logged — it does not
41
+ * contribute a zero, and it does not take the other providers' answers down
42
+ * with it. A location that ends up with no contribution at all simply stays
43
+ * out of the slice, which is what makes it read back as unknown.
44
+ */
45
+ refresh(): Promise<void>;
46
+ private runRefresh;
47
+ }
@@ -0,0 +1,20 @@
1
+ /** statfs of the location's volume, when this node could take it. */
2
+ export interface LocationVolumeCapacity {
3
+ readonly totalBytes: number;
4
+ readonly availableBytes: number;
5
+ }
6
+ /** One line, at most once per location per process. */
7
+ export type ProtectionNotice = (message: string, meta: Record<string, unknown>) => void;
8
+ /**
9
+ * The free-space percentage the guard enforces for one location.
10
+ *
11
+ * Order:
12
+ * 1. `config.minFreeGb`, converted against the volume total — the operator's
13
+ * criterion in the unit they set it in.
14
+ * 2. `config.minFreePercent` — the legacy per-location override, and the
15
+ * fallback when `minFreeGb` cannot be converted (no volume total: a
16
+ * remote-node or unstattable location). Falling back is the right
17
+ * direction — the alternative is a location with no guard at all.
18
+ * 3. the provider's declared default.
19
+ */
20
+ export declare function resolveMinFreePercent(config: Readonly<Record<string, unknown>>, providerDefaultPercent: number, capacity: LocationVolumeCapacity | null, notice: ProtectionNotice): number;
@@ -0,0 +1,44 @@
1
+ import { PhysicalRoot } from '@camstack/types/node';
2
+ /** The minimum a row must expose for this check. */
3
+ export interface RootCollisionCandidate {
4
+ readonly id: string;
5
+ readonly type: string;
6
+ /** Which node the path lives on. Two locations on DIFFERENT nodes can never
7
+ * collide, however identical their paths look. */
8
+ readonly nodeId?: string;
9
+ readonly config: Readonly<Record<string, unknown>>;
10
+ }
11
+ /** How this location's physical root was derived, or that it could not be. */
12
+ export type ResolveRoot = (basePath: string) => PhysicalRoot;
13
+ /** A same-type root collision, with everything the message needs to name it. */
14
+ export interface RootCollision {
15
+ readonly otherId: string;
16
+ readonly type: string;
17
+ readonly sharedRoot: string;
18
+ /** `equal` — the same directory. `nested` — one inside the other's tree. */
19
+ readonly kind: 'equal' | 'nested';
20
+ }
21
+ /** Why the check could not be made. Never a refusal — the caller warns. */
22
+ export interface RootCollisionUnknown {
23
+ readonly reason: 'no-base-path' | 'unresolvable';
24
+ readonly detail: string;
25
+ }
26
+ export type RootCollisionVerdict = {
27
+ readonly outcome: 'clear';
28
+ } | {
29
+ readonly outcome: 'collides';
30
+ readonly collision: RootCollision;
31
+ } | {
32
+ readonly outcome: 'unknown';
33
+ readonly unknown: RootCollisionUnknown;
34
+ };
35
+ /**
36
+ * Does `next` collide with anything already stored?
37
+ *
38
+ * `existing` is every other location; the row being EDITED is excluded by id,
39
+ * so re-saving a location without moving its root never refuses itself.
40
+ */
41
+ export declare function findRootCollision(next: RootCollisionCandidate, existing: Iterable<RootCollisionCandidate>, resolveRoot: ResolveRoot, storedSelf?: RootCollisionCandidate): RootCollisionVerdict;
42
+ /** The operator-facing refusal. Names the location it collides with and the
43
+ * shared root — a refusal that says only "conflict" makes the operator hunt. */
44
+ export declare function rootCollisionMessage(id: string, collision: RootCollision): string;
@@ -1,4 +1,11 @@
1
- import { ISettingsStoreProvider, StorageLocation } from '@camstack/types';
1
+ import { ISettingsStoreProvider, LegacyStorageLocationDefault, StorageLocation } from '@camstack/types';
2
+ /**
3
+ * A location as it comes back OFF DISK: the live record plus the retired
4
+ * `isDefault` column, which exists only so the orchestrator's boot migration
5
+ * can turn an old default into the enabled write set and say which row it came
6
+ * from. No other reader may take this type.
7
+ */
8
+ export type PersistedStorageLocation = StorageLocation & LegacyStorageLocationDefault;
2
9
  /**
3
10
  * Abstract storage interface for `StorageLocation` records. Methods are
4
11
  * async because production-side implementations hit SQLite — but the
@@ -6,8 +13,10 @@ import { ISettingsStoreProvider, StorageLocation } from '@camstack/types';
6
13
  * disk doesn't block the cap surface.
7
14
  */
8
15
  export interface ILocationStore {
9
- /** Read every persisted location. Called once at boot. */
10
- loadAll(): Promise<readonly StorageLocation[]>;
16
+ /** Read every persisted location. Called once at boot. Rows carry the
17
+ * RETIRED `isDefault` column when it was set, for the one reader allowed to
18
+ * see it: the orchestrator's boot migration onto the `enabled` write set. */
19
+ loadAll(): Promise<readonly PersistedStorageLocation[]>;
11
20
  /** Insert or replace a single location by id. */
12
21
  upsert(loc: StorageLocation): Promise<void>;
13
22
  /** Remove a location by id. */
@@ -125,7 +125,7 @@ export declare class StorageMigrationCoordinator {
125
125
  * remaining" action.
126
126
  *
127
127
  * A class appears here only when something is (or might be) left on a
128
- * location that is not its default. An empty result therefore means exactly
128
+ * location outside its write set. An empty result therefore means exactly
129
129
  * "there is nothing to drain", which is what lets the UI offer the action
130
130
  * only when it is true, and what lets {@link drain} refuse rather than start
131
131
  * a job that would move nothing and report `done` — the failure mode D295
@@ -137,12 +137,12 @@ export declare class StorageMigrationCoordinator {
137
137
  */
138
138
  residue(): Promise<readonly StorageMigrationResidue[]>;
139
139
  /**
140
- * Run the DRAIN half alone, against classes whose default has already moved.
140
+ * Run the DRAIN half alone, against classes whose write set has already moved.
141
141
  *
142
142
  * ## Why this is a second verb rather than a looser `start`
143
143
  *
144
- * `start` refuses a destination that is already the class's default
145
- * (`"recordingsLow:ssd" is already the "recordingsLow" default`). That refusal
144
+ * `start` refuses a destination that is already the class's only write target
145
+ * (`"recordingsLow:ssd" is already the only write target for "recordingsLow"`). That refusal
146
146
  * is correct and it is load-bearing: there is genuinely nothing left to
147
147
  * repoint, and an operator must never be able to re-repoint a migrated class
148
148
  * by accident. Making `start` idempotent — "an already-repointed class
@@ -151,7 +151,7 @@ export declare class StorageMigrationCoordinator {
151
151
  * operator reads ("pauses the writers…") would be a lie half the time.
152
152
  *
153
153
  * `drain` instead cannot repoint AT ALL: it never touches
154
- * `setDefaultLocations`, and its job starts in `draining` with `repointed`
154
+ * `setWriteTargets`, and its job starts in `draining` with `repointed`
155
155
  * already true, so the `repointing` / `refreshing` / `resuming` blocks of
156
156
  * {@link run} are behind it and unreachable. The two verbs are disjoint, and
157
157
  * `start`'s refusal keeps meaning exactly what it meant.
@@ -161,7 +161,7 @@ export declare class StorageMigrationCoordinator {
161
161
  * The finished job is the audit of what happened; re-opening it destroys
162
162
  * that. And a drain is needed in cases where no migration job ever existed
163
163
  * (a mover armed by hand, footage stranded on a location an operator added
164
- * and then un-defaulted). One job = one operation, and the work list comes
164
+ * and then disabled again). One job = one operation, and the work list comes
165
165
  * from {@link residue} — the archive — not from what a previous job believed.
166
166
  */
167
167
  drain(input: StorageMigrationDrainInput): Promise<string>;
@@ -217,9 +217,9 @@ export declare class StorageMigrationCoordinator {
217
217
  private waitForSeal;
218
218
  private verifyMoves;
219
219
  /**
220
- * After a from→to copy whose source was NOT the class default: stop new
221
- * writes to `from`. The default is unchanged (that is the whole point of
222
- * naming a non-default source). A location that is already disabled is
220
+ * After a from→to copy whose source was NOT a write target: stop new writes
221
+ * to `from`. The rest of the write set is unchanged (that is the whole point
222
+ * of naming a source outside it). A location that is already disabled is
223
223
  * left alone.
224
224
  */
225
225
  private freezeDrainedSources;
@@ -4,6 +4,15 @@ interface StorageOrchestratorConfig extends Record<string, unknown> {
4
4
  export declare class StorageOrchestratorAddon extends BaseAddon<StorageOrchestratorConfig> {
5
5
  private service;
6
6
  private pressureManager;
7
+ /**
8
+ * How much of each location CamStack itself holds (D388).
9
+ *
10
+ * Built on first use rather than in a field initializer: `this.ctx` does not
11
+ * exist until `initialize()` runs, and a field initializer touching it throws
12
+ * in the constructor.
13
+ */
14
+ private occupancySlice;
15
+ private get occupancy();
7
16
  private pressureTimer;
8
17
  private pressureSweepInFlight;
9
18
  private migration;
@@ -98,6 +107,25 @@ export declare class StorageOrchestratorAddon extends BaseAddon<StorageOrchestra
98
107
  * the recorder so disk-pressure is a single core concern.
99
108
  */
100
109
  private resolveLocationThresholds;
110
+ /** Location ids that have already logged the `minFreeGb` / `minFreePercent`
111
+ * redundancy. The pressure sweep runs on a timer; the notice is worth
112
+ * exactly one line per location per process, not one per minute. */
113
+ private readonly minFreeRedundancyLogged;
114
+ private logMinFreeRedundancyOnce;
115
+ /**
116
+ * The draining locations and the ACTIVE peers of their class whose growth
117
+ * paces them (D386). Rebuilt every sweep from the live mode of every
118
+ * location, so this is also what STOPS a drain: a location the operator
119
+ * switched back simply stops appearing.
120
+ */
121
+ private drainStore;
122
+ /** The drain ratchet's durable home, once a data-store engine exists. Same
123
+ * late-wiring window as the location table — on an agent it is never. */
124
+ private resolveDrainStore;
125
+ private drainPlanRefusalLogged;
126
+ private resolveDrainPlan;
127
+ /** Drain progress for every draining location, as of the last sweep. */
128
+ private drainProgress;
101
129
  /**
102
130
  * What the provider's own schema declared secret, or `undefined` when this
103
131
  * node has never heard from that provider. The distinction is the whole
@@ -106,6 +134,25 @@ export declare class StorageOrchestratorAddon extends BaseAddon<StorageOrchestra
106
134
  private declaredSecretKeys;
107
135
  /** Which keys of a given stored config must never leave this process. */
108
136
  private secretKeysFor;
137
+ /**
138
+ * Ring the doorbell: the SET of storage locations changed (D387).
139
+ *
140
+ * A consumer that resolves locations at boot had no way to learn that the
141
+ * operator added, edited or removed one — the recorder kept writing to the
142
+ * disk set it resolved hours ago, and an operator who added a second
143
+ * recordings disk in the admin UI got nothing until something unrelated
144
+ * happened to re-resolve.
145
+ *
146
+ * Telemetry, and deliberately so (D8/D11): it carries no row, it is allowed
147
+ * to be dropped, and every consumer re-reads `listLocations` for itself and
148
+ * converges on its own periodic path anyway. A bulk edit rings it once per
149
+ * row — coalescing is the consumer's job, not this one's.
150
+ *
151
+ * Never throws into the mutation that produced it: the location IS saved,
152
+ * and a bus that will not take a telemetry event must not turn that into a
153
+ * failed upsert.
154
+ */
155
+ private announceLocationsChanged;
109
156
  /** A location with its provider's declared secrets replaced by the sentinel. */
110
157
  private redacted;
111
158
  /**