@fleetless/contracts 1.0.0
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.
- package/CHANGELOG.md +26 -0
- package/LICENSE +202 -0
- package/NOTICE +17 -0
- package/README.md +88 -0
- package/artifacts/constants.json +24 -0
- package/artifacts/openapi.json +17219 -0
- package/artifacts/routes.json +4605 -0
- package/artifacts/schema/accept-team-invite-request.schema.json +22 -0
- package/artifacts/schema/action-config.schema.json +198 -0
- package/artifacts/schema/alert-list-response.schema.json +172 -0
- package/artifacts/schema/api-error.schema.json +20 -0
- package/artifacts/schema/app-auth-config.schema.json +106 -0
- package/artifacts/schema/app-invitation-list-response.schema.json +57 -0
- package/artifacts/schema/app-invitation.schema.json +69 -0
- package/artifacts/schema/app-list-response.schema.json +82 -0
- package/artifacts/schema/app-mail-template-list-response.schema.json +68 -0
- package/artifacts/schema/app-mail-template.schema.json +54 -0
- package/artifacts/schema/app-oidc-provider-list-response.schema.json +93 -0
- package/artifacts/schema/app-oidc-provider.schema.json +80 -0
- package/artifacts/schema/app-user-list-response.schema.json +111 -0
- package/artifacts/schema/app-user.schema.json +98 -0
- package/artifacts/schema/app.schema.json +69 -0
- package/artifacts/schema/apply-error.schema.json +41 -0
- package/artifacts/schema/asset-list-response.schema.json +288 -0
- package/artifacts/schema/asset-sync-request.schema.json +17 -0
- package/artifacts/schema/asset-sync-response.schema.json +16 -0
- package/artifacts/schema/asset-sync-status.schema.json +136 -0
- package/artifacts/schema/asset.schema.json +68 -0
- package/artifacts/schema/audit-actor.schema.json +32 -0
- package/artifacts/schema/audit-event.schema.json +119 -0
- package/artifacts/schema/audit-list-response.schema.json +144 -0
- package/artifacts/schema/audit-query.schema.json +79 -0
- package/artifacts/schema/auth-error.schema.json +23 -0
- package/artifacts/schema/auth-me-response.schema.json +99 -0
- package/artifacts/schema/auth-ok.schema.json +115 -0
- package/artifacts/schema/authorization-server-metadata.schema.json +80 -0
- package/artifacts/schema/bridge-asset-progress.schema.json +99 -0
- package/artifacts/schema/bridge-assets-available.schema.json +25 -0
- package/artifacts/schema/bridge-camera-state.schema.json +78 -0
- package/artifacts/schema/bridge-config-applied.schema.json +67 -0
- package/artifacts/schema/bridge-hello.schema.json +65 -0
- package/artifacts/schema/bridge-introspect.schema.json +114 -0
- package/artifacts/schema/bridge-job-lost.schema.json +22 -0
- package/artifacts/schema/bridge-job-update.schema.json +100 -0
- package/artifacts/schema/bridge-pong.schema.json +19 -0
- package/artifacts/schema/bridge-pressure.schema.json +292 -0
- package/artifacts/schema/bridge-state.schema.json +24 -0
- package/artifacts/schema/bridge-type-definitions.schema.json +169 -0
- package/artifacts/schema/busy-details.schema.json +115 -0
- package/artifacts/schema/camera-descriptor.schema.json +45 -0
- package/artifacts/schema/camera-list-response.schema.json +58 -0
- package/artifacts/schema/camera-source.schema.json +240 -0
- package/artifacts/schema/cancel-request.schema.json +20 -0
- package/artifacts/schema/client-accept-invitation-request.schema.json +35 -0
- package/artifacts/schema/client-auth.schema.json +18 -0
- package/artifacts/schema/client-cancel.schema.json +45 -0
- package/artifacts/schema/client-identity.schema.json +103 -0
- package/artifacts/schema/client-invoke.schema.json +45 -0
- package/artifacts/schema/client-login-request.schema.json +29 -0
- package/artifacts/schema/client-logout-request.schema.json +14 -0
- package/artifacts/schema/client-mcp-interaction-decision-response.schema.json +15 -0
- package/artifacts/schema/client-mcp-interaction.schema.json +59 -0
- package/artifacts/schema/client-oidc-callback-query.schema.json +28 -0
- package/artifacts/schema/client-oidc-exchange-request.schema.json +21 -0
- package/artifacts/schema/client-oidc-start-query.schema.json +36 -0
- package/artifacts/schema/client-password-reset-confirm-request.schema.json +22 -0
- package/artifacts/schema/client-password-reset-request.schema.json +24 -0
- package/artifacts/schema/client-provider-list-query.schema.json +17 -0
- package/artifacts/schema/client-provider-list-response.schema.json +34 -0
- package/artifacts/schema/client-publish.schema.json +40 -0
- package/artifacts/schema/client-refresh-request.schema.json +14 -0
- package/artifacts/schema/client-register-request.schema.json +44 -0
- package/artifacts/schema/client-resend-verification-request.schema.json +24 -0
- package/artifacts/schema/client-subscribe.schema.json +43 -0
- package/artifacts/schema/client-unsubscribe.schema.json +26 -0
- package/artifacts/schema/client-verify-email-request.schema.json +15 -0
- package/artifacts/schema/cloud-asset-request.schema.json +37 -0
- package/artifacts/schema/cloud-camera-start.schema.json +41 -0
- package/artifacts/schema/cloud-camera-stop.schema.json +26 -0
- package/artifacts/schema/cloud-cancel.schema.json +33 -0
- package/artifacts/schema/cloud-config.schema.json +1635 -0
- package/artifacts/schema/cloud-hello-error.schema.json +23 -0
- package/artifacts/schema/cloud-hello-ok.schema.json +19 -0
- package/artifacts/schema/cloud-introspect-request.schema.json +19 -0
- package/artifacts/schema/cloud-invoke.schema.json +40 -0
- package/artifacts/schema/cloud-ping.schema.json +19 -0
- package/artifacts/schema/cloud-publish.schema.json +28 -0
- package/artifacts/schema/cloud-type-request.schema.json +30 -0
- package/artifacts/schema/command-result.schema.json +175 -0
- package/artifacts/schema/config-draft-response.schema.json +1695 -0
- package/artifacts/schema/config-state.schema.json +124 -0
- package/artifacts/schema/config-version-response.schema.json +1641 -0
- package/artifacts/schema/config-versions-response.schema.json +33 -0
- package/artifacts/schema/create-app-invitation-request.schema.json +40 -0
- package/artifacts/schema/create-app-oidc-provider-request.schema.json +70 -0
- package/artifacts/schema/create-app-request.schema.json +30 -0
- package/artifacts/schema/create-app-user-request.schema.json +42 -0
- package/artifacts/schema/create-robot-request.schema.json +14 -0
- package/artifacts/schema/create-robot-response.schema.json +44 -0
- package/artifacts/schema/create-server-key-response.schema.json +65 -0
- package/artifacts/schema/create-team-invite-request.schema.json +43 -0
- package/artifacts/schema/datapoint-alert-row.schema.json +160 -0
- package/artifacts/schema/datapoint-config.schema.json +366 -0
- package/artifacts/schema/datapoint-display.schema.json +31 -0
- package/artifacts/schema/datapoint-event.schema.json +34 -0
- package/artifacts/schema/datapoint-frame.schema.json +28 -0
- package/artifacts/schema/datapoint-list-response.schema.json +61 -0
- package/artifacts/schema/datapoint-value.schema.json +28 -0
- package/artifacts/schema/developer-login-request.schema.json +19 -0
- package/artifacts/schema/dynamic-client-registration-request.schema.json +60 -0
- package/artifacts/schema/dynamic-client-registration-response.schema.json +68 -0
- package/artifacts/schema/error-frame.schema.json +23 -0
- package/artifacts/schema/exposure-counts.schema.json +39 -0
- package/artifacts/schema/exposure-list-response.schema.json +43 -0
- package/artifacts/schema/fetch-types-request.schema.json +19 -0
- package/artifacts/schema/fetch-types-response.schema.json +163 -0
- package/artifacts/schema/fleetless-user-list-response.schema.json +73 -0
- package/artifacts/schema/fleetless-user.schema.json +60 -0
- package/artifacts/schema/history-buckets-response.schema.json +79 -0
- package/artifacts/schema/history-query.schema.json +58 -0
- package/artifacts/schema/history-response.schema.json +150 -0
- package/artifacts/schema/history-samples-response.schema.json +68 -0
- package/artifacts/schema/introspection-response.schema.json +118 -0
- package/artifacts/schema/invoke-or-service-response.schema.json +141 -0
- package/artifacts/schema/invoke-request.schema.json +23 -0
- package/artifacts/schema/invoke-response.schema.json +125 -0
- package/artifacts/schema/job-actor.schema.json +34 -0
- package/artifacts/schema/job-event.schema.json +158 -0
- package/artifacts/schema/job-response.schema.json +123 -0
- package/artifacts/schema/job-run-list-response.schema.json +222 -0
- package/artifacts/schema/job-run-query.schema.json +95 -0
- package/artifacts/schema/job-run-summary-query.schema.json +23 -0
- package/artifacts/schema/job-run-summary.schema.json +33 -0
- package/artifacts/schema/job-run.schema.json +195 -0
- package/artifacts/schema/job-state.schema.json +11 -0
- package/artifacts/schema/job.schema.json +106 -0
- package/artifacts/schema/latency-bucket.schema.json +63 -0
- package/artifacts/schema/live-session-response.schema.json +41 -0
- package/artifacts/schema/mail-outcome.schema.json +20 -0
- package/artifacts/schema/mail-template-preview-request.schema.json +35 -0
- package/artifacts/schema/mail-template-preview-response.schema.json +31 -0
- package/artifacts/schema/mail-template-problem-details.schema.json +24 -0
- package/artifacts/schema/mcp-consent-grant-list-response.schema.json +52 -0
- package/artifacts/schema/mcp-consent-grant.schema.json +39 -0
- package/artifacts/schema/mcp-robot-datasheet.schema.json +115 -0
- package/artifacts/schema/mcp-role-preview-response.schema.json +134 -0
- package/artifacts/schema/missing-asset-query.schema.json +11 -0
- package/artifacts/schema/oauth-authorize-query.schema.json +47 -0
- package/artifacts/schema/oauth-redirect-response.schema.json +15 -0
- package/artifacts/schema/oauth-token-request.schema.json +47 -0
- package/artifacts/schema/oauth-token-response.schema.json +38 -0
- package/artifacts/schema/org-alerts-query.schema.json +15 -0
- package/artifacts/schema/org-event-dropped.schema.json +26 -0
- package/artifacts/schema/org-event-replay.schema.json +97 -0
- package/artifacts/schema/org-event-subscribe.schema.json +14 -0
- package/artifacts/schema/org-event-unsubscribe.schema.json +14 -0
- package/artifacts/schema/org-event.schema.json +75 -0
- package/artifacts/schema/org-firing-alerts-response.schema.json +178 -0
- package/artifacts/schema/org-health-query.schema.json +13 -0
- package/artifacts/schema/org-latency-query.schema.json +42 -0
- package/artifacts/schema/org-latency-response.schema.json +124 -0
- package/artifacts/schema/org-quota-usage-counts.schema.json +42 -0
- package/artifacts/schema/org-quota-usage.schema.json +102 -0
- package/artifacts/schema/org-quotas.schema.json +51 -0
- package/artifacts/schema/org-usage-query.schema.json +19 -0
- package/artifacts/schema/org-usage-response.schema.json +77 -0
- package/artifacts/schema/org.schema.json +30 -0
- package/artifacts/schema/parameter-invalid-details.schema.json +37 -0
- package/artifacts/schema/parameter-spec.schema.json +120 -0
- package/artifacts/schema/parameter-violation.schema.json +24 -0
- package/artifacts/schema/password-change-request.schema.json +21 -0
- package/artifacts/schema/password-reset-confirm.schema.json +19 -0
- package/artifacts/schema/password-reset-request.schema.json +14 -0
- package/artifacts/schema/patch-app-oidc-provider-request.schema.json +50 -0
- package/artifacts/schema/patch-app-user-request.schema.json +34 -0
- package/artifacts/schema/patch-auth-me-request.schema.json +22 -0
- package/artifacts/schema/patch-fleetless-user-request.schema.json +20 -0
- package/artifacts/schema/patch-org-request.schema.json +15 -0
- package/artifacts/schema/patch-org-response.schema.json +40 -0
- package/artifacts/schema/patch-robot-request.schema.json +15 -0
- package/artifacts/schema/patch-robot-response.schema.json +40 -0
- package/artifacts/schema/pending-team-invite-list-response.schema.json +52 -0
- package/artifacts/schema/pending-team-invite.schema.json +39 -0
- package/artifacts/schema/protected-resource-metadata.schema.json +41 -0
- package/artifacts/schema/publish-config-response.schema.json +21 -0
- package/artifacts/schema/publish-request.schema.json +17 -0
- package/artifacts/schema/publisher-config.schema.json +285 -0
- package/artifacts/schema/put-app-auth-config-request.schema.json +93 -0
- package/artifacts/schema/put-app-mail-template-request.schema.json +35 -0
- package/artifacts/schema/put-config-draft-request.schema.json +13 -0
- package/artifacts/schema/put-datapoint-display-request.schema.json +31 -0
- package/artifacts/schema/put-robot-details-request.schema.json +41 -0
- package/artifacts/schema/put-robot-details-response.schema.json +43 -0
- package/artifacts/schema/rate-limit-details.schema.json +15 -0
- package/artifacts/schema/refresh-request.schema.json +13 -0
- package/artifacts/schema/release-live-query.schema.json +13 -0
- package/artifacts/schema/rename-slug-request.schema.json +23 -0
- package/artifacts/schema/rename-slug-response.schema.json +24 -0
- package/artifacts/schema/resource-health-event.schema.json +72 -0
- package/artifacts/schema/resource-health-list-response.schema.json +80 -0
- package/artifacts/schema/resource-health-state.schema.json +68 -0
- package/artifacts/schema/robot-config-doc.schema.json +1616 -0
- package/artifacts/schema/robot-delete-query.schema.json +12 -0
- package/artifacts/schema/robot-deletion-summary.schema.json +63 -0
- package/artifacts/schema/robot-detail-response.schema.json +262 -0
- package/artifacts/schema/robot-details-doc.schema.json +33 -0
- package/artifacts/schema/robot-jobs-response.schema.json +119 -0
- package/artifacts/schema/robot-latency-series.schema.json +81 -0
- package/artifacts/schema/robot-list-item.schema.json +94 -0
- package/artifacts/schema/robot-list-response.schema.json +106 -0
- package/artifacts/schema/robot.schema.json +30 -0
- package/artifacts/schema/role-list-response.schema.json +48 -0
- package/artifacts/schema/role-permissions.schema.json +61 -0
- package/artifacts/schema/role.schema.json +35 -0
- package/artifacts/schema/ros-graph.schema.json +99 -0
- package/artifacts/schema/server-key-list-response.schema.json +64 -0
- package/artifacts/schema/server-key.schema.json +51 -0
- package/artifacts/schema/service-call-response.schema.json +13 -0
- package/artifacts/schema/service-config.schema.json +198 -0
- package/artifacts/schema/session-tokens.schema.json +28 -0
- package/artifacts/schema/sign-up-request.schema.json +26 -0
- package/artifacts/schema/sign-up-response.schema.json +127 -0
- package/artifacts/schema/slug-usage-response.schema.json +32 -0
- package/artifacts/schema/snapshot-header.schema.json +44 -0
- package/artifacts/schema/snapshot-meta-response.schema.json +85 -0
- package/artifacts/schema/subscribe-error.schema.json +31 -0
- package/artifacts/schema/team-invite.schema.json +57 -0
- package/artifacts/schema/tier-change-request.schema.json +17 -0
- package/artifacts/schema/type-definition.schema.json +144 -0
- package/artifacts/schema/types-response.schema.json +156 -0
- package/artifacts/schema/update-app-request.schema.json +32 -0
- package/artifacts/schema/urdf-completeness.schema.json +50 -0
- package/artifacts/schema/validation-issue.schema.json +43 -0
- package/artifacts/schema/waitlist-request.schema.json +15 -0
- package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +102 -0
- package/artifacts/schema-outgoing/bridge-assets-available.schema.json +26 -0
- package/artifacts/schema-outgoing/bridge-camera-state.schema.json +80 -0
- package/artifacts/schema-outgoing/bridge-config-applied.schema.json +69 -0
- package/artifacts/schema-outgoing/bridge-hello.schema.json +68 -0
- package/artifacts/schema-outgoing/bridge-introspect.schema.json +119 -0
- package/artifacts/schema-outgoing/bridge-job-lost.schema.json +23 -0
- package/artifacts/schema-outgoing/bridge-job-update.schema.json +102 -0
- package/artifacts/schema-outgoing/bridge-pong.schema.json +20 -0
- package/artifacts/schema-outgoing/bridge-type-definitions.schema.json +174 -0
- package/artifacts/schema-outgoing/datapoint-frame.schema.json +29 -0
- package/artifacts/schema-outgoing/snapshot-header.schema.json +45 -0
- package/dist/alerts.d.ts +255 -0
- package/dist/alerts.js +193 -0
- package/dist/app-users.d.ts +606 -0
- package/dist/app-users.js +696 -0
- package/dist/apps.d.ts +175 -0
- package/dist/apps.js +267 -0
- package/dist/assets.d.ts +434 -0
- package/dist/assets.js +546 -0
- package/dist/audit.d.ts +129 -0
- package/dist/audit.js +238 -0
- package/dist/client-auth.d.ts +409 -0
- package/dist/client-auth.js +487 -0
- package/dist/common.d.ts +186 -0
- package/dist/common.js +199 -0
- package/dist/config-issues.d.ts +175 -0
- package/dist/config-issues.js +339 -0
- package/dist/config.d.ts +862 -0
- package/dist/config.js +1988 -0
- package/dist/errors.d.ts +52 -0
- package/dist/errors.js +786 -0
- package/dist/identity.d.ts +549 -0
- package/dist/identity.js +503 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +51 -0
- package/dist/introspection.d.ts +99 -0
- package/dist/introspection.js +97 -0
- package/dist/jobs.d.ts +334 -0
- package/dist/jobs.js +345 -0
- package/dist/mcp.d.ts +239 -0
- package/dist/mcp.js +153 -0
- package/dist/oauth.d.ts +344 -0
- package/dist/oauth.js +488 -0
- package/dist/protocol.d.ts +781 -0
- package/dist/protocol.js +715 -0
- package/dist/realtime.d.ts +494 -0
- package/dist/realtime.js +512 -0
- package/dist/rest.d.ts +1989 -0
- package/dist/rest.js +1963 -0
- package/dist/routes.d.ts +94 -0
- package/dist/routes.js +2298 -0
- package/package.json +61 -0
package/dist/alerts.d.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Datapoint alerts and per-datapoint chart display config (spec
|
|
4
|
+
* `2026-08-28-alerts-and-datapoint-modal-design`, D1/D2/D5).
|
|
5
|
+
*
|
|
6
|
+
* An alert is a **state machine** (`ok ⇄ firing`), not a fire-once event —
|
|
7
|
+
* the definition (this file's request/entity shapes) and the runtime state
|
|
8
|
+
* (`state`, `state_since`, `last_value`) share one row, evaluated by the
|
|
9
|
+
* cloud at ingest.
|
|
10
|
+
*
|
|
11
|
+
* **Both tables moved into `robotConfigDoc` in FL-002.** The alert definition
|
|
12
|
+
* is `config.ts`'s `datapointAlert`, nested under the datapoint it watches;
|
|
13
|
+
* the chart bounds are `datapointChart`. They therefore take effect on
|
|
14
|
+
* publish rather than immediately, and in exchange every change to them is
|
|
15
|
+
* versioned, comparable and revertible. The runtime state stays wherever the
|
|
16
|
+
* definition goes: it belongs in the database and has no business in a
|
|
17
|
+
* versioned document.
|
|
18
|
+
*
|
|
19
|
+
* **What is left here is the read surface**, which FL-002 wave 4 kept rather
|
|
20
|
+
* than deleted: `GET /api/robots/:id/alerts` and `GET /api/org/alerts` still
|
|
21
|
+
* answer with the definition joined to its state, and the shapes below are
|
|
22
|
+
* what they answer with. What wave 4 did remove is the mail path — the fields
|
|
23
|
+
* `cooldown_minutes`, `recipients` and `notify_on_resolve`, and the two
|
|
24
|
+
* bounds that guarded them. No alert can send mail, so nothing here describes
|
|
25
|
+
* one.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* `above`/`below` compare the numeric sample value (already scale/offset
|
|
29
|
+
* applied by the bridge) against `threshold`. `resolve_hysteresis` (≥ 0,
|
|
30
|
+
* **defaulted to 0** — plain re-cross) moves the resolve point off the
|
|
31
|
+
* threshold itself: `above` resolves at `value ≤ threshold −
|
|
32
|
+
* resolve_hysteresis`, mirrored for `below`. Defaulted rather than left
|
|
33
|
+
* `optional` so a parsed entity never makes a consumer re-derive "absent
|
|
34
|
+
* means 0" — the cloud always sends an explicit resolve point, and every
|
|
35
|
+
* reader gets the same number whether it was sent or not. `equals` compares
|
|
36
|
+
* the raw value for equality — the shape for boolean/string datapoints a
|
|
37
|
+
* threshold cannot describe ("Hindernis erkannt" = `equals true`) — and
|
|
38
|
+
* carries no hysteresis, because equality has no direction to relax.
|
|
39
|
+
*
|
|
40
|
+
* A discriminated union on `kind` rather than one object with optional
|
|
41
|
+
* fields: an `equals` alert carrying a stray `threshold` would otherwise
|
|
42
|
+
* parse silently and mean nothing, and a consumer's `switch (kind)` fails
|
|
43
|
+
* `tsc` on an unhandled member instead of failing at runtime on a `never`.
|
|
44
|
+
*
|
|
45
|
+
* **Each member is `.strict()`, not the union's default `z.object`.** A
|
|
46
|
+
* plain `z.object` strips unknown keys silently rather than refusing them —
|
|
47
|
+
* so without this, `{kind: 'equals', value: true, threshold: 5}` would
|
|
48
|
+
* parse successfully with `threshold` dropped on the floor, which is exactly
|
|
49
|
+
* the "parse silently and mean nothing" failure the paragraph above already
|
|
50
|
+
* argued against, just one layer further in.
|
|
51
|
+
*
|
|
52
|
+
* **`Row` distinguishes this from the document's own condition.**
|
|
53
|
+
* `config.ts`'s `alertCondition` is what an author writes inside a datapoint —
|
|
54
|
+
* a different shape for a different question (`fire_at`/`resolve_at` rather
|
|
55
|
+
* than `kind`/`threshold`/`resolve_hysteresis`). This one is what the
|
|
56
|
+
* evaluator switches on and what the read routes answer with; the cloud
|
|
57
|
+
* derives it from the document (`alert-definitions.ts`'s `toRowCondition`) and
|
|
58
|
+
* derives it nowhere else.
|
|
59
|
+
*
|
|
60
|
+
* **It was renamed for a wave 4 deletion that did not happen**, and the name
|
|
61
|
+
* is kept because the distinction it draws is still needed: two condition
|
|
62
|
+
* shapes coexist, and only one of them is authored. Nothing is stored under
|
|
63
|
+
* this shape any more — the database keeps runtime state only — so read `Row`
|
|
64
|
+
* as "the derived one", not as "the persisted one".
|
|
65
|
+
*/
|
|
66
|
+
export declare const alertRowCondition: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
67
|
+
kind: z.ZodLiteral<"above">;
|
|
68
|
+
threshold: z.ZodNumber;
|
|
69
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
70
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
71
|
+
kind: z.ZodLiteral<"below">;
|
|
72
|
+
threshold: z.ZodNumber;
|
|
73
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
74
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
75
|
+
kind: z.ZodLiteral<"equals">;
|
|
76
|
+
value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
|
|
77
|
+
}, z.core.$strict>], "kind">;
|
|
78
|
+
export type AlertRowCondition = z.infer<typeof alertRowCondition>;
|
|
79
|
+
/**
|
|
80
|
+
* Assigned at creation, carried onto every `orgEventKind: 'alert'` firing
|
|
81
|
+
* event verbatim (resolved events are always `info` — see
|
|
82
|
+
* `orgEventKind`'s doc comment in `realtime.ts`).
|
|
83
|
+
*/
|
|
84
|
+
export declare const alertSeverity: z.ZodEnum<{
|
|
85
|
+
error: "error";
|
|
86
|
+
warning: "warning";
|
|
87
|
+
}>;
|
|
88
|
+
export type AlertSeverity = z.infer<typeof alertSeverity>;
|
|
89
|
+
/** The two states of the alert state machine. There is no third state — an alert is never "unknown" or "pending"; it holds its last state across non-comparable samples (D2). */
|
|
90
|
+
export declare const alertState: z.ZodEnum<{
|
|
91
|
+
ok: "ok";
|
|
92
|
+
firing: "firing";
|
|
93
|
+
}>;
|
|
94
|
+
export type AlertState = z.infer<typeof alertState>;
|
|
95
|
+
/**
|
|
96
|
+
* One alert row, definition and runtime state together — the runtime fields
|
|
97
|
+
* (`state`, `state_since`, `last_value`) are DB-held so they survive a cloud
|
|
98
|
+
* restart, and are read-only from every client's point of view: nothing
|
|
99
|
+
* writes them from outside the cloud's own evaluator.
|
|
100
|
+
*
|
|
101
|
+
* **`Row` is a name the shape outgrew**, kept only to keep it apart from
|
|
102
|
+
* `config.ts`'s `datapointAlert`, which is the definition an author writes.
|
|
103
|
+
* Nothing is stored in this shape: the definition comes out of the published
|
|
104
|
+
* document and the runtime state out of `datapoint_alert_state`, and the
|
|
105
|
+
* cloud joins the two per request (`routes/alerts.ts`'s `toWire`).
|
|
106
|
+
*
|
|
107
|
+
* **It carried three mail settings — `cooldown_minutes`, `recipients` and
|
|
108
|
+
* `notify_on_resolve` — and FL-002 wave 4 removed them with the mail path.**
|
|
109
|
+
* The format has no mail fields, so no alert could be configured to send one;
|
|
110
|
+
* the three had nothing behind them well before they were deleted.
|
|
111
|
+
*/
|
|
112
|
+
export declare const datapointAlertRow: z.ZodObject<{
|
|
113
|
+
id: z.ZodUUID;
|
|
114
|
+
robot_id: z.ZodUUID;
|
|
115
|
+
slug: z.ZodString;
|
|
116
|
+
name: z.ZodString;
|
|
117
|
+
enabled: z.ZodBoolean;
|
|
118
|
+
severity: z.ZodEnum<{
|
|
119
|
+
error: "error";
|
|
120
|
+
warning: "warning";
|
|
121
|
+
}>;
|
|
122
|
+
condition: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
123
|
+
kind: z.ZodLiteral<"above">;
|
|
124
|
+
threshold: z.ZodNumber;
|
|
125
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
126
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
127
|
+
kind: z.ZodLiteral<"below">;
|
|
128
|
+
threshold: z.ZodNumber;
|
|
129
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
130
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
131
|
+
kind: z.ZodLiteral<"equals">;
|
|
132
|
+
value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
|
|
133
|
+
}, z.core.$strict>], "kind">;
|
|
134
|
+
state: z.ZodEnum<{
|
|
135
|
+
ok: "ok";
|
|
136
|
+
firing: "firing";
|
|
137
|
+
}>;
|
|
138
|
+
state_since: z.ZodNullable<z.ZodISODateTime>;
|
|
139
|
+
last_value: z.ZodNullable<z.ZodUnknown>;
|
|
140
|
+
created_at: z.ZodISODateTime;
|
|
141
|
+
}, z.core.$strip>;
|
|
142
|
+
export type DatapointAlertRow = z.infer<typeof datapointAlertRow>;
|
|
143
|
+
/** `GET /api/robots/:id/alerts`. */
|
|
144
|
+
export declare const alertListResponse: z.ZodObject<{
|
|
145
|
+
alerts: z.ZodArray<z.ZodObject<{
|
|
146
|
+
id: z.ZodUUID;
|
|
147
|
+
robot_id: z.ZodUUID;
|
|
148
|
+
slug: z.ZodString;
|
|
149
|
+
name: z.ZodString;
|
|
150
|
+
enabled: z.ZodBoolean;
|
|
151
|
+
severity: z.ZodEnum<{
|
|
152
|
+
error: "error";
|
|
153
|
+
warning: "warning";
|
|
154
|
+
}>;
|
|
155
|
+
condition: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
156
|
+
kind: z.ZodLiteral<"above">;
|
|
157
|
+
threshold: z.ZodNumber;
|
|
158
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
159
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
160
|
+
kind: z.ZodLiteral<"below">;
|
|
161
|
+
threshold: z.ZodNumber;
|
|
162
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
163
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
164
|
+
kind: z.ZodLiteral<"equals">;
|
|
165
|
+
value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
|
|
166
|
+
}, z.core.$strict>], "kind">;
|
|
167
|
+
state: z.ZodEnum<{
|
|
168
|
+
ok: "ok";
|
|
169
|
+
firing: "firing";
|
|
170
|
+
}>;
|
|
171
|
+
state_since: z.ZodNullable<z.ZodISODateTime>;
|
|
172
|
+
last_value: z.ZodNullable<z.ZodUnknown>;
|
|
173
|
+
created_at: z.ZodISODateTime;
|
|
174
|
+
}, z.core.$strip>>;
|
|
175
|
+
}, z.core.$strip>;
|
|
176
|
+
export type AlertListResponse = z.infer<typeof alertListResponse>;
|
|
177
|
+
/**
|
|
178
|
+
* `GET /api/org/alerts?state=firing` — feeds the overview's "open issues"
|
|
179
|
+
* tile and the fleet grid's per-robot badge (D3). Org-scoped and
|
|
180
|
+
* cross-robot, so each entry carries `robot_name` alongside the alert: the
|
|
181
|
+
* overview has no robot context of its own to join against.
|
|
182
|
+
*/
|
|
183
|
+
export declare const orgFiringAlertsResponse: z.ZodObject<{
|
|
184
|
+
alerts: z.ZodArray<z.ZodObject<{
|
|
185
|
+
id: z.ZodUUID;
|
|
186
|
+
robot_id: z.ZodUUID;
|
|
187
|
+
slug: z.ZodString;
|
|
188
|
+
name: z.ZodString;
|
|
189
|
+
enabled: z.ZodBoolean;
|
|
190
|
+
severity: z.ZodEnum<{
|
|
191
|
+
error: "error";
|
|
192
|
+
warning: "warning";
|
|
193
|
+
}>;
|
|
194
|
+
condition: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
195
|
+
kind: z.ZodLiteral<"above">;
|
|
196
|
+
threshold: z.ZodNumber;
|
|
197
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
198
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
199
|
+
kind: z.ZodLiteral<"below">;
|
|
200
|
+
threshold: z.ZodNumber;
|
|
201
|
+
resolve_hysteresis: z.ZodDefault<z.ZodNumber>;
|
|
202
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
203
|
+
kind: z.ZodLiteral<"equals">;
|
|
204
|
+
value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
|
|
205
|
+
}, z.core.$strict>], "kind">;
|
|
206
|
+
state: z.ZodEnum<{
|
|
207
|
+
ok: "ok";
|
|
208
|
+
firing: "firing";
|
|
209
|
+
}>;
|
|
210
|
+
state_since: z.ZodNullable<z.ZodISODateTime>;
|
|
211
|
+
last_value: z.ZodNullable<z.ZodUnknown>;
|
|
212
|
+
created_at: z.ZodISODateTime;
|
|
213
|
+
robot_name: z.ZodString;
|
|
214
|
+
}, z.core.$strip>>;
|
|
215
|
+
}, z.core.$strip>;
|
|
216
|
+
export type OrgFiringAlertsResponse = z.infer<typeof orgFiringAlertsResponse>;
|
|
217
|
+
/**
|
|
218
|
+
* The query of `GET /api/org/alerts`: only the firing set is served.
|
|
219
|
+
*
|
|
220
|
+
* A schema for a one-value parameter looks like ceremony, and it is not: the
|
|
221
|
+
* route **refuses** anything else rather than ignoring it, so the single
|
|
222
|
+
* accepted value is a contract a caller can read off the parameter table
|
|
223
|
+
* instead of discovering as a `400`.
|
|
224
|
+
*/
|
|
225
|
+
export declare const orgAlertsQuery: z.ZodObject<{
|
|
226
|
+
state: z.ZodLiteral<"firing">;
|
|
227
|
+
}, z.core.$strip>;
|
|
228
|
+
export type OrgAlertsQuery = z.infer<typeof orgAlertsQuery>;
|
|
229
|
+
/**
|
|
230
|
+
* `GET /api/robots/:id/datapoints/:slug/display` — chart display config from
|
|
231
|
+
* the modal's Chart tab (D1, D4). `robot_id`/`slug` live in the path, not
|
|
232
|
+
* the body; there is exactly one row per `(robot_id, slug)`, so there is
|
|
233
|
+
* nothing to list or identify beyond the path itself.
|
|
234
|
+
*
|
|
235
|
+
* `null` means auto-scale — the uPlot chart's default — not "unset versus
|
|
236
|
+
* zero": a bound of literal `0` is a real, common y-axis floor and must
|
|
237
|
+
* round-trip as `0`, not fall back to auto because it was falsy.
|
|
238
|
+
*/
|
|
239
|
+
export declare const datapointDisplay: z.ZodObject<{
|
|
240
|
+
y_min: z.ZodNullable<z.ZodNumber>;
|
|
241
|
+
y_max: z.ZodNullable<z.ZodNumber>;
|
|
242
|
+
}, z.core.$strip>;
|
|
243
|
+
export type DatapointDisplay = z.infer<typeof datapointDisplay>;
|
|
244
|
+
/**
|
|
245
|
+
* `PUT /api/robots/:id/datapoints/:slug/display` — applies immediately,
|
|
246
|
+
* never published, never sent to the bridge (D1). Same shape as
|
|
247
|
+
* `datapointDisplay`, kept as its own type per house convention (`put*Request`
|
|
248
|
+
* beside the entity it writes) so the two can diverge if the read side ever
|
|
249
|
+
* grows a field the write side should not accept.
|
|
250
|
+
*/
|
|
251
|
+
export declare const putDatapointDisplayRequest: z.ZodObject<{
|
|
252
|
+
y_min: z.ZodNullable<z.ZodNumber>;
|
|
253
|
+
y_max: z.ZodNullable<z.ZodNumber>;
|
|
254
|
+
}, z.core.$strict>;
|
|
255
|
+
export type PutDatapointDisplayRequest = z.infer<typeof putDatapointDisplayRequest>;
|
package/dist/alerts.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { slug } from './common.js';
|
|
4
|
+
/**
|
|
5
|
+
* Datapoint alerts and per-datapoint chart display config (spec
|
|
6
|
+
* `2026-08-28-alerts-and-datapoint-modal-design`, D1/D2/D5).
|
|
7
|
+
*
|
|
8
|
+
* An alert is a **state machine** (`ok ⇄ firing`), not a fire-once event —
|
|
9
|
+
* the definition (this file's request/entity shapes) and the runtime state
|
|
10
|
+
* (`state`, `state_since`, `last_value`) share one row, evaluated by the
|
|
11
|
+
* cloud at ingest.
|
|
12
|
+
*
|
|
13
|
+
* **Both tables moved into `robotConfigDoc` in FL-002.** The alert definition
|
|
14
|
+
* is `config.ts`'s `datapointAlert`, nested under the datapoint it watches;
|
|
15
|
+
* the chart bounds are `datapointChart`. They therefore take effect on
|
|
16
|
+
* publish rather than immediately, and in exchange every change to them is
|
|
17
|
+
* versioned, comparable and revertible. The runtime state stays wherever the
|
|
18
|
+
* definition goes: it belongs in the database and has no business in a
|
|
19
|
+
* versioned document.
|
|
20
|
+
*
|
|
21
|
+
* **What is left here is the read surface**, which FL-002 wave 4 kept rather
|
|
22
|
+
* than deleted: `GET /api/robots/:id/alerts` and `GET /api/org/alerts` still
|
|
23
|
+
* answer with the definition joined to its state, and the shapes below are
|
|
24
|
+
* what they answer with. What wave 4 did remove is the mail path — the fields
|
|
25
|
+
* `cooldown_minutes`, `recipients` and `notify_on_resolve`, and the two
|
|
26
|
+
* bounds that guarded them. No alert can send mail, so nothing here describes
|
|
27
|
+
* one.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* `above`/`below` compare the numeric sample value (already scale/offset
|
|
31
|
+
* applied by the bridge) against `threshold`. `resolve_hysteresis` (≥ 0,
|
|
32
|
+
* **defaulted to 0** — plain re-cross) moves the resolve point off the
|
|
33
|
+
* threshold itself: `above` resolves at `value ≤ threshold −
|
|
34
|
+
* resolve_hysteresis`, mirrored for `below`. Defaulted rather than left
|
|
35
|
+
* `optional` so a parsed entity never makes a consumer re-derive "absent
|
|
36
|
+
* means 0" — the cloud always sends an explicit resolve point, and every
|
|
37
|
+
* reader gets the same number whether it was sent or not. `equals` compares
|
|
38
|
+
* the raw value for equality — the shape for boolean/string datapoints a
|
|
39
|
+
* threshold cannot describe ("Hindernis erkannt" = `equals true`) — and
|
|
40
|
+
* carries no hysteresis, because equality has no direction to relax.
|
|
41
|
+
*
|
|
42
|
+
* A discriminated union on `kind` rather than one object with optional
|
|
43
|
+
* fields: an `equals` alert carrying a stray `threshold` would otherwise
|
|
44
|
+
* parse silently and mean nothing, and a consumer's `switch (kind)` fails
|
|
45
|
+
* `tsc` on an unhandled member instead of failing at runtime on a `never`.
|
|
46
|
+
*
|
|
47
|
+
* **Each member is `.strict()`, not the union's default `z.object`.** A
|
|
48
|
+
* plain `z.object` strips unknown keys silently rather than refusing them —
|
|
49
|
+
* so without this, `{kind: 'equals', value: true, threshold: 5}` would
|
|
50
|
+
* parse successfully with `threshold` dropped on the floor, which is exactly
|
|
51
|
+
* the "parse silently and mean nothing" failure the paragraph above already
|
|
52
|
+
* argued against, just one layer further in.
|
|
53
|
+
*
|
|
54
|
+
* **`Row` distinguishes this from the document's own condition.**
|
|
55
|
+
* `config.ts`'s `alertCondition` is what an author writes inside a datapoint —
|
|
56
|
+
* a different shape for a different question (`fire_at`/`resolve_at` rather
|
|
57
|
+
* than `kind`/`threshold`/`resolve_hysteresis`). This one is what the
|
|
58
|
+
* evaluator switches on and what the read routes answer with; the cloud
|
|
59
|
+
* derives it from the document (`alert-definitions.ts`'s `toRowCondition`) and
|
|
60
|
+
* derives it nowhere else.
|
|
61
|
+
*
|
|
62
|
+
* **It was renamed for a wave 4 deletion that did not happen**, and the name
|
|
63
|
+
* is kept because the distinction it draws is still needed: two condition
|
|
64
|
+
* shapes coexist, and only one of them is authored. Nothing is stored under
|
|
65
|
+
* this shape any more — the database keeps runtime state only — so read `Row`
|
|
66
|
+
* as "the derived one", not as "the persisted one".
|
|
67
|
+
*/
|
|
68
|
+
export const alertRowCondition = z.discriminatedUnion('kind', [
|
|
69
|
+
z.strictObject({
|
|
70
|
+
kind: z.literal('above'),
|
|
71
|
+
threshold: z.number().finite(),
|
|
72
|
+
resolve_hysteresis: z.number().nonnegative().default(0),
|
|
73
|
+
}),
|
|
74
|
+
z.strictObject({
|
|
75
|
+
kind: z.literal('below'),
|
|
76
|
+
threshold: z.number().finite(),
|
|
77
|
+
resolve_hysteresis: z.number().nonnegative().default(0),
|
|
78
|
+
}),
|
|
79
|
+
z.strictObject({
|
|
80
|
+
kind: z.literal('equals'),
|
|
81
|
+
/** A JSON scalar, matching what a datapoint value actually is on the wire — never an object or array. */
|
|
82
|
+
value: z.union([z.number(), z.string(), z.boolean()]),
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
85
|
+
/**
|
|
86
|
+
* Assigned at creation, carried onto every `orgEventKind: 'alert'` firing
|
|
87
|
+
* event verbatim (resolved events are always `info` — see
|
|
88
|
+
* `orgEventKind`'s doc comment in `realtime.ts`).
|
|
89
|
+
*/
|
|
90
|
+
export const alertSeverity = z.enum(['warning', 'error']);
|
|
91
|
+
/** The two states of the alert state machine. There is no third state — an alert is never "unknown" or "pending"; it holds its last state across non-comparable samples (D2). */
|
|
92
|
+
export const alertState = z.enum(['ok', 'firing']);
|
|
93
|
+
/**
|
|
94
|
+
* One alert row, definition and runtime state together — the runtime fields
|
|
95
|
+
* (`state`, `state_since`, `last_value`) are DB-held so they survive a cloud
|
|
96
|
+
* restart, and are read-only from every client's point of view: nothing
|
|
97
|
+
* writes them from outside the cloud's own evaluator.
|
|
98
|
+
*
|
|
99
|
+
* **`Row` is a name the shape outgrew**, kept only to keep it apart from
|
|
100
|
+
* `config.ts`'s `datapointAlert`, which is the definition an author writes.
|
|
101
|
+
* Nothing is stored in this shape: the definition comes out of the published
|
|
102
|
+
* document and the runtime state out of `datapoint_alert_state`, and the
|
|
103
|
+
* cloud joins the two per request (`routes/alerts.ts`'s `toWire`).
|
|
104
|
+
*
|
|
105
|
+
* **It carried three mail settings — `cooldown_minutes`, `recipients` and
|
|
106
|
+
* `notify_on_resolve` — and FL-002 wave 4 removed them with the mail path.**
|
|
107
|
+
* The format has no mail fields, so no alert could be configured to send one;
|
|
108
|
+
* the three had nothing behind them well before they were deleted.
|
|
109
|
+
*/
|
|
110
|
+
export const datapointAlertRow = z.object({
|
|
111
|
+
id: z.uuid(),
|
|
112
|
+
robot_id: z.uuid(),
|
|
113
|
+
slug,
|
|
114
|
+
name: z.string().min(1).max(120),
|
|
115
|
+
enabled: z.boolean(),
|
|
116
|
+
severity: alertSeverity,
|
|
117
|
+
condition: alertRowCondition,
|
|
118
|
+
state: alertState,
|
|
119
|
+
/** `null` only until the first evaluation writes a state; every alert is created `ok` (D2), so in practice this is set from creation onward. */
|
|
120
|
+
state_since: z.iso.datetime().nullable(),
|
|
121
|
+
/**
|
|
122
|
+
* The value at the alert's last state transition — written only when the
|
|
123
|
+
* alert fires or resolves, never on a per-sample basis. This is a
|
|
124
|
+
* deliberate cost trade, not an oversight: a per-sample write would turn
|
|
125
|
+
* every accepted sample into a DB write regardless of whether anything
|
|
126
|
+
* changed, which is exactly the hot-path cost the evaluator avoids
|
|
127
|
+
* everywhere else. It follows that this is NOT "the datapoint's current
|
|
128
|
+
* value" — for that, read the live snapshot (`datapointValue`, or the
|
|
129
|
+
* realtime datapoint stream), never this field. `null` before the
|
|
130
|
+
* alert's first transition, and returns to `null` when a `PATCH`
|
|
131
|
+
* replaces `condition` wholesale — the old value was judged against the
|
|
132
|
+
* old condition, and the store resets runtime state in that same write
|
|
133
|
+
* rather than let it survive a condition change it no longer means
|
|
134
|
+
* anything against.
|
|
135
|
+
*/
|
|
136
|
+
last_value: z.unknown().nullable(),
|
|
137
|
+
created_at: z.iso.datetime(),
|
|
138
|
+
});
|
|
139
|
+
/** `GET /api/robots/:id/alerts`. */
|
|
140
|
+
export const alertListResponse = z.object({
|
|
141
|
+
alerts: z.array(datapointAlertRow),
|
|
142
|
+
});
|
|
143
|
+
/**
|
|
144
|
+
* `GET /api/org/alerts?state=firing` — feeds the overview's "open issues"
|
|
145
|
+
* tile and the fleet grid's per-robot badge (D3). Org-scoped and
|
|
146
|
+
* cross-robot, so each entry carries `robot_name` alongside the alert: the
|
|
147
|
+
* overview has no robot context of its own to join against.
|
|
148
|
+
*/
|
|
149
|
+
export const orgFiringAlertsResponse = z.object({
|
|
150
|
+
alerts: z.array(datapointAlertRow.extend({ robot_name: z.string().min(1).max(63) })),
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* The query of `GET /api/org/alerts`: only the firing set is served.
|
|
154
|
+
*
|
|
155
|
+
* A schema for a one-value parameter looks like ceremony, and it is not: the
|
|
156
|
+
* route **refuses** anything else rather than ignoring it, so the single
|
|
157
|
+
* accepted value is a contract a caller can read off the parameter table
|
|
158
|
+
* instead of discovering as a `400`.
|
|
159
|
+
*/
|
|
160
|
+
export const orgAlertsQuery = z
|
|
161
|
+
.object({
|
|
162
|
+
state: z.literal('firing').meta({
|
|
163
|
+
description: 'Required, and the only accepted value — this endpoint lists the alerts that are firing now. Anything else, the parameter\'s absence included, is `400 validation_error`: a door with one answer must not advertise a dial.',
|
|
164
|
+
}),
|
|
165
|
+
})
|
|
166
|
+
.meta({ description: 'The query of `GET /api/org/alerts`. One required parameter with one accepted value.' });
|
|
167
|
+
/**
|
|
168
|
+
* `GET /api/robots/:id/datapoints/:slug/display` — chart display config from
|
|
169
|
+
* the modal's Chart tab (D1, D4). `robot_id`/`slug` live in the path, not
|
|
170
|
+
* the body; there is exactly one row per `(robot_id, slug)`, so there is
|
|
171
|
+
* nothing to list or identify beyond the path itself.
|
|
172
|
+
*
|
|
173
|
+
* `null` means auto-scale — the uPlot chart's default — not "unset versus
|
|
174
|
+
* zero": a bound of literal `0` is a real, common y-axis floor and must
|
|
175
|
+
* round-trip as `0`, not fall back to auto because it was falsy.
|
|
176
|
+
*/
|
|
177
|
+
export const datapointDisplay = z.object({
|
|
178
|
+
y_min: z.number().finite().nullable(),
|
|
179
|
+
y_max: z.number().finite().nullable(),
|
|
180
|
+
});
|
|
181
|
+
/**
|
|
182
|
+
* `PUT /api/robots/:id/datapoints/:slug/display` — applies immediately,
|
|
183
|
+
* never published, never sent to the bridge (D1). Same shape as
|
|
184
|
+
* `datapointDisplay`, kept as its own type per house convention (`put*Request`
|
|
185
|
+
* beside the entity it writes) so the two can diverge if the read side ever
|
|
186
|
+
* grows a field the write side should not accept.
|
|
187
|
+
*/
|
|
188
|
+
export const putDatapointDisplayRequest = z
|
|
189
|
+
.object({
|
|
190
|
+
y_min: z.number().finite().nullable(),
|
|
191
|
+
y_max: z.number().finite().nullable(),
|
|
192
|
+
})
|
|
193
|
+
.strict();
|