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:
@@ -2,10 +2,12 @@ export { requireUser, type AuthenticatedUser } from "./plugin";
|
||||
export { roleSatisfies, ORG_ROLES, type OrgRole } from "./roles";
|
||||
export {
|
||||
listMemberships,
|
||||
requireAlert,
|
||||
requireFarm,
|
||||
requireJob,
|
||||
requireOrg,
|
||||
requirePrinter,
|
||||
visibleAlerts,
|
||||
visibleFarms,
|
||||
visibleJobs,
|
||||
visiblePrinters,
|
||||
|
||||
+31
-1
@@ -1,7 +1,7 @@
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import { ForbiddenError, NotFoundError } from "@continuum/ts-core";
|
||||
import { db } from "../db";
|
||||
import { farms, orgMembers, printJobs, printers } from "../db/schema";
|
||||
import { farms, orgMembers, printJobs, printerAlerts, printers } from "../db/schema";
|
||||
import { roleSatisfies, type OrgRole } from "./roles";
|
||||
|
||||
/**
|
||||
@@ -64,6 +64,14 @@ export function visibleJobs(userId: string) {
|
||||
);
|
||||
}
|
||||
|
||||
/** `where` condition limiting `printer_alerts` to those the user can see. */
|
||||
export function visibleAlerts(userId: string) {
|
||||
return inArray(
|
||||
printerAlerts.farmId,
|
||||
db.select({ id: farms.id }).from(farms).where(visibleFarms(userId)),
|
||||
);
|
||||
}
|
||||
|
||||
function check(role: OrgRole | null, minimum: OrgRole, missing: string, action: string): OrgRole {
|
||||
if (role === null) throw new NotFoundError(missing);
|
||||
if (!roleSatisfies(role, minimum)) {
|
||||
@@ -156,3 +164,25 @@ export async function requireJob(
|
||||
const role = check(row?.role ?? null, minimum, `Print job ${jobId} not found`, action);
|
||||
return { farmId: row!.farmId, orgId: row!.orgId, role };
|
||||
}
|
||||
|
||||
/** As `requireFarm`, resolved through the alert's farm. */
|
||||
export async function requireAlert(
|
||||
userId: string,
|
||||
alertId: string,
|
||||
minimum: OrgRole,
|
||||
action = "this operation",
|
||||
): Promise<{ farmId: string; orgId: string; role: OrgRole }> {
|
||||
const [row] = await db
|
||||
.select({ farmId: printerAlerts.farmId, orgId: farms.orgId, role: orgMembers.role })
|
||||
.from(printerAlerts)
|
||||
.innerJoin(farms, eq(farms.id, printerAlerts.farmId))
|
||||
.leftJoin(
|
||||
orgMembers,
|
||||
and(eq(orgMembers.orgId, farms.orgId), eq(orgMembers.userId, userId)),
|
||||
)
|
||||
.where(eq(printerAlerts.id, alertId))
|
||||
.limit(1);
|
||||
|
||||
const role = check(row?.role ?? null, minimum, `Alert ${alertId} not found`, action);
|
||||
return { farmId: row!.farmId, orgId: row!.orgId, role };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user