feat(api): add device identity guard and Prometheus metrics

- Persist device identity observations and expose fleet identity summaries.

- Block device control actions when identity verification fails.

- Add metrics collection and a Prometheus scrape endpoint.
This commit is contained in:
chick
2026-09-07 00:44:51 +08:00
parent 8406f14469
commit 961eb928c4
14 changed files with 1619 additions and 4 deletions
@@ -20,7 +20,8 @@ export type DeviceActionErrorCode =
| 'VALIDATION_FAILED'
| 'SESSION_INVALID'
| 'UPSTREAM_FAILED'
| 'NOT_DISPATCHED';
| 'NOT_DISPATCHED'
| 'IDENTITY_UNCONFIRMED';
export class DeviceActionError extends Error {
constructor(
@@ -192,6 +193,12 @@ export interface DeviceActionServiceOptions {
readonly ensureSession?: (instanceId: string, origin: string, force?: boolean) => Promise<void>;
readonly now?: () => Date;
readonly id?: () => string;
/**
* The identity guard. A node whose reported hardware stopped matching its record, or whose
* identity is claimed by another record, is not safe to control: the command would land on a
* device the operator is not looking at. Unbinding stays available because it is the way out.
*/
readonly identities?: { isBlocked(instanceId: string): boolean };
}
/**
@@ -239,6 +246,8 @@ export class DeviceActionService {
const session = this.options.sessions.sessionFor(instanceId);
if (session && session.origin !== instance.origin)
throw new DeviceActionError('SESSION_INVALID');
if (action.id !== 'hub.unbind' && this.options.identities?.isBlocked(instanceId))
throw new DeviceActionError('IDENTITY_UNCONFIRMED');
const values = this.#validate(action, params);
const { path, query, body } = this.#assemble(action, values);