Raise alerts: failed or paused prints, and AI detections
Nothing subscribed to channel:print_alerts and nothing watched a printer fail, so an operator away from the screen found out when they came back to a ruined plate. Alerts are now rows in printer_alerts, listed at GET /alerts (open=true for what still needs a person) and cleared with POST /alerts/:id/acknowledge, which is operator-only and keeps the first acknowledger on the record. Two sources. Telemetry: a change into error is a failed print if the printer was printing or paused, otherwise a printer error; a print the printer paused itself (how an AMS fault looks) is announced too. Transitions, not levels - a printer sitting in error is one alert, not one per frame - and a connection's first sight of a printer raises nothing, so a gateway that reconnects does not re-announce known faults. AI: the worker's Valkey publications are stored when confidence is at least ALERT_MIN_CONFIDENCE (default 0.6); the worker deliberately does not threshold, so the policy lives here. A bad message costs one message, not the listener. e2e-authz.sh gains the scoping/ack cases; new e2e-alerts.sh drives a stand-in gateway over the real /ws/edge/v1 and the real Valkey channel (checked to fail when the derivation is broken).
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { pgTable, uuid, text, timestamp, real, pgEnum, index } from "drizzle-orm/pg-core";
|
||||
import { farms } from "./farms";
|
||||
import { printers } from "./printers";
|
||||
|
||||
export const alertKindEnum = pgEnum("alert_kind", [
|
||||
"print_failed",
|
||||
"print_paused",
|
||||
"printer_error",
|
||||
"ai_detection",
|
||||
]);
|
||||
|
||||
/**
|
||||
* The permanent record of something an operator should look at.
|
||||
*
|
||||
* A table and not a channel: Valkey PUBLISH keeps nothing, so an alert raised
|
||||
* while no console was open would otherwise be gone for good. The console
|
||||
* lists open (unacknowledged) rows, which is also what makes "I was away,
|
||||
* what happened" answerable.
|
||||
*/
|
||||
export const printerAlerts = pgTable(
|
||||
"printer_alerts",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
farmId: uuid("farm_id")
|
||||
.notNull()
|
||||
.references(() => farms.id, { onDelete: "cascade" }),
|
||||
// Set null rather than cascade: the history of why a deleted printer was
|
||||
// flagged is still worth having, and the farm cascade bounds it anyway.
|
||||
printerId: uuid("printer_id").references(() => printers.id, { onDelete: "set null" }),
|
||||
kind: alertKindEnum("kind").notNull(),
|
||||
message: text("message").notNull(),
|
||||
// Only for ai_detection; null is "not applicable", not zero confidence.
|
||||
confidence: real("confidence"),
|
||||
// Where the frame the detection came from lives, for ai_detection.
|
||||
objectKey: text("object_key"),
|
||||
acknowledgedBy: uuid("acknowledged_by"),
|
||||
acknowledgedAt: timestamp("acknowledged_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
farmOpen: index("printer_alerts_farm_open_idx").on(table.farmId, table.acknowledgedAt),
|
||||
}),
|
||||
);
|
||||
|
||||
export type PrinterAlert = typeof printerAlerts.$inferSelect;
|
||||
@@ -3,3 +3,4 @@ export * from "./farms";
|
||||
export * from "./printers";
|
||||
export * from "./print_jobs";
|
||||
export * from "./embeddings";
|
||||
export * from "./alerts";
|
||||
|
||||
Reference in New Issue
Block a user