@fleetless/contracts 1.0.0 → 1.0.2
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 +68 -2
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +136 -0
- package/README.md +49 -13
- package/SECURITY.md +55 -0
- package/artifacts/openapi.json +1 -1
- package/artifacts/routes.json +1 -1
- package/dist/alerts.d.ts +19 -24
- package/dist/alerts.js +18 -24
- package/dist/app-users.d.ts +7 -6
- package/dist/app-users.js +6 -6
- package/dist/apps.d.ts +21 -25
- package/dist/apps.js +40 -51
- package/dist/assets.d.ts +70 -132
- package/dist/assets.js +130 -223
- package/dist/audit.d.ts +11 -11
- package/dist/audit.js +25 -51
- package/dist/client-auth.d.ts +4 -4
- package/dist/client-auth.js +3 -4
- package/dist/common.d.ts +27 -35
- package/dist/common.js +26 -35
- package/dist/config-issues.d.ts +4 -3
- package/dist/config-issues.js +7 -6
- package/dist/config.d.ts +31 -37
- package/dist/config.js +81 -110
- package/dist/errors.d.ts +4 -3
- package/dist/errors.js +61 -87
- package/dist/identity.d.ts +18 -21
- package/dist/identity.js +17 -21
- package/dist/index.d.ts +4 -4
- package/dist/index.js +12 -13
- package/dist/introspection.d.ts +7 -6
- package/dist/introspection.js +6 -6
- package/dist/jobs.d.ts +12 -12
- package/dist/jobs.js +20 -25
- package/dist/mcp.d.ts +11 -12
- package/dist/mcp.js +10 -12
- package/dist/oauth.d.ts +13 -18
- package/dist/oauth.js +13 -19
- package/dist/protocol.d.ts +51 -62
- package/dist/protocol.js +107 -139
- package/dist/realtime.d.ts +53 -68
- package/dist/realtime.js +77 -103
- package/dist/rest.d.ts +182 -243
- package/dist/rest.js +301 -395
- package/dist/routes.d.ts +4 -3
- package/dist/routes.js +3 -2
- package/package.json +12 -7
package/dist/app-users.js
CHANGED
|
@@ -52,12 +52,12 @@ export const providerSlug = z
|
|
|
52
52
|
/**
|
|
53
53
|
* **The three states an app user can be in, and the order is the lifecycle.**
|
|
54
54
|
*
|
|
55
|
-
* - `pending_verification` — self-registered, mail sent, cannot log in yet
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* - `pending_verification` — self-registered, mail sent, cannot log in yet.
|
|
56
|
+
* Without this state a domain allow-list would prove nothing: anybody could
|
|
57
|
+
* claim any address at an allowed domain.
|
|
58
58
|
* - `active` — may log in.
|
|
59
59
|
* - `blocked` — may not, and every refusal is the same `invalid_credentials`
|
|
60
|
-
* a wrong password gets
|
|
60
|
+
* a wrong password gets. A block that announced itself would be an
|
|
61
61
|
* account-enumeration oracle with an extra step.
|
|
62
62
|
*
|
|
63
63
|
* `pending_verification` is reached exactly once and left only by spending the
|
|
@@ -519,7 +519,7 @@ export const MAIL_TEMPLATE_VARIABLES = [
|
|
|
519
519
|
'expires_in_hours',
|
|
520
520
|
];
|
|
521
521
|
/**
|
|
522
|
-
* **The Fleetless default text for the three app mails
|
|
522
|
+
* **The Fleetless default text for the three app mails.**
|
|
523
523
|
*
|
|
524
524
|
* It lives here rather than in the cloud because two products send the same
|
|
525
525
|
* words: the cloud renders these when an app has no template of its own, and
|
|
@@ -553,7 +553,7 @@ export const MAIL_TEMPLATE_VARIABLES = [
|
|
|
553
553
|
* that one is written by an authenticated developer about somebody they
|
|
554
554
|
* invited.
|
|
555
555
|
*
|
|
556
|
-
* **`expires_in_hours` is the only lifetime variable
|
|
556
|
+
* **`expires_in_hours` is the only lifetime variable a template gets**, and
|
|
557
557
|
* the three values are 1, 24 and 168. "The next 168 hours" is not how a person
|
|
558
558
|
* says a week, so each default converts: 48 and up reads in days, exactly one
|
|
559
559
|
* reads "1 hour", everything else reads in hours. The conversion is in the
|
package/dist/apps.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* Apps, roles and rights
|
|
4
|
+
* Apps, roles and rights.
|
|
4
5
|
*
|
|
5
6
|
* The rule that shapes all of this: **roles are the only filter**. A robot
|
|
6
7
|
* assigned to an app exposes every one of its services to that app; what a
|
|
@@ -42,12 +43,11 @@ export declare const appListResponse: z.ZodObject<{
|
|
|
42
43
|
}, z.core.$strip>;
|
|
43
44
|
export type AppListResponse = z.infer<typeof appListResponse>;
|
|
44
45
|
/**
|
|
45
|
-
* **`robot_ids` is accepted here, and `.strict()` catches everything else
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* definition of a shape that reads as though it does something it does not.
|
|
46
|
+
* **`robot_ids` is accepted here, and `.strict()` catches everything else.**
|
|
47
|
+
* A create shape carrying `name` and `identifier` only would let zod strip an
|
|
48
|
+
* offered `robot_ids`, so a caller creating an app *with* robots gets a `201`
|
|
49
|
+
* and an app with none — a shape that reads as though it does something it does
|
|
50
|
+
* not.
|
|
51
51
|
*
|
|
52
52
|
* Both halves matter and neither alone is enough. Accepting `robot_ids` is
|
|
53
53
|
* right because attaching robots at creation is the obvious operation and the
|
|
@@ -69,21 +69,17 @@ export type CreateAppRequest = z.infer<typeof createAppRequest>;
|
|
|
69
69
|
/**
|
|
70
70
|
* **`.strict()` is what makes an absent field mean something here.**
|
|
71
71
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* been carried one shape over. A caller who sends a field this route does not
|
|
78
|
-
* do is asking for something, and the honest answer is `400`, not a success
|
|
79
|
-
* that means less than it looks.
|
|
72
|
+
* Without it, an offered field the route does not implement is *dropped* — the
|
|
73
|
+
* caller gets a `200`, nothing changes, and nothing anywhere says so. It is the
|
|
74
|
+
* same silence `createAppRequest` above describes. A caller who sends a field
|
|
75
|
+
* this route does not do is asking for something, and the honest answer is
|
|
76
|
+
* `400`, not a success that means less than it looks.
|
|
80
77
|
*
|
|
81
|
-
* Two fields
|
|
82
|
-
* were on this shape and neither has a successor here.
|
|
78
|
+
* Two fields are absent and worth naming, because neither has a successor here.
|
|
83
79
|
* `accepts_dynamic_clients` gated app-level OAuth dynamic client registration,
|
|
84
|
-
* which
|
|
85
|
-
* only for MCP. `group_id` named
|
|
86
|
-
*
|
|
80
|
+
* which no longer exists: apps use the JSON client-auth API, and OAuth 2.1
|
|
81
|
+
* remains only for MCP. `group_id` named a group that owned the app; who may
|
|
82
|
+
* log into an app is the app's own user list.
|
|
87
83
|
*
|
|
88
84
|
* The route keeps its own check as belt-and-braces; a schema and a handler
|
|
89
85
|
* agreeing is not two policies, it is one policy stated where each half can
|
|
@@ -96,9 +92,9 @@ export declare const updateAppRequest: z.ZodObject<{
|
|
|
96
92
|
}, z.core.$strict>;
|
|
97
93
|
export type UpdateAppRequest = z.infer<typeof updateAppRequest>;
|
|
98
94
|
/**
|
|
99
|
-
* A server key carries full app rights for server-side code
|
|
100
|
-
*
|
|
101
|
-
*
|
|
95
|
+
* A server key carries full app rights for server-side code — never for
|
|
96
|
+
* clients. Same handling as the robot token: the value is returned exactly once
|
|
97
|
+
* and only its hash is stored.
|
|
102
98
|
*/
|
|
103
99
|
export declare const serverKeyToken: z.ZodString;
|
|
104
100
|
export declare const serverKey: z.ZodObject<{
|
|
@@ -133,7 +129,7 @@ export declare const createServerKeyResponse: z.ZodObject<{
|
|
|
133
129
|
export type CreateServerKeyResponse = z.infer<typeof createServerKeyResponse>;
|
|
134
130
|
/**
|
|
135
131
|
* Every app starts with `observe` and `operate`; custom roles are allowed
|
|
136
|
-
*
|
|
132
|
+
* too. `builtin` marks the two starting roles — they may be
|
|
137
133
|
* edited like any other, the flag exists so the console can explain where
|
|
138
134
|
* they came from.
|
|
139
135
|
*/
|
|
@@ -156,7 +152,7 @@ export declare const roleListResponse: z.ZodObject<{
|
|
|
156
152
|
export type RoleListResponse = z.infer<typeof roleListResponse>;
|
|
157
153
|
/**
|
|
158
154
|
* The rights matrix of one role: which slugs of which robot it may use, plus
|
|
159
|
-
* the capabilities roles also govern
|
|
155
|
+
* the capabilities roles also govern. `capabilities`' own doc comment
|
|
160
156
|
* below says which of them are enforced today and which is still a switch
|
|
161
157
|
* that changes nothing.
|
|
162
158
|
*/
|
package/dist/apps.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { slug } from './common.js';
|
|
4
4
|
/**
|
|
5
|
-
* Apps, roles and rights
|
|
5
|
+
* Apps, roles and rights.
|
|
6
6
|
*
|
|
7
7
|
* The rule that shapes all of this: **roles are the only filter**. A robot
|
|
8
8
|
* assigned to an app exposes every one of its services to that app; what a
|
|
@@ -33,7 +33,7 @@ export const app = z.object({
|
|
|
33
33
|
identifier: appIdentifier.meta({
|
|
34
34
|
description: 'The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context to disambiguate with, so a collision is refused with `identifier_taken`.',
|
|
35
35
|
}),
|
|
36
|
-
/** Robots are referenced individually; tags never grant rights
|
|
36
|
+
/** Robots are referenced individually; tags never grant rights. */
|
|
37
37
|
robot_ids: z.array(z.uuid()).meta({
|
|
38
38
|
description: 'The robots this app may reach, each referenced individually. Tags never grant rights, and a robot absent from this list is invisible to the app whatever a role grants.',
|
|
39
39
|
}),
|
|
@@ -79,12 +79,11 @@ export const appListResponse = z.object({
|
|
|
79
79
|
}),
|
|
80
80
|
});
|
|
81
81
|
/**
|
|
82
|
-
* **`robot_ids` is accepted here, and `.strict()` catches everything else
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* definition of a shape that reads as though it does something it does not.
|
|
82
|
+
* **`robot_ids` is accepted here, and `.strict()` catches everything else.**
|
|
83
|
+
* A create shape carrying `name` and `identifier` only would let zod strip an
|
|
84
|
+
* offered `robot_ids`, so a caller creating an app *with* robots gets a `201`
|
|
85
|
+
* and an app with none — a shape that reads as though it does something it does
|
|
86
|
+
* not.
|
|
88
87
|
*
|
|
89
88
|
* Both halves matter and neither alone is enough. Accepting `robot_ids` is
|
|
90
89
|
* right because attaching robots at creation is the obvious operation and the
|
|
@@ -105,21 +104,17 @@ export const createAppRequest = z.object({
|
|
|
105
104
|
/**
|
|
106
105
|
* **`.strict()` is what makes an absent field mean something here.**
|
|
107
106
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* been carried one shape over. A caller who sends a field this route does not
|
|
114
|
-
* do is asking for something, and the honest answer is `400`, not a success
|
|
115
|
-
* that means less than it looks.
|
|
107
|
+
* Without it, an offered field the route does not implement is *dropped* — the
|
|
108
|
+
* caller gets a `200`, nothing changes, and nothing anywhere says so. It is the
|
|
109
|
+
* same silence `createAppRequest` above describes. A caller who sends a field
|
|
110
|
+
* this route does not do is asking for something, and the honest answer is
|
|
111
|
+
* `400`, not a success that means less than it looks.
|
|
116
112
|
*
|
|
117
|
-
* Two fields
|
|
118
|
-
* were on this shape and neither has a successor here.
|
|
113
|
+
* Two fields are absent and worth naming, because neither has a successor here.
|
|
119
114
|
* `accepts_dynamic_clients` gated app-level OAuth dynamic client registration,
|
|
120
|
-
* which
|
|
121
|
-
* only for MCP. `group_id` named
|
|
122
|
-
*
|
|
115
|
+
* which no longer exists: apps use the JSON client-auth API, and OAuth 2.1
|
|
116
|
+
* remains only for MCP. `group_id` named a group that owned the app; who may
|
|
117
|
+
* log into an app is the app's own user list.
|
|
123
118
|
*
|
|
124
119
|
* The route keeps its own check as belt-and-braces; a schema and a handler
|
|
125
120
|
* agreeing is not two policies, it is one policy stated where each half can
|
|
@@ -142,9 +137,9 @@ export const updateAppRequest = z.object({
|
|
|
142
137
|
default_role_id: z.uuid().nullable().optional(),
|
|
143
138
|
}).strict();
|
|
144
139
|
/**
|
|
145
|
-
* A server key carries full app rights for server-side code
|
|
146
|
-
*
|
|
147
|
-
*
|
|
140
|
+
* A server key carries full app rights for server-side code — never for
|
|
141
|
+
* clients. Same handling as the robot token: the value is returned exactly once
|
|
142
|
+
* and only its hash is stored.
|
|
148
143
|
*/
|
|
149
144
|
export const serverKeyToken = z.string().regex(/^flk_[0-9a-f]{32}$/);
|
|
150
145
|
export const serverKey = z.object({
|
|
@@ -177,7 +172,7 @@ export const createServerKeyResponse = z.object({
|
|
|
177
172
|
});
|
|
178
173
|
/**
|
|
179
174
|
* Every app starts with `observe` and `operate`; custom roles are allowed
|
|
180
|
-
*
|
|
175
|
+
* too. `builtin` marks the two starting roles — they may be
|
|
181
176
|
* edited like any other, the flag exists so the console can explain where
|
|
182
177
|
* they came from.
|
|
183
178
|
*/
|
|
@@ -203,23 +198,20 @@ export const roleListResponse = z.object({
|
|
|
203
198
|
});
|
|
204
199
|
/**
|
|
205
200
|
* The rights matrix of one role: which slugs of which robot it may use, plus
|
|
206
|
-
* the capabilities roles also govern
|
|
201
|
+
* the capabilities roles also govern. `capabilities`' own doc comment
|
|
207
202
|
* below says which of them are enforced today and which is still a switch
|
|
208
203
|
* that changes nothing.
|
|
209
204
|
*/
|
|
210
205
|
export const rolePermissions = z.object({
|
|
211
206
|
role_id: z.uuid(),
|
|
212
207
|
/**
|
|
213
|
-
* **A slug is unique per robot across ALL
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* slug of a robot with its kind, so the console's matrix can offer them —
|
|
221
|
-
* today it enumerates datapoints only, which is the seam that would
|
|
222
|
-
* otherwise force a rebuild.
|
|
208
|
+
* **A slug is unique per robot across ALL exposure kinds** — one namespace,
|
|
209
|
+
* not one per kind — and the cloud's configuration validation enforces that
|
|
210
|
+
* with a kind-agnostic collection pass. That is why this list carries slugs
|
|
211
|
+
* and not (kind, slug) pairs: a grant means the same thing whichever kind the
|
|
212
|
+
* slug turns out to name. `GET /api/robots/:id/exposures` is the companion
|
|
213
|
+
* read that lists every grantable slug of a robot **with** its kind, so a
|
|
214
|
+
* rights matrix can offer them.
|
|
223
215
|
*/
|
|
224
216
|
grants: z.array(z.object({
|
|
225
217
|
robot_id: z.uuid(),
|
|
@@ -228,25 +220,22 @@ export const rolePermissions = z.object({
|
|
|
228
220
|
/**
|
|
229
221
|
* App-wide abilities a role grants, as opposed to per-slug grants above.
|
|
230
222
|
*
|
|
231
|
-
* **A capability here is a promise, and one of them is still not kept.**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* is: it is the only place that says a switch in the console may change
|
|
238
|
-
* nothing, and it is how the next unkept capability gets caught.
|
|
223
|
+
* **A capability here is a promise, and one of them is still not kept.** A
|
|
224
|
+
* capability with no route, no SDK method and no realtime frame behind it can
|
|
225
|
+
* be switched on while nothing changes, which is worse than its absence: the
|
|
226
|
+
* developer believes they granted something. **This paragraph stays**
|
|
227
|
+
* whatever the current tally is: it is the only place that says a switch may
|
|
228
|
+
* change nothing, and it is how the next unkept capability gets caught.
|
|
239
229
|
*
|
|
240
|
-
* `assets`
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
230
|
+
* `assets` gates the asset store, which is not covered by `grants` because
|
|
231
|
+
* **assets are not slugs** — and it is its own decision rather than a side
|
|
232
|
+
* effect of reaching the robot, because a mesh set gives away the machine's
|
|
233
|
+
* build.
|
|
244
234
|
*
|
|
245
|
-
* **`action_history` is kept
|
|
235
|
+
* **`action_history` is kept.** It gates
|
|
246
236
|
* `GET /api/robots/:id/jobs/history` — an end user whose role lacks it is
|
|
247
237
|
* refused `403 capability_required`, naming the capability so the developer
|
|
248
|
-
* knows which switch is off.
|
|
249
|
-
* recorded what had run; `jobRun` and `job_runs` are that record.
|
|
238
|
+
* knows which switch is off.
|
|
250
239
|
*
|
|
251
240
|
* **What granting it discloses.** A `jobRun` names the actor who invoked
|
|
252
241
|
* it, and `jobActor.label` is an email — so an end user holding this
|
package/dist/assets.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* The asset store
|
|
4
|
+
* The asset store.
|
|
4
5
|
*
|
|
5
6
|
* An **asset is an immutable file belonging to a robot**. It has a uuid and is
|
|
6
7
|
* fetched by it. There are no org-level assets and no public retrieval: every
|
|
7
|
-
* read is authenticated and
|
|
8
|
+
* read is authenticated and checked against the `assets` role.
|
|
8
9
|
*
|
|
9
10
|
* **One authorization model — the `Authorization` header.** Not a signed URL,
|
|
10
11
|
* not a cookie, not a token in a query string. The reason is not ergonomics
|
|
@@ -12,7 +13,7 @@ import { z } from 'zod';
|
|
|
12
13
|
* lifetime, its own renewal, its own rotation, and in every log the question
|
|
13
14
|
* of which token that was. Directus solves the same problem with a cookie and
|
|
14
15
|
* an `access_token` query parameter, and the cookie half does not transfer —
|
|
15
|
-
* Fleetless has no interface of its own
|
|
16
|
+
* Fleetless has no end-user interface of its own, so the consumers of a robot's
|
|
16
17
|
* assets sit on other origins.
|
|
17
18
|
*
|
|
18
19
|
* **The consequence, said out loud: this is not a CDN.** A shared cache must
|
|
@@ -25,10 +26,9 @@ import { z } from 'zod';
|
|
|
25
26
|
*
|
|
26
27
|
* ---
|
|
27
28
|
*
|
|
28
|
-
* **`texture` is its own member of `assetKind` and not `other
|
|
29
|
+
* **`texture` is its own member of `assetKind` and not `other`.**
|
|
29
30
|
*
|
|
30
|
-
* Filing textures under `other`
|
|
31
|
-
* already split five times, and it costs a real capability: a client that
|
|
31
|
+
* Filing textures under `other` costs a real capability: a client that
|
|
32
32
|
* renders a robot must know, from the asset list alone and before fetching
|
|
33
33
|
* anything, which bytes it has to pre-fetch. Every load in the browser goes
|
|
34
34
|
* through the SDK with the bearer token — there is no lazy second fetch a
|
|
@@ -94,23 +94,18 @@ export type Asset = z.infer<typeof asset>;
|
|
|
94
94
|
*
|
|
95
95
|
* `missing` carries **the reference, verbatim, that no asset answers** — for
|
|
96
96
|
* a `package://` mesh the URI the bridge could not resolve in the workspace,
|
|
97
|
-
* and
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* producers, not on this field (W7a).** An entry a developer cannot make
|
|
110
|
-
* disappear by fixing what it names is a defect in whoever put it there: for
|
|
111
|
-
* a whole wave `<texture>` references were listed here and no sync would ever
|
|
112
|
-
* offer them, so the honest instruction behind the list was "fix this, it
|
|
113
|
-
* will not help".
|
|
97
|
+
* and also the absolute paths and bare relative paths a URDF may carry, which
|
|
98
|
+
* the extractor sees and the sync deliberately never offers. A developer whose
|
|
99
|
+
* URDF names `/opt/meshes/arm.stl` is entitled to be told that nothing will
|
|
100
|
+
* ever fetch it.
|
|
101
|
+
*
|
|
102
|
+
* A bare count of what is missing is a dead end: it tells a developer to go
|
|
103
|
+
* looking through a workspace by hand. The references are what they can act
|
|
104
|
+
* on, so the references travel.
|
|
105
|
+
*
|
|
106
|
+
* **Every entry must be actionable, and that is a constraint on the producers,
|
|
107
|
+
* not on this field.** An entry a developer cannot make disappear by fixing
|
|
108
|
+
* what it names is a defect in whoever put it there.
|
|
114
109
|
*/
|
|
115
110
|
export declare const urdfCompleteness: z.ZodObject<{
|
|
116
111
|
present: z.ZodBoolean;
|
|
@@ -128,21 +123,14 @@ export type UrdfCompleteness = z.infer<typeof urdfCompleteness>;
|
|
|
128
123
|
* A sync is long-running and is therefore answered with something to watch,
|
|
129
124
|
* never with a status that was true at the moment of asking.
|
|
130
125
|
*
|
|
131
|
-
* **`source` has one value, and that is deliberate.**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* differently; that is a defect this project has deliberately refused to
|
|
136
|
-
* introduce before, when an error code was proposed whose payload had moved.
|
|
137
|
-
* The zip path stays a condition rather than sitting in the wire as a
|
|
138
|
-
* promise.
|
|
126
|
+
* **`source` has one value, and that is deliberate.** A manual upload path is
|
|
127
|
+
* planned but has no body defined for the bytes yet. An enum value with no
|
|
128
|
+
* producer and no payload invites every consumer to guess a shape, and each
|
|
129
|
+
* guesses differently, so it stays out of the wire until it is real.
|
|
139
130
|
*
|
|
140
|
-
* A single-member enum rather than
|
|
131
|
+
* A single-member enum rather than no field at all: the second source is a
|
|
141
132
|
* question of when, not whether, and a caller that already names its source
|
|
142
133
|
* does not change shape when the second one arrives.
|
|
143
|
-
*
|
|
144
|
-
* Caught by Eve-W7 asking what body `'upload'` takes, rather than building
|
|
145
|
-
* against a guess.
|
|
146
134
|
*/
|
|
147
135
|
export declare const assetSyncRequest: z.ZodObject<{
|
|
148
136
|
source: z.ZodEnum<{
|
|
@@ -150,18 +138,11 @@ export declare const assetSyncRequest: z.ZodObject<{
|
|
|
150
138
|
}>;
|
|
151
139
|
}, z.core.$strict>;
|
|
152
140
|
/**
|
|
153
|
-
* **And the route
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* comment with a type.
|
|
159
|
-
*
|
|
160
|
-
* The decision is to keep the shape and **validate it, `.strict()`**, rather
|
|
161
|
-
* than delete it. Deleting removes the record of why the enum has one member,
|
|
162
|
-
* and the zip path is a question of when. Validation is what makes the single
|
|
163
|
-
* member mean something: a caller who sends `'upload'` today learns that it
|
|
164
|
-
* does not exist yet, instead of silently getting a bridge sync.
|
|
141
|
+
* **And the route reads it.** The body is `.strict()`, so `{source:'upload'}`
|
|
142
|
+
* and `{nonsense:1}` are refusals rather than silent bridge syncs. A shape no
|
|
143
|
+
* route validates is not a contract; it is a comment with a type. Validation
|
|
144
|
+
* is what makes the single member mean something: a caller who names a source
|
|
145
|
+
* that does not exist yet learns that, instead of getting a different one.
|
|
165
146
|
*/
|
|
166
147
|
export type AssetSyncRequest = z.infer<typeof assetSyncRequest>;
|
|
167
148
|
export declare const assetSyncResponse: z.ZodObject<{
|
|
@@ -176,83 +157,43 @@ export type AssetSyncResponse = z.infer<typeof assetSyncResponse>;
|
|
|
176
157
|
* worse than one that fails outright, because the failure surfaces later, in a
|
|
177
158
|
* renderer, as a robot with missing limbs and no explanation.
|
|
178
159
|
*
|
|
179
|
-
* **`failed` carries per-reference facts and `reason` carries the sync's
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* assume one. Read together with the old sentence *"`failed` is URIs and
|
|
196
|
-
* nothing else"*, this file told a consumer the same value was both a defect
|
|
197
|
-
* and a documented case.
|
|
198
|
-
*
|
|
199
|
-
* So the rule is: **anything that is not about one specific reference goes in
|
|
200
|
-
* `reason`** — one human-readable sentence
|
|
201
|
-
* about why the sync ended as it did, `null` when the outcome speaks for
|
|
202
|
-
* itself. It also carries the distinction `bridgeAssetProgress.state` makes
|
|
203
|
-
* and this shape could not — a sync **refused** because another was in flight
|
|
204
|
-
* is not a sync that tried and failed.
|
|
205
|
-
*
|
|
206
|
-
* **`assetSyncState` was deliberately not widened to carry that.** Consumers
|
|
207
|
-
* switch on it, a new member silently changes what every existing switch
|
|
208
|
-
* covers, and "refused" is a *reason* for a terminal outcome rather than a
|
|
209
|
-
* different one. Adding a field is additive; adding an enum member is not.
|
|
160
|
+
* **`failed` carries per-reference facts and `reason` carries the sync's
|
|
161
|
+
* own.** Anything that is not about one specific reference goes in `reason` —
|
|
162
|
+
* one human-readable sentence about why the sync ended as it did, `null` when
|
|
163
|
+
* the outcome speaks for itself. A whole-sync condition written into `failed`
|
|
164
|
+
* reaches a developer as a mesh they are told to go and find.
|
|
165
|
+
*
|
|
166
|
+
* Not every `failed` entry is a mesh URI. A URDF upload can fail like any
|
|
167
|
+
* other asset, and it appears under the name `robot_description`; see
|
|
168
|
+
* `assetFailure.reference`. A consumer must not assume every entry is a
|
|
169
|
+
* `package://` URI.
|
|
170
|
+
*
|
|
171
|
+
* **`assetSyncState` is deliberately not widened to carry the reason.**
|
|
172
|
+
* Consumers switch on it, and a new member silently changes what every
|
|
173
|
+
* existing switch covers. A sync refused because another was in flight is
|
|
174
|
+
* still a terminal outcome with a reason, not a different state. Adding a
|
|
175
|
+
* field is additive; adding an enum member is not.
|
|
210
176
|
*/
|
|
211
177
|
/**
|
|
212
|
-
* **
|
|
213
|
-
*
|
|
214
|
-
* Bis hierher hatte die Bridge eine eigene Zahl und die Cloud eine eigene, und
|
|
215
|
-
* die Registerzeile dazu nannte die der Bridge beim Namen: *„a guess … chosen
|
|
216
|
-
* as a starting number with no measurement behind it"* (DEF-127). Eine Grenze,
|
|
217
|
-
* die der Sender rät und der Empfänger durchsetzt, ist keine Grenze — sie ist
|
|
218
|
-
* zwei Zahlen, die zufällig übereinstimmen, bis eine von beiden sich ändert.
|
|
219
|
-
*
|
|
220
|
-
* Hier steht sie einmal. Die Bridge liest sie, **bevor** sie eine Datei in den
|
|
221
|
-
* Speicher liest; die Cloud setzt sie durch. Ohne das kann die Bridge gar nicht
|
|
222
|
-
* ablehnen, ohne 194 MB zu puffern — was am 2026-08-18 auf rx1 genau so passiert
|
|
223
|
-
* ist (DEF-148).
|
|
224
|
-
*
|
|
225
|
-
* **Sie gilt je Datei, nicht je Sync**, und das steht hier, weil DEF-127 es
|
|
226
|
-
* ausdrücklich verlangt hat: acht Meshes zu je 30 MiB passen durch, eine Datei
|
|
227
|
-
* zu 65 MiB nicht. Wer sie für eine Obergrenze der Übertragung hält, rechnet
|
|
228
|
-
* mit einer Schranke, die es nicht gibt — die Summe eines Syncs bindet das
|
|
229
|
-
* Speicherkontingent der Organisation, und das ist eine andere Zahl an einer
|
|
230
|
-
* anderen Stelle.
|
|
178
|
+
* **The upload ceiling both sides read.**
|
|
231
179
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
180
|
+
* It is stated once, here, rather than once in the producer and once in the
|
|
181
|
+
* cloud. A limit the sender guesses and the receiver enforces is not a limit;
|
|
182
|
+
* it is two numbers that agree until one of them changes.
|
|
234
183
|
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* **laufende** `robot_description`: acht `package://`-Referenzen, zusammen
|
|
239
|
-
* 89.379.096 Bytes, die größte `RX1.dae` mit 38.229.621 — **keine über dem
|
|
240
|
-
* Deckel.**
|
|
184
|
+
* The bridge reads it **before** it reads a file into memory, and the cloud
|
|
185
|
+
* enforces it. Without a shared number a producer cannot refuse an oversized
|
|
186
|
+
* mesh without first buffering the whole of it.
|
|
241
187
|
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* gemessen wurde. Die Beobachtung von damals war korrekt und ihre Erklärung
|
|
248
|
-
* auch.
|
|
188
|
+
* **It applies per file, not per sync.** Eight meshes of 30 MiB each pass; one
|
|
189
|
+
* file of 65 MiB does not. Reading it as a ceiling on a whole transfer means
|
|
190
|
+
* planning against a bound that does not exist — the total of a sync counts
|
|
191
|
+
* against the organisation's storage quota, which is a different number in a
|
|
192
|
+
* different place.
|
|
249
193
|
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* Übertragungszeit und Kontingente, und sie liegt bei André. Sie blockiert
|
|
254
|
-
* nichts: W9bs Gate-Schritt 6 ist gegen die heute laufende Beschreibung
|
|
255
|
-
* erreichbar, ohne dass jemand eine Zahl anfasst.
|
|
194
|
+
* A robot whose meshes exceed this is not a contract question but a question
|
|
195
|
+
* about storage, transfer time and quota, and it is answered by raising the
|
|
196
|
+
* number here, in one place, for both sides.
|
|
256
197
|
*/
|
|
257
198
|
export declare const ASSET_UPLOAD_MAX_BYTES: number;
|
|
258
199
|
export declare const assetTooLargeDetails: z.ZodObject<{
|
|
@@ -274,9 +215,9 @@ export type AssetTooLargeDetails = z.infer<typeof assetTooLargeDetails>;
|
|
|
274
215
|
* - **`upload_failed`** — the bytes exist and the transfer did not succeed.
|
|
275
216
|
* **Transient.** The asset is still wanted; a later sync will carry it.
|
|
276
217
|
* - **`refused`** — never attempted, because a producer-side ceiling was hit
|
|
277
|
-
* (
|
|
278
|
-
* report). **Transient in the same sense**: nothing is known
|
|
279
|
-
* only unexamined.
|
|
218
|
+
* (for example a `.dae` carrying more internal references than one file or
|
|
219
|
+
* one sync will report). **Transient in the same sense**: nothing is known
|
|
220
|
+
* to be missing, only unexamined.
|
|
280
221
|
*
|
|
281
222
|
* A consumer that cannot act on the distinction may still print `reference`
|
|
282
223
|
* alone and lose nothing it had before.
|
|
@@ -413,19 +354,16 @@ export type MissingAssetQuery = z.infer<typeof missingAssetQuery>;
|
|
|
413
354
|
* number leaves the caller unable to decide anything.
|
|
414
355
|
*/
|
|
415
356
|
/**
|
|
416
|
-
*
|
|
357
|
+
* What a `busy` refusal on an asset sync has to carry.
|
|
417
358
|
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* id nicht aufgehoben hatte. Genau die Form, die W6b eine ganze Welle lang
|
|
423
|
-
* ausgeräumt hat: ein Abbruch ohne Job-Id, eine Freigabe ohne Session-Id.
|
|
359
|
+
* A refusal that names a **state** — "a sync is already in progress" — and not
|
|
360
|
+
* the **thing** in that state leaves the caller with nothing to look at. The
|
|
361
|
+
* running sync is observable under `GET .../assets/sync/<id>`, but only to
|
|
362
|
+
* someone who kept its id.
|
|
424
363
|
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* gar nichts und braucht den laufenden Sync im ersten `GET`.
|
|
364
|
+
* These details serve the caller that presses the button again. A client that
|
|
365
|
+
* reloads instead presses nothing, which is why `active_sync` sits on the
|
|
366
|
+
* asset list as well.
|
|
429
367
|
*/
|
|
430
368
|
export declare const assetSyncBusyDetails: z.ZodObject<{
|
|
431
369
|
sync_id: z.ZodUUID;
|