@fleetless/contracts 1.0.5 → 1.0.6
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 +11 -2
- package/CONTRIBUTING.md +100 -75
- package/README.md +69 -83
- package/SECURITY.md +24 -24
- package/artifacts/openapi.json +65 -65
- package/artifacts/routes.json +3 -3
- package/artifacts/schema/app-list-response.schema.json +2 -2
- package/artifacts/schema/app-oidc-provider-list-response.schema.json +1 -1
- package/artifacts/schema/app-oidc-provider.schema.json +1 -1
- package/artifacts/schema/app-user-list-response.schema.json +2 -2
- package/artifacts/schema/app-user.schema.json +2 -2
- package/artifacts/schema/app.schema.json +2 -2
- package/artifacts/schema/asset-list-response.schema.json +7 -7
- package/artifacts/schema/asset-sync-request.schema.json +1 -1
- package/artifacts/schema/asset-sync-status.schema.json +1 -1
- package/artifacts/schema/asset.schema.json +3 -3
- package/artifacts/schema/auth-me-response.schema.json +2 -2
- package/artifacts/schema/auth-ok.schema.json +1 -1
- package/artifacts/schema/authorization-server-metadata.schema.json +1 -1
- package/artifacts/schema/bridge-asset-progress.schema.json +1 -1
- package/artifacts/schema/busy-details.schema.json +3 -3
- package/artifacts/schema/client-identity.schema.json +1 -1
- package/artifacts/schema/client-login-request.schema.json +1 -1
- package/artifacts/schema/client-logout-request.schema.json +1 -1
- package/artifacts/schema/client-mcp-interaction.schema.json +1 -1
- package/artifacts/schema/cloud-config.schema.json +1 -1
- package/artifacts/schema/command-result.schema.json +3 -3
- package/artifacts/schema/config-draft-response.schema.json +1 -1
- package/artifacts/schema/config-version-response.schema.json +1 -1
- package/artifacts/schema/create-server-key-response.schema.json +1 -1
- package/artifacts/schema/datapoint-config.schema.json +1 -1
- package/artifacts/schema/datapoint-value.schema.json +2 -2
- package/artifacts/schema/dynamic-client-registration-request.schema.json +2 -2
- package/artifacts/schema/fleetless-user-list-response.schema.json +2 -2
- package/artifacts/schema/fleetless-user.schema.json +2 -2
- package/artifacts/schema/invoke-or-service-response.schema.json +4 -4
- package/artifacts/schema/invoke-response.schema.json +3 -3
- package/artifacts/schema/job-actor.schema.json +1 -1
- package/artifacts/schema/job-event.schema.json +3 -3
- package/artifacts/schema/job-response.schema.json +3 -3
- package/artifacts/schema/job-run-list-response.schema.json +2 -2
- package/artifacts/schema/job-run.schema.json +2 -2
- package/artifacts/schema/job.schema.json +3 -3
- package/artifacts/schema/mcp-consent-grant-list-response.schema.json +2 -2
- package/artifacts/schema/mcp-consent-grant.schema.json +2 -2
- package/artifacts/schema/oauth-authorize-query.schema.json +1 -1
- package/artifacts/schema/oauth-token-request.schema.json +1 -1
- package/artifacts/schema/patch-org-response.schema.json +1 -1
- package/artifacts/schema/patch-robot-response.schema.json +1 -1
- package/artifacts/schema/robot-config-doc.schema.json +1 -1
- package/artifacts/schema/robot-jobs-response.schema.json +3 -3
- package/artifacts/schema/role-list-response.schema.json +1 -1
- package/artifacts/schema/role.schema.json +1 -1
- package/artifacts/schema/server-key-list-response.schema.json +2 -2
- package/artifacts/schema/server-key.schema.json +1 -1
- package/artifacts/schema/service-call-response.schema.json +1 -1
- package/artifacts/schema/sign-up-response.schema.json +2 -2
- package/artifacts/schema/urdf-completeness.schema.json +2 -2
- package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +1 -1
- package/dist/alerts.d.ts +15 -17
- package/dist/alerts.js +15 -17
- package/dist/app-users.d.ts +5 -6
- package/dist/app-users.js +9 -10
- package/dist/apps.d.ts +5 -6
- package/dist/apps.js +11 -12
- package/dist/assets.js +10 -13
- package/dist/audit.d.ts +10 -12
- package/dist/audit.js +14 -17
- package/dist/client-auth.d.ts +8 -9
- package/dist/client-auth.js +14 -15
- package/dist/config-issues.d.ts +3 -3
- package/dist/config-issues.js +3 -3
- package/dist/config.d.ts +5 -6
- package/dist/config.js +7 -8
- package/dist/errors.d.ts +4 -4
- package/dist/errors.js +8 -9
- package/dist/identity.d.ts +4 -5
- package/dist/identity.js +7 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/jobs.js +5 -5
- package/dist/mcp.d.ts +5 -8
- package/dist/mcp.js +2 -2
- package/dist/oauth.d.ts +8 -10
- package/dist/oauth.js +13 -15
- package/dist/protocol.d.ts +7 -8
- package/dist/protocol.js +20 -22
- package/dist/realtime.js +4 -4
- package/dist/rest.js +4 -4
- package/dist/routes.js +3 -3
- package/package.json +1 -1
package/dist/apps.js
CHANGED
|
@@ -6,7 +6,7 @@ import { slug } from './common.js';
|
|
|
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
|
|
9
|
-
* role does not grant
|
|
9
|
+
* role does not grant does not exist for that user. There is no second
|
|
10
10
|
* visibility mechanism, and adding one later would create two places to look
|
|
11
11
|
* when someone cannot see something.
|
|
12
12
|
*/
|
|
@@ -16,8 +16,8 @@ import { slug } from './common.js';
|
|
|
16
16
|
*
|
|
17
17
|
* **Globally unique, not per org.** `clientLoginRequest` carries only the
|
|
18
18
|
* identifier, the email and the password — there is no org context to
|
|
19
|
-
* disambiguate with, so a per-org identifier could not be resolved at login
|
|
20
|
-
*
|
|
19
|
+
* disambiguate with, so a per-org identifier could not be resolved at login.
|
|
20
|
+
* A collision is refused with `identifier_taken`.
|
|
21
21
|
*/
|
|
22
22
|
export const appIdentifier = slug;
|
|
23
23
|
export const app = z.object({
|
|
@@ -31,7 +31,7 @@ export const app = z.object({
|
|
|
31
31
|
description: 'The display name, shown in the console and available to the developer\'s own pages through the `app.name` mail-template variable. Free text, changed through `PATCH /api/apps/:id`.',
|
|
32
32
|
}),
|
|
33
33
|
identifier: appIdentifier.meta({
|
|
34
|
-
description: 'The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context
|
|
34
|
+
description: 'The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context, so a collision is refused with `identifier_taken`.',
|
|
35
35
|
}),
|
|
36
36
|
/** Robots are referenced individually; tags never grant rights. */
|
|
37
37
|
robot_ids: z.array(z.uuid()).meta({
|
|
@@ -49,7 +49,7 @@ export const app = z.object({
|
|
|
49
49
|
* (`createAppUserRequest`, `createAppInvitationRequest`) rather than
|
|
50
50
|
* pre-filling a form.
|
|
51
51
|
*
|
|
52
|
-
*
|
|
52
|
+
* Worth saying: it changes what a wrong value costs.
|
|
53
53
|
* A prefill somebody can see and correct became a default applied on the
|
|
54
54
|
* server, and the obvious next question — should it be required instead? —
|
|
55
55
|
* has a deliberate answer: no, because an invitation resolves the role at
|
|
@@ -66,7 +66,7 @@ export const app = z.object({
|
|
|
66
66
|
* check that, so `PATCH /api/apps/:id` does.
|
|
67
67
|
*/
|
|
68
68
|
default_role_id: z.uuid().nullable().meta({
|
|
69
|
-
description: 'The role an app user gets when
|
|
69
|
+
description: 'The role an app user gets when created or invited without an explicit one. `null` means this app has not chosen a default, the normal state of an app created before its roles were configured — and then a create or invite that omits `role_id` gets `validation_error`, not a user with no role. An invitation resolves the role when issued, so changing this never re-aims an outstanding one. The role must belong to this app, which `PATCH /api/apps/:id` checks and the schema cannot.',
|
|
70
70
|
}),
|
|
71
71
|
created_at: z.iso.datetime().meta({
|
|
72
72
|
description: 'When the app was created, as an ISO 8601 timestamp. `GET /api/apps` orders by this field.',
|
|
@@ -145,7 +145,7 @@ export const updateAppRequest = z.object({
|
|
|
145
145
|
export const serverKeyToken = z.string().regex(/^flk_[0-9a-f]{32}$/);
|
|
146
146
|
export const serverKey = z.object({
|
|
147
147
|
id: z.uuid().meta({
|
|
148
|
-
description: 'The key row, and what the rotate and delete routes address. It is not the key:
|
|
148
|
+
description: 'The key row, and what the rotate and delete routes address. It is not the key: this shape never carries the secret.',
|
|
149
149
|
}),
|
|
150
150
|
app_id: z.uuid().meta({
|
|
151
151
|
description: 'The app whose full rights this key carries. A key is never shared between apps.',
|
|
@@ -164,7 +164,7 @@ export const serverKey = z.object({
|
|
|
164
164
|
/** What `GET /api/apps/:id/server-keys` answers — metadata only; the raw secret exists once, in `createServerKeyResponse`, and never here. */
|
|
165
165
|
export const serverKeyListResponse = z.object({
|
|
166
166
|
server_keys: z.array(serverKey).meta({
|
|
167
|
-
description: 'The app\'s server keys as metadata, oldest first by `created_at`. The raw secret is not here and never will be: it exists once, in the
|
|
167
|
+
description: 'The app\'s server keys as metadata, oldest first by `created_at`. The raw secret is not here and never will be: it exists once, in the response that created or rotated the key.',
|
|
168
168
|
}),
|
|
169
169
|
});
|
|
170
170
|
export const createServerKeyResponse = z.object({
|
|
@@ -173,9 +173,8 @@ export const createServerKeyResponse = z.object({
|
|
|
173
173
|
});
|
|
174
174
|
/**
|
|
175
175
|
* Every app starts with `observe` and `operate`; custom roles are allowed
|
|
176
|
-
* too. `builtin` marks the two starting roles —
|
|
177
|
-
*
|
|
178
|
-
* they came from.
|
|
176
|
+
* too. `builtin` marks the two starting roles — editable like any other,
|
|
177
|
+
* the flag only tells the console where they came from.
|
|
179
178
|
*/
|
|
180
179
|
export const role = z.object({
|
|
181
180
|
id: z.uuid().meta({
|
|
@@ -188,7 +187,7 @@ export const role = z.object({
|
|
|
188
187
|
description: 'The role\'s name, shown wherever a user\'s access is chosen. The two roles every app starts with are named `observe` and `operate`.',
|
|
189
188
|
}),
|
|
190
189
|
builtin: z.boolean().meta({
|
|
191
|
-
description: '`true` for the two roles every app starts with. Their **rights may be re-scoped** exactly like a custom role\'s, through `PUT /api/apps/:id/roles/:roleId/permissions` — the flag exists so the console can explain where they came from, not to protect them. It does not make them renamable or deletable
|
|
190
|
+
description: '`true` for the two roles every app starts with. Their **rights may be re-scoped** exactly like a custom role\'s, through `PUT /api/apps/:id/roles/:roleId/permissions` — the flag exists so the console can explain where they came from, not to protect them. It does not make them renamable or deletable — no route does that for any role.',
|
|
192
191
|
}),
|
|
193
192
|
});
|
|
194
193
|
/** What `GET /api/apps/:id/roles` answers: the app's roles, builtin and custom alike. */
|
package/dist/assets.js
CHANGED
|
@@ -70,7 +70,7 @@ export const asset = z.object({
|
|
|
70
70
|
id: z.uuid().meta({ description: 'The asset\'s id in the store.' }),
|
|
71
71
|
robot_id: z.uuid().meta({ description: 'The robot this asset belongs to.' }),
|
|
72
72
|
kind: assetKind.meta({
|
|
73
|
-
description: 'What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what
|
|
73
|
+
description: 'What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what to pre-fetch.',
|
|
74
74
|
}),
|
|
75
75
|
/**
|
|
76
76
|
* What the robot called it — for a mesh, the `package://` URI the URDF
|
|
@@ -105,7 +105,7 @@ export const asset = z.object({
|
|
|
105
105
|
* rules, one enforced and one only documented, is how a traversal gets in.
|
|
106
106
|
*/
|
|
107
107
|
name: z.string().min(1).max(500).meta({
|
|
108
|
-
description: 'What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim,
|
|
108
|
+
description: 'What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim, the only string a developer can match against their own workspace. A file the URDF never names (an image a `.dae` loads for itself) is named by joining the mesh\'s own directory with that internal reference.',
|
|
109
109
|
}),
|
|
110
110
|
media_type: z.string().min(1).max(120).meta({
|
|
111
111
|
description: 'The media type of the stored bytes, as the producer reported it.',
|
|
@@ -122,7 +122,7 @@ export const asset = z.object({
|
|
|
122
122
|
* predictable failure of a store that hides it.
|
|
123
123
|
*/
|
|
124
124
|
sha256: z.string().regex(/^[a-f0-9]{64}$/).meta({
|
|
125
|
-
description: 'The content hash, lowercase hex
|
|
125
|
+
description: 'The content hash, lowercase hex. Exposed because it is the only way a client can tell "this is the same mesh I already have" across robots — the reason two robots sharing a mesh cost one copy.',
|
|
126
126
|
}),
|
|
127
127
|
created_at: z.iso.datetime().meta({
|
|
128
128
|
description: 'When the asset was first stored, as an ISO 8601 timestamp.',
|
|
@@ -169,13 +169,13 @@ export const urdfCompleteness = z.object({
|
|
|
169
169
|
*/
|
|
170
170
|
missing: z.array(z.object({
|
|
171
171
|
uri: z.string().min(1).max(500).meta({
|
|
172
|
-
description: 'The reference, verbatim, that no stored asset answers — a `package://` URI the workspace does not hold, or an absolute or bare relative path nothing will ever fetch.
|
|
172
|
+
description: 'The reference, verbatim, that no stored asset answers — a `package://` URI the workspace does not hold, or an absolute or bare relative path nothing will ever fetch.',
|
|
173
173
|
}),
|
|
174
174
|
element: z.enum(['mesh', 'texture']).meta({
|
|
175
175
|
description: 'Which kind of reference it was: geometry the URDF names as a `mesh`, or a `texture` a surface paints with. Without it a client reports a missing texture as a missing mesh, contradicting `mesh_count` beside it.',
|
|
176
176
|
}),
|
|
177
177
|
})).meta({
|
|
178
|
-
description: 'The references nothing in the store answers, each with the element that asked for it. A bare count
|
|
178
|
+
description: 'The references nothing in the store answers, each with the element that asked for it. A bare count would send a developer hunting through the workspace by hand; the references are what they can act on.',
|
|
179
179
|
}),
|
|
180
180
|
});
|
|
181
181
|
/**
|
|
@@ -193,7 +193,7 @@ export const urdfCompleteness = z.object({
|
|
|
193
193
|
*/
|
|
194
194
|
export const assetSyncRequest = z.object({
|
|
195
195
|
source: z.enum(['bridge']).meta({
|
|
196
|
-
description: 'Where the bytes come from. `bridge` is the only value today: the connected bridge reads them from the robot\'s own workspace.
|
|
196
|
+
description: 'Where the bytes come from. `bridge` is the only value today: the connected bridge reads them from the robot\'s own workspace. Validated rather than ignored, so a caller naming an unknown source is told so instead of silently getting a bridge sync.',
|
|
197
197
|
}),
|
|
198
198
|
}).strict();
|
|
199
199
|
export const assetSyncResponse = z.object({
|
|
@@ -253,7 +253,7 @@ export const assetTooLargeDetails = z.object({
|
|
|
253
253
|
description: 'The upload ceiling, in bytes.',
|
|
254
254
|
}),
|
|
255
255
|
size_bytes: z.number().int().positive().meta({
|
|
256
|
-
description: 'How large the refused file
|
|
256
|
+
description: 'How large the refused file is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; "too large" alone answers neither.',
|
|
257
257
|
}),
|
|
258
258
|
});
|
|
259
259
|
/**
|
|
@@ -312,9 +312,8 @@ export const assetFailure = z.object({
|
|
|
312
312
|
description: 'The two numbers behind a `too_large` failure, and absent for every other kind — a forced `null` on every `unresolvable` entry buys nothing. The pairing is enforced, not merely described.',
|
|
313
313
|
}),
|
|
314
314
|
}).superRefine((f, ctx) => {
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
// filled with something else.
|
|
315
|
+
// Enforced here, not merely described above — a rule that lives only in a
|
|
316
|
+
// comment gets filled with something else.
|
|
318
317
|
if (f.kind === 'too_large' && f.details == null) {
|
|
319
318
|
ctx.addIssue({ code: 'custom', path: ['details'], message: '`too_large` without limit_bytes/size_bytes says nothing a developer can act on' });
|
|
320
319
|
}
|
|
@@ -377,8 +376,6 @@ export const assetListResponse = z.object({
|
|
|
377
376
|
description: 'Every asset stored for this robot: the URDF, the meshes it references, and the textures those paint with.',
|
|
378
377
|
}),
|
|
379
378
|
/**
|
|
380
|
-
* The sync running right now, or `null`.
|
|
381
|
-
*
|
|
382
379
|
* **This field exists for the reload case.** A client that holds the
|
|
383
380
|
* `sync_id` only in memory loses its progress display on a refresh, and the
|
|
384
381
|
* state is still there server-side under `GET .../assets/sync/<id>` —
|
|
@@ -409,7 +406,7 @@ export const assetListResponse = z.object({
|
|
|
409
406
|
* crash, and no amount of polling shortens it.
|
|
410
407
|
*/
|
|
411
408
|
urdf_available: z.boolean().nullable().meta({
|
|
412
|
-
description: 'What the connected bridge says it *could* transfer
|
|
409
|
+
description: 'What the connected bridge says it *could* transfer — deliberately separate from what has been transferred. `null` when no bridge is connected, distinct from `false`: "no robot is online to ask" and "the robot has no URDF" send a developer to different places. After a publisher is killed rather than shut down this can read `true` for some seconds, on the underlying DDS liveliness timeout rather than on any check made here.',
|
|
413
410
|
}),
|
|
414
411
|
});
|
|
415
412
|
/**
|
package/dist/audit.d.ts
CHANGED
|
@@ -14,21 +14,19 @@ import { z } from 'zod';
|
|
|
14
14
|
* log — an email, a key name, a robot name — so the console never has to
|
|
15
15
|
* resolve four different id kinds to render a row.
|
|
16
16
|
*
|
|
17
|
-
* **`end_user` stays,
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* cannot be read back is worse than one carrying a retired word.
|
|
17
|
+
* **`end_user` stays, for the rows already written.** The identity split
|
|
18
|
+
* replaced the org's one user pool with Fleetless users and per-app app
|
|
19
|
+
* users; new rows from an app user carry `app_user`. But an audit log must
|
|
20
|
+
* never be rewritten, and stored rows still have `kind: end_user` — dropping
|
|
21
|
+
* the member would fail their own schema. A log that can't be read back is
|
|
22
|
+
* worse than one carrying a retired word.
|
|
24
23
|
*
|
|
25
|
-
* So
|
|
26
|
-
* writes `end_user` any more, and nothing
|
|
27
|
-
* rather than left
|
|
24
|
+
* So the enum is deliberately **wider than what any producer emits**:
|
|
25
|
+
* nothing writes `end_user` any more, and nothing should start again — said
|
|
26
|
+
* here rather than left for a future search to infer.
|
|
28
27
|
*
|
|
29
28
|
* `developer` is a Fleetless user. It kept its name through both redesigns
|
|
30
|
-
* because it was always right
|
|
31
|
-
* robots.
|
|
29
|
+
* because it was always right: the person who configures robots.
|
|
32
30
|
*/
|
|
33
31
|
export declare const auditActor: z.ZodObject<{
|
|
34
32
|
kind: z.ZodEnum<{
|
package/dist/audit.js
CHANGED
|
@@ -15,21 +15,19 @@ import { wireSeqCursor, wireTimestampMs } from './common.js';
|
|
|
15
15
|
* log — an email, a key name, a robot name — so the console never has to
|
|
16
16
|
* resolve four different id kinds to render a row.
|
|
17
17
|
*
|
|
18
|
-
* **`end_user` stays,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* cannot be read back is worse than one carrying a retired word.
|
|
18
|
+
* **`end_user` stays, for the rows already written.** The identity split
|
|
19
|
+
* replaced the org's one user pool with Fleetless users and per-app app
|
|
20
|
+
* users; new rows from an app user carry `app_user`. But an audit log must
|
|
21
|
+
* never be rewritten, and stored rows still have `kind: end_user` — dropping
|
|
22
|
+
* the member would fail their own schema. A log that can't be read back is
|
|
23
|
+
* worse than one carrying a retired word.
|
|
25
24
|
*
|
|
26
|
-
* So
|
|
27
|
-
* writes `end_user` any more, and nothing
|
|
28
|
-
* rather than left
|
|
25
|
+
* So the enum is deliberately **wider than what any producer emits**:
|
|
26
|
+
* nothing writes `end_user` any more, and nothing should start again — said
|
|
27
|
+
* here rather than left for a future search to infer.
|
|
29
28
|
*
|
|
30
29
|
* `developer` is a Fleetless user. It kept its name through both redesigns
|
|
31
|
-
* because it was always right
|
|
32
|
-
* robots.
|
|
30
|
+
* because it was always right: the person who configures robots.
|
|
33
31
|
*/
|
|
34
32
|
export const auditActor = z.object({
|
|
35
33
|
kind: z.enum(['developer', 'end_user', 'app_user', 'server_key', 'bridge']),
|
|
@@ -130,12 +128,11 @@ export const auditQuery = z.object({
|
|
|
130
128
|
* Everything under a dotted prefix, e.g. `server_key.` for all three
|
|
131
129
|
* server-key actions.
|
|
132
130
|
*
|
|
133
|
-
* **A separate parameter, not a widening of `action`.** The
|
|
134
|
-
* `action` above
|
|
135
|
-
*
|
|
136
|
-
* one it is. Setting both is refused rather than resolved, because a query
|
|
131
|
+
* **A separate parameter, not a widening of `action`.** The rule on
|
|
132
|
+
* `action` above still stands; this is a different question with a name
|
|
133
|
+
* that says which one it is. Setting both is refused, not resolved —
|
|
137
134
|
* naming an exact action *and* a prefix is a caller mistake, not a
|
|
138
|
-
* combination
|
|
135
|
+
* combination to guess the meaning of.
|
|
139
136
|
*
|
|
140
137
|
* **The published artifact cannot express that refusal**: a cross-field
|
|
141
138
|
* `.refine()` has no JSON Schema rendering, so `audit-query.schema.json`
|
package/dist/client-auth.d.ts
CHANGED
|
@@ -48,13 +48,12 @@ export type ClientRefreshRequest = z.infer<typeof clientRefreshRequest>;
|
|
|
48
48
|
*
|
|
49
49
|
* **The route answers `204` and has no response shape.** It used to answer a
|
|
50
50
|
* `clientLogoutResponse` reporting what was left of the session at the identity
|
|
51
|
-
* provider — RP-initiated logout, an `end_session_endpoint
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* quietly kept as a field nobody fills.
|
|
51
|
+
* provider — RP-initiated logout, an `end_session_endpoint`, four ways of
|
|
52
|
+
* saying "we cannot end that session". That belonged to the hosted login flow,
|
|
53
|
+
* where Fleetless owned the browser; it doesn't any more. The developer's app
|
|
54
|
+
* does, and knows its own provider better than Fleetless ever did — ending a
|
|
55
|
+
* provider session is its own redirect to make. Listed as a breaking change,
|
|
56
|
+
* not quietly kept as a field nobody fills.
|
|
58
57
|
*/
|
|
59
58
|
export declare const clientLogoutRequest: z.ZodObject<{
|
|
60
59
|
refresh_token: z.ZodString;
|
|
@@ -110,8 +109,8 @@ export type ClientResendVerificationRequest = z.infer<typeof clientResendVerific
|
|
|
110
109
|
* to be. An **app identifier** no app carries is the one refusal, `404
|
|
111
110
|
* not_found`, because an identifier is public and an address is not.
|
|
112
111
|
*
|
|
113
|
-
* Moved here from `identity.ts`,
|
|
114
|
-
*
|
|
112
|
+
* Moved here from `identity.ts`, which predates a client-surface file — it's
|
|
113
|
+
* an app-user shape and belongs with them.
|
|
115
114
|
*/
|
|
116
115
|
export declare const clientPasswordResetRequest: z.ZodObject<{
|
|
117
116
|
app_identifier: z.ZodString;
|
package/dist/client-auth.js
CHANGED
|
@@ -37,7 +37,7 @@ import { password } from './identity.js';
|
|
|
37
37
|
/* ------------------------------------------------------ password login -- */
|
|
38
38
|
export const clientLoginRequest = z.object({
|
|
39
39
|
app_identifier: appIdentifier.meta({
|
|
40
|
-
description: 'The app being logged in to
|
|
40
|
+
description: 'The app being logged in to: its globally unique, lowercase, underscore-separated identifier, chosen by the developer at creation. There is no organisation context at login, so this is what decides which app the credentials are checked for.',
|
|
41
41
|
}),
|
|
42
42
|
email: z.email().meta({
|
|
43
43
|
description: 'The app user\'s address. Addresses are unique **per app**, not across Fleetless: the same address may be an unrelated account in another app of the same organisation, so this pair is what identifies a person here.',
|
|
@@ -58,17 +58,16 @@ export const clientRefreshRequest = z.object({
|
|
|
58
58
|
*
|
|
59
59
|
* **The route answers `204` and has no response shape.** It used to answer a
|
|
60
60
|
* `clientLogoutResponse` reporting what was left of the session at the identity
|
|
61
|
-
* provider — RP-initiated logout, an `end_session_endpoint
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* quietly kept as a field nobody fills.
|
|
61
|
+
* provider — RP-initiated logout, an `end_session_endpoint`, four ways of
|
|
62
|
+
* saying "we cannot end that session". That belonged to the hosted login flow,
|
|
63
|
+
* where Fleetless owned the browser; it doesn't any more. The developer's app
|
|
64
|
+
* does, and knows its own provider better than Fleetless ever did — ending a
|
|
65
|
+
* provider session is its own redirect to make. Listed as a breaking change,
|
|
66
|
+
* not quietly kept as a field nobody fills.
|
|
68
67
|
*/
|
|
69
68
|
export const clientLogoutRequest = z.object({
|
|
70
69
|
refresh_token: z.string().min(1).meta({
|
|
71
|
-
description: 'Any refresh token of the session to end. The whole token family is revoked server-side, so a token stolen before this call stops working too — clearing a client-side store is a gesture, not a revocation. The answer is `204`: a token the server does not recognise gets it too, since the end state
|
|
70
|
+
description: 'Any refresh token of the session to end. The whole token family is revoked server-side, so a token stolen before this call stops working too — clearing a client-side store is a gesture, not a revocation. The answer is `204`: a token the server does not recognise gets it too, since that is the end state being asked for.',
|
|
72
71
|
}),
|
|
73
72
|
});
|
|
74
73
|
/* ---------------------------------------------- registration and mails -- */
|
|
@@ -137,8 +136,8 @@ export const clientResendVerificationRequest = z
|
|
|
137
136
|
* to be. An **app identifier** no app carries is the one refusal, `404
|
|
138
137
|
* not_found`, because an identifier is public and an address is not.
|
|
139
138
|
*
|
|
140
|
-
* Moved here from `identity.ts`,
|
|
141
|
-
*
|
|
139
|
+
* Moved here from `identity.ts`, which predates a client-surface file — it's
|
|
140
|
+
* an app-user shape and belongs with them.
|
|
142
141
|
*/
|
|
143
142
|
export const clientPasswordResetRequest = z
|
|
144
143
|
.object({
|
|
@@ -373,7 +372,7 @@ export const clientMcpInteraction = z.object({
|
|
|
373
372
|
app_id: z.uuid().meta({ description: 'The app this authorization is for. The approving token\'s `app_id` must match it — an interaction of one app cannot be approved with a session from another.' }),
|
|
374
373
|
client_name: z.string().nullable().meta({ description: 'What the MCP client calls itself, or `null` if it named nothing. **Unverified** — see `client_name_verified`.' }),
|
|
375
374
|
client_name_verified: z.literal(false).meta({
|
|
376
|
-
description: 'Always `false`. The client registered itself without authentication and
|
|
375
|
+
description: 'Always `false`. The client registered itself without authentication and named itself, so it must be rendered as a claim and never as an identity. There is no verified case, which is why this is a literal and not a boolean: a `true` branch would be dead code that looked like a safeguard.',
|
|
377
376
|
}),
|
|
378
377
|
scopes: z.array(z.string()).meta({ description: 'The scopes the client asked for, to show the person before they approve.' }),
|
|
379
378
|
already_granted: z.boolean().meta({ description: 'Whether this user has already approved this client. It is a record of what they answered last time, and **this route makes no second use of it**: an app that skips its own consent screen when this is `true` is the only thing deciding that, and approve succeeds identically for a user who holds no grant at all. The standing grant is read elsewhere, on every request to the app\'s MCP endpoint. Withdrawing it is `DELETE /api/client/mcp/grants/:clientId` for the person themselves and `DELETE /api/apps/:id/users/:userId/mcp-grants/:clientId` for the developer. A withdrawal makes this `false` again at the next authorization **and stops the client at its very next MCP call**, unexpired access token and all — up to fifteen minutes of it — because the endpoint keys that check on the `client_id` the token carries.' }),
|
|
@@ -417,10 +416,10 @@ export const mcpConsentGrant = z.object({
|
|
|
417
416
|
description: 'The MCP client this consent is for, as its dynamic registration was issued. It is the value the withdrawal routes take in their path, and it is the only stable handle on a client — the name beside it is not one.',
|
|
418
417
|
}),
|
|
419
418
|
client_name: z.string().nullable().meta({
|
|
420
|
-
description: 'What the client calls itself, or `null`
|
|
419
|
+
description: 'What the client calls itself, or `null` once its registration is gone. **Unverified** — see `client_name_verified`.',
|
|
421
420
|
}),
|
|
422
421
|
client_name_verified: z.literal(false).meta({
|
|
423
|
-
description: 'Always `false`. The client registered itself without authentication and
|
|
422
|
+
description: 'Always `false`. The client registered itself without authentication and named itself, so it must be rendered as a claim and never as an identity. There is no verified case, which is why this is a literal and not a boolean: a `true` branch would be dead code that looked like a safeguard.',
|
|
424
423
|
}),
|
|
425
424
|
granted_at: z.iso.datetime().meta({
|
|
426
425
|
description: 'When the consent was last given. A withdrawal followed by a fresh approval moves it, because the second approval is the agreement that stands — it is not a record of the first time anybody ever said yes.',
|
|
@@ -463,7 +462,7 @@ export const mcpConsentGrantListResponse = z.object({
|
|
|
463
462
|
*/
|
|
464
463
|
export const clientIdentity = z.object({
|
|
465
464
|
kind: z.enum(['developer', 'app_user', 'server_key']).meta({
|
|
466
|
-
description: 'Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright
|
|
465
|
+
description: 'Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright, not inferred from which id is set.',
|
|
467
466
|
}),
|
|
468
467
|
developer_id: z.uuid().nullable().meta({
|
|
469
468
|
description: 'The Fleetless user behind this session, or `null` when `kind` is not `developer`.',
|
package/dist/config-issues.d.ts
CHANGED
|
@@ -92,9 +92,9 @@ export declare function schemaIssues(value: unknown, issues: readonly SchemaIssu
|
|
|
92
92
|
* `configDraftResponse`, not one issue: a client's `safeParse` drops the
|
|
93
93
|
* response and hands the editor nothing, for two characters typed.
|
|
94
94
|
*
|
|
95
|
-
* The quoted spelling is the segment's JSON string literal
|
|
96
|
-
*
|
|
97
|
-
*
|
|
95
|
+
* The quoted spelling is the segment's JSON string literal — the whole reason
|
|
96
|
+
* for choosing it: JSON's string syntax is a subset of YAML's double-quoted
|
|
97
|
+
* scalar syntax, so `""`, `" "` and `"\t"` are each a
|
|
98
98
|
* valid YAML spelling of exactly the key being complained about. The path is
|
|
99
99
|
* therefore text the developer can search their own file for — which is the
|
|
100
100
|
* bar this has to clear. It is also the same move `DOCUMENT_ROOT_PATH` makes
|
package/dist/config-issues.js
CHANGED
|
@@ -111,9 +111,9 @@ function slugOf(path) {
|
|
|
111
111
|
* `configDraftResponse`, not one issue: a client's `safeParse` drops the
|
|
112
112
|
* response and hands the editor nothing, for two characters typed.
|
|
113
113
|
*
|
|
114
|
-
* The quoted spelling is the segment's JSON string literal
|
|
115
|
-
*
|
|
116
|
-
*
|
|
114
|
+
* The quoted spelling is the segment's JSON string literal — the whole reason
|
|
115
|
+
* for choosing it: JSON's string syntax is a subset of YAML's double-quoted
|
|
116
|
+
* scalar syntax, so `""`, `" "` and `"\t"` are each a
|
|
117
117
|
* valid YAML spelling of exactly the key being complained about. The path is
|
|
118
118
|
* therefore text the developer can search their own file for — which is the
|
|
119
119
|
* bar this has to clear. It is also the same move `DOCUMENT_ROOT_PATH` makes
|
package/dist/config.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { z } from 'zod';
|
|
|
20
20
|
* field as **required** in the generated JSON Schema, because after parsing
|
|
21
21
|
* it is always present. That is recorded in `scripts/export-schemas.ts`, whose
|
|
22
22
|
* remedy (`io: 'input'`, applied per schema) is a judgement call across dozens
|
|
23
|
-
* of schemas and every consumer that vendors them. So this field
|
|
24
|
-
*
|
|
23
|
+
* of schemas and every consumer that vendors them. So this field does not add
|
|
24
|
+
* another instance: optional is optional in both modes, and *absent* is
|
|
25
25
|
* the single spelling of "not described". `.min(1)` keeps the empty string from
|
|
26
26
|
* becoming a second.
|
|
27
27
|
*/
|
|
@@ -144,10 +144,9 @@ export declare const parameterMap: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
|
144
144
|
*
|
|
145
145
|
* **This constant is the only list.** The cloud builds its set from it and
|
|
146
146
|
* emits `reserved_slug`; the rename path reads it for the target; an editor
|
|
147
|
-
* reads it for slug suggestion and repairs. Nothing copies the members.
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* publishes.
|
|
147
|
+
* reads it for slug suggestion and repairs. Nothing copies the members. It is
|
|
148
|
+
* NOT the enumeration of built-in datapoints — those are kept separately,
|
|
149
|
+
* and must be, because a reserved name exists that nothing publishes.
|
|
151
150
|
*
|
|
152
151
|
* **What it does not do: `robotConfigDoc` does not enforce it.** Reservation is
|
|
153
152
|
* a semantic check that belongs with the ones that need the robot's context,
|
package/dist/config.js
CHANGED
|
@@ -379,8 +379,8 @@ const underSlug = (slugKey, snippet) => ({ ...snippet, body: { [slugKey]: snippe
|
|
|
379
379
|
* field as **required** in the generated JSON Schema, because after parsing
|
|
380
380
|
* it is always present. That is recorded in `scripts/export-schemas.ts`, whose
|
|
381
381
|
* remedy (`io: 'input'`, applied per schema) is a judgement call across dozens
|
|
382
|
-
* of schemas and every consumer that vendors them. So this field
|
|
383
|
-
*
|
|
382
|
+
* of schemas and every consumer that vendors them. So this field does not add
|
|
383
|
+
* another instance: optional is optional in both modes, and *absent* is
|
|
384
384
|
* the single spelling of "not described". `.min(1)` keeps the empty string from
|
|
385
385
|
* becoming a second.
|
|
386
386
|
*/
|
|
@@ -582,7 +582,7 @@ export const parameterSpec = strictObject({
|
|
|
582
582
|
* Without this, a default is the one way past bounds that are otherwise
|
|
583
583
|
* the enforcement point. `min_value`/`max_value` are the speed limit that
|
|
584
584
|
* actually holds, enforced in the cloud before anything reaches the robot.
|
|
585
|
-
* But a caller who
|
|
585
|
+
* But a caller who omits the parameter gets the
|
|
586
586
|
* default, and the bridge fills it at the template walk without
|
|
587
587
|
* re-checking bounds, deliberately: a second enforcement point there
|
|
588
588
|
* would be the weaker of two policies. So `{min_value: -1, max_value: 1,
|
|
@@ -658,10 +658,9 @@ export const parameterMap = slugKeyed(parameterSpec)
|
|
|
658
658
|
*
|
|
659
659
|
* **This constant is the only list.** The cloud builds its set from it and
|
|
660
660
|
* emits `reserved_slug`; the rename path reads it for the target; an editor
|
|
661
|
-
* reads it for slug suggestion and repairs. Nothing copies the members.
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
* publishes.
|
|
661
|
+
* reads it for slug suggestion and repairs. Nothing copies the members. It is
|
|
662
|
+
* NOT the enumeration of built-in datapoints — those are kept separately,
|
|
663
|
+
* and must be, because a reserved name exists that nothing publishes.
|
|
665
664
|
*
|
|
666
665
|
* **What it does not do: `robotConfigDoc` does not enforce it.** Reservation is
|
|
667
666
|
* a semantic check that belongs with the ones that need the robot's context,
|
|
@@ -993,7 +992,7 @@ export const datapointConfig = strictObject({
|
|
|
993
992
|
examples: [2, 0.5],
|
|
994
993
|
}).optional(),
|
|
995
994
|
description: serviceDescription.meta({
|
|
996
|
-
description: 'Prose about what this value is, for whoever meets it in the console
|
|
995
|
+
description: 'Prose about what this value is, for whoever meets it in the console. It changes nothing the robot does, so a publish that touches only it pushes no configuration at all — but it is carried verbatim into `robot_describe`, where a model that has never seen this robot reads it. The datapoint is offered whenever the role grants it; without one it is offered with `description: null` and the model has less to go on, as for actions, services, publishers and cameras. Omission is the only way to say nothing; an empty string is refused, here and on all five.',
|
|
997
996
|
/**
|
|
998
997
|
* The sentence both datapoint snippets already place here, verbatim. Its
|
|
999
998
|
* four siblings — an action's, a service's, a publisher's, a camera's —
|
package/dist/errors.d.ts
CHANGED
|
@@ -19,10 +19,10 @@ export type ApiError = z.infer<typeof apiError>;
|
|
|
19
19
|
* console renders a third, and each is right in its own tests.
|
|
20
20
|
*
|
|
21
21
|
* `field` is the **flat key exactly as the caller sent it** — the same string
|
|
22
|
-
* as the `parameterSpec.name` it violated. That is the
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* as the `parameterSpec.name` it violated. That is why the parameter form
|
|
23
|
+
* stays flat: a refusal has to name something the caller can find in what
|
|
24
|
+
* they typed, and a console can attach the error to that one input rather
|
|
25
|
+
* than to the form.
|
|
26
26
|
*/
|
|
27
27
|
export declare const parameterViolation: z.ZodObject<{
|
|
28
28
|
field: z.ZodString;
|
package/dist/errors.js
CHANGED
|
@@ -18,10 +18,10 @@ export const apiError = z.object({
|
|
|
18
18
|
* console renders a third, and each is right in its own tests.
|
|
19
19
|
*
|
|
20
20
|
* `field` is the **flat key exactly as the caller sent it** — the same string
|
|
21
|
-
* as the `parameterSpec.name` it violated. That is the
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* as the `parameterSpec.name` it violated. That is why the parameter form
|
|
22
|
+
* stays flat: a refusal has to name something the caller can find in what
|
|
23
|
+
* they typed, and a console can attach the error to that one input rather
|
|
24
|
+
* than to the form.
|
|
25
25
|
*/
|
|
26
26
|
export const parameterViolation = z.object({
|
|
27
27
|
field: z.string().min(1),
|
|
@@ -81,11 +81,10 @@ export const ERROR_CODES = [
|
|
|
81
81
|
'invalid_credentials',
|
|
82
82
|
'token_expired',
|
|
83
83
|
'token_revoked',
|
|
84
|
-
/* `invite_expired` and `invite_used` were removed on 2026-09-05
|
|
85
|
-
* reasoning
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* either.
|
|
84
|
+
/* `invite_expired` and `invite_used` were removed on 2026-09-05 — same
|
|
85
|
+
* reasoning and evidence as `not_a_member`: a `grep` across contracts, cloud,
|
|
86
|
+
* sdk, console and bridge found only their own entries here and one test
|
|
87
|
+
* asserting those entries existed. Nothing has ever emitted either.
|
|
89
88
|
*
|
|
90
89
|
* They were written for `POST /api/client/invitations/accept`, to tell an
|
|
91
90
|
* expired invitation from an already-accepted one. That route answers `410
|
package/dist/identity.d.ts
CHANGED
|
@@ -19,11 +19,10 @@ import { z } from 'zod';
|
|
|
19
19
|
*
|
|
20
20
|
* **What that deleted, with no successor**: groups and the Org Admins
|
|
21
21
|
* group, app assignments, impersonation, the per-user MCP override and the
|
|
22
|
-
* org-level federation policy. The 2026-08-29 model
|
|
23
|
-
* users
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* reason.
|
|
22
|
+
* org-level federation policy. The 2026-08-29 model pooled developers and end
|
|
23
|
+
* users per org and joined them with all of the above. In use, wrong model:
|
|
24
|
+
* the two populations have different lifecycles, and every joining mechanism
|
|
25
|
+
* was cost without a product reason.
|
|
27
26
|
*
|
|
28
27
|
* What did **not** change: the password rules, the session and token shapes,
|
|
29
28
|
* and the enumeration-oracle reasoning on password reset. None of those was
|
package/dist/identity.js
CHANGED
|
@@ -19,11 +19,10 @@ import { z } from 'zod';
|
|
|
19
19
|
*
|
|
20
20
|
* **What that deleted, with no successor**: groups and the Org Admins
|
|
21
21
|
* group, app assignments, impersonation, the per-user MCP override and the
|
|
22
|
-
* org-level federation policy. The 2026-08-29 model
|
|
23
|
-
* users
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* reason.
|
|
22
|
+
* org-level federation policy. The 2026-08-29 model pooled developers and end
|
|
23
|
+
* users per org and joined them with all of the above. In use, wrong model:
|
|
24
|
+
* the two populations have different lifecycles, and every joining mechanism
|
|
25
|
+
* was cost without a product reason.
|
|
27
26
|
*
|
|
28
27
|
* What did **not** change: the password rules, the session and token shapes,
|
|
29
28
|
* and the enumeration-oracle reasoning on password reset. None of those was
|
|
@@ -78,7 +77,7 @@ export const org = z.object({
|
|
|
78
77
|
/** What `PATCH /api/org` answers: the org as it now stands. */
|
|
79
78
|
export const patchOrgResponse = z.object({
|
|
80
79
|
org: org.meta({
|
|
81
|
-
description: 'The organisation as it now stands, after the patch
|
|
80
|
+
description: 'The organisation as it now stands, after the patch. The whole resource comes back, not only the changed fields.',
|
|
82
81
|
}),
|
|
83
82
|
});
|
|
84
83
|
/**
|
|
@@ -104,7 +103,7 @@ export const fleetlessUser = z.object({
|
|
|
104
103
|
description: 'The Fleetless user in the API, assigned by the cloud and stable for the life of the account.',
|
|
105
104
|
}),
|
|
106
105
|
org_id: z.uuid().meta({
|
|
107
|
-
description: 'The organisation this person belongs to. Every developer route is already scoped to the caller\'s org, so this confirms what a client is looking at
|
|
106
|
+
description: 'The organisation this person belongs to. Every developer route is already scoped to the caller\'s org, so this confirms what a client is looking at, not a filter it applies.',
|
|
108
107
|
}),
|
|
109
108
|
email: z.email().meta({
|
|
110
109
|
description: 'The address the account is identified by, **globally unique** across every organisation. Immutable after creation: it is what every invitation, reset link and audit line names.',
|
|
@@ -113,7 +112,7 @@ export const fleetlessUser = z.object({
|
|
|
113
112
|
description: 'Optional human name, shown by the console instead of the address where present. Self-service through `PATCH /api/auth/me`; never used for authentication. `null` when the person never supplied one.',
|
|
114
113
|
}),
|
|
115
114
|
tier: orgAdminTier.meta({
|
|
116
|
-
description: 'The console powers this person holds. **Required** — every Fleetless user
|
|
115
|
+
description: 'The console powers this person holds. **Required** — every Fleetless user has a tier; it was optional only while the org also held people with no console powers to grade, and that pool is gone.',
|
|
117
116
|
}),
|
|
118
117
|
created_at: z.iso.datetime().meta({
|
|
119
118
|
description: 'When the account was created, as an ISO 8601 timestamp.',
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type { ParameterType, ParameterSpec, ActionConfig, ServiceConfig, Publish
|
|
|
15
15
|
/**
|
|
16
16
|
* The one account of what is wrong with a configuration document, shared by
|
|
17
17
|
* every layer that reports on one. See `config-issues.ts`'s header for why a
|
|
18
|
-
* second copy of this vocabulary is a defect
|
|
18
|
+
* second copy of this vocabulary is a defect, not a convenience.
|
|
19
19
|
*/
|
|
20
20
|
export { DOCUMENT_ROOT_PATH, EXPOSURE_SECTIONS, schemaIssues, formatPath, splitFormatPath, configSchemaHash, } from './config-issues.js';
|
|
21
21
|
export type { SchemaIssue, ExposureSection } from './config-issues.js';
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ cameraSource, cameraCredentials, } from './config.js';
|
|
|
16
16
|
/**
|
|
17
17
|
* The one account of what is wrong with a configuration document, shared by
|
|
18
18
|
* every layer that reports on one. See `config-issues.ts`'s header for why a
|
|
19
|
-
* second copy of this vocabulary is a defect
|
|
19
|
+
* second copy of this vocabulary is a defect, not a convenience.
|
|
20
20
|
*/
|
|
21
21
|
export { DOCUMENT_ROOT_PATH, EXPOSURE_SECTIONS, schemaIssues, formatPath, splitFormatPath, configSchemaHash, } from './config-issues.js';
|
|
22
22
|
export { rosGraphEntry, rosGraph, typeField, typeDefinition, parameterFieldsOf } from './introspection.js';
|