feat(api): absorb the Hub control plane into the local instance model
Add central notification channels, rules, queue and delivery logs, fleet organization groups and tags, device discovery, the device action catalog, instance module reads, the log centre, connection settings and system maintenance as native /api/v1 routes backed by the existing secret store, audit trail and pinned upstream transport.
This commit is contained in:
@@ -47,6 +47,18 @@ export interface StatusSnapshotServiceOptions {
|
||||
readonly maxStaleMs: number;
|
||||
readonly now?: () => Date;
|
||||
readonly idFactory?: () => string;
|
||||
/** Observes every probe outcome, including ones fenced from persistence by a revision change. */
|
||||
readonly onProbe?: (result: HealthProbeResult) => void;
|
||||
}
|
||||
|
||||
export interface HealthProbeResult {
|
||||
readonly instanceId: string;
|
||||
readonly outcome: 'success' | 'stale' | 'failed' | 'unsupported';
|
||||
readonly state: HealthSnapshotState;
|
||||
readonly errorCode: string | null;
|
||||
readonly httpStatus: number | null;
|
||||
readonly durationMs: number;
|
||||
readonly observedAt: string;
|
||||
}
|
||||
|
||||
export class StatusSnapshotError extends Error {
|
||||
@@ -193,6 +205,7 @@ export class StatusSnapshotService {
|
||||
readonly #maxStaleMs: number;
|
||||
readonly #now: () => Date;
|
||||
readonly #id: () => string;
|
||||
readonly #onProbe: ((result: HealthProbeResult) => void) | undefined;
|
||||
readonly #latestGeneration = new Map<string, number>();
|
||||
#nextGeneration = 0;
|
||||
|
||||
@@ -210,6 +223,7 @@ export class StatusSnapshotService {
|
||||
this.#maxStaleMs = options.maxStaleMs;
|
||||
this.#now = options.now ?? (() => new Date());
|
||||
this.#id = options.idFactory ?? randomUUID;
|
||||
this.#onProbe = options.onProbe;
|
||||
}
|
||||
|
||||
async refreshHealth(instanceId: string): Promise<HealthSnapshot> {
|
||||
@@ -247,6 +261,7 @@ export class StatusSnapshotService {
|
||||
);
|
||||
const { snapshot } = classified;
|
||||
|
||||
this.#observe(instanceId, snapshot, classified.persisted);
|
||||
if (this.#latestGeneration.get(instanceId) === generation) {
|
||||
if (
|
||||
isValidOwner(instanceId, currentOwner) &&
|
||||
@@ -260,6 +275,35 @@ export class StatusSnapshotService {
|
||||
return Object.freeze({ ...snapshot, payload: Object.freeze({ ...snapshot.payload }) });
|
||||
}
|
||||
|
||||
/** A failed connection log write must never take a health probe down with it. */
|
||||
#observe(instanceId: string, snapshot: HealthSnapshot, persisted: PersistedHealthEnvelope): void {
|
||||
if (!this.#onProbe) return;
|
||||
const status = persisted.httpStatus;
|
||||
const outcome: HealthProbeResult['outcome'] =
|
||||
persisted.errorCode === 'UNSUPPORTED'
|
||||
? 'unsupported'
|
||||
: snapshot.state === 'fresh'
|
||||
? 'success'
|
||||
: snapshot.state === 'stale'
|
||||
? 'stale'
|
||||
: status !== null && status >= 200 && status < 300
|
||||
? 'success'
|
||||
: 'failed';
|
||||
try {
|
||||
this.#onProbe({
|
||||
instanceId,
|
||||
outcome,
|
||||
state: snapshot.state,
|
||||
errorCode: persisted.errorCode,
|
||||
httpStatus: status,
|
||||
durationMs: persisted.durationMs,
|
||||
observedAt: snapshot.observedAt,
|
||||
});
|
||||
} catch {
|
||||
// Diagnostics are advisory; the snapshot result stands on its own.
|
||||
}
|
||||
}
|
||||
|
||||
#previous(instanceId: string): SnapshotRow | undefined {
|
||||
return this.#db
|
||||
.prepare(
|
||||
|
||||
Reference in New Issue
Block a user