@fleetless/contracts 6.0.0-next.1 → 6.0.0-next.3

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 CHANGED
@@ -90,6 +90,18 @@ version.
90
90
  `acceptTeamInviteRequest` takes an optional `display_name` and no
91
91
  password. The default invitation mail no longer asks the invitee to
92
92
  choose a password.
93
+ - **`appInvitation.accept_url` is never `null`**: an app with no
94
+ `invite_url` gets the hosted invitation page's link.
95
+ - **`appMailTemplateListResponse.templates` holds up to four** (was three),
96
+ with `login_code`.
97
+ - **The portal's identify step mails a sign-in code** (`POST
98
+ /console/oauth/identify`, `POST /mcp/oauth/identify`) instead of handing
99
+ back a password step, and answers the same for every address.
100
+ - **A password reset and a password change are refused with `403
101
+ method_not_allowed`** while the app has the password method off — `POST
102
+ /api/client/password/reset`, `…/password/reset/confirm`,
103
+ `…/password/change` and the developer's `POST
104
+ /api/apps/:id/users/:userId/reset-password`.
93
105
 
94
106
  ### Removed
95
107
 
@@ -102,11 +114,49 @@ version.
102
114
  /console/oauth/login` and `POST /mcp/oauth/login`. Fleetless users sign
103
115
  in by emailed code or passkey; the portal sign-up is the one way to
104
116
  create an organisation. `passwordChangeRequest` stays, for `POST
105
- /api/client/password/change`. Together with the newly required fields
106
- above (`appUser.two_factor`, `org.require_two_factor`,
117
+ /api/client/password/change`. The newly required fields above
118
+ (`appUser.two_factor`, `org.require_two_factor`,
107
119
  `fleetlessUser.two_factor`, `clientIdentity.two_factor_enabled`,
108
120
  `clientProviderListResponse.sign_in_methods`) and the narrowed `mcp`
109
- slice, this is why the release is a major.
121
+ slice break existing readers as well.
122
+
123
+ ## [5.3.0] — 2026-10-02
124
+
125
+ ### Added
126
+
127
+ - **`feedbackRequest` and `feedbackResponse`** (`FeedbackRequest`,
128
+ `FeedbackResponse`) for the new `POST /api/feedback`: a developer's
129
+ message to the people who build Fleetless, `{ kind, message, page }` with
130
+ `kind` one of `FEEDBACK_KINDS` (`idea`, `problem`, `question`, `other`), a
131
+ trimmed message of at most `FEEDBACK_MESSAGE_MAX` (5000) characters and
132
+ the console path it was sent from. The route answers `202` with
133
+ `{ id, mail }`: the message is stored before any mail is tried, so `mail`
134
+ (`sent`, `failed`, `not_configured`) never means it was lost. Rate
135
+ limited to 10 per developer per hour. Published as the artifacts
136
+ `feedback-request` and `feedback-response`.
137
+ - **`jobActor.name`**: the person's display name when the job started,
138
+ required and nullable — `null` for a server key, a person without a name
139
+ and runs recorded before this version; show `label` then. `jobActor`
140
+ stays non-strict, so a consumer on 5.2.0 strips the new key rather than
141
+ refusing the run. Changes the artifacts `job-actor`, `job-run` and
142
+ `job-run-list-response`.
143
+ - **`roleRenameRequest`, `roleDeleteQuery` and `roleInUseDetails`**
144
+ (`RoleRenameRequest`, `RoleDeleteQuery`, `RoleInUseDetails`) for the new
145
+ `PATCH` and `DELETE /api/apps/:id/roles/:roleId`: rename a role
146
+ (`409 role_name_taken` on a clash), or delete it, moving its app users,
147
+ pending invitations and default-role status to `move_to`. Without
148
+ `move_to` a held role answers `409 role_in_use` with
149
+ `{ users, invitations, is_default }`; the app's only role answers
150
+ `409 last_role`. The three codes join `ERROR_CODES`. `role.builtin` no
151
+ longer says built-in roles cannot be renamed or deleted, and the create
152
+ route's note now states the 60-character limit `role.name` always had.
153
+ Published as the artifacts `role-rename-request`, `role-delete-query`
154
+ and `role-in-use-details`.
155
+ - **`auditQuery.target_id`**: only the events about one target — for a
156
+ robot also those that name it in `details.robot_id`, so a robot's log
157
+ includes what was started on it. A string, since target ids are not all
158
+ uuids. `GET /api/audit/export` takes it too. Changes the artifact
159
+ `audit-query`.
110
160
 
111
161
  ## [5.2.0] — 2026-09-30
112
162
 
@@ -732,6 +732,17 @@
732
732
  "maxLength": 40
733
733
  }
734
734
  },
735
+ {
736
+ "name": "target_id",
737
+ "in": "query",
738
+ "required": false,
739
+ "schema": {
740
+ "description": "Events whose target is this id; for a robot also the events that name it in `details.robot_id` (`action.invoked`, `service.called`, …), so a robot's events include what was started on it.",
741
+ "type": "string",
742
+ "minLength": 1,
743
+ "maxLength": 200
744
+ }
745
+ },
735
746
  {
736
747
  "name": "from_ms",
737
748
  "in": "query",
@@ -883,6 +894,17 @@
883
894
  "maxLength": 40
884
895
  }
885
896
  },
897
+ {
898
+ "name": "target_id",
899
+ "in": "query",
900
+ "required": false,
901
+ "schema": {
902
+ "description": "Events whose target is this id; for a robot also the events that name it in `details.robot_id` (`action.invoked`, `service.called`, …), so a robot's events include what was started on it.",
903
+ "type": "string",
904
+ "minLength": 1,
905
+ "maxLength": 200
906
+ }
907
+ },
886
908
  {
887
909
  "name": "from_ms",
888
910
  "in": "query",
@@ -1265,7 +1287,7 @@
1265
1287
  }
1266
1288
  }
1267
1289
  },
1268
- "description": "The body is `{ \"name\": string }` — non-empty, trimmed, at most 120 characters — and is deliberately not a contract shape: contracts define the `role` this answers with, not this one trivial request. **The answer is a bare `role`, not an envelope**, unlike the listing beside it."
1290
+ "description": "The body is `{ \"name\": string }` — non-empty, trimmed, at most 60 characters as on `role.name` — and is deliberately not a contract shape: contracts define the `role` this answers with, not this one trivial request. **The answer is a bare `role`, not an envelope**, unlike the listing beside it."
1269
1291
  },
1270
1292
  "get": {
1271
1293
  "operationId": "get_api_apps_id_roles",
@@ -1492,6 +1514,132 @@
1492
1514
  "description": "Built by the same builder the MCP server's own `robot_describe` uses, so the two cannot drift. It answers what the role *would* be offered and consults nothing about any user's actual MCP entitlement. A robot the role grants nothing on still appears, with an empty `exposures` — dropping it would read as \"not attached\", which is a different fact."
1493
1515
  }
1494
1516
  },
1517
+ "/api/apps/{id}/roles/{roleId}": {
1518
+ "patch": {
1519
+ "operationId": "patch_api_apps_id_roles_roleId",
1520
+ "summary": "Renames a role; its users keep it.",
1521
+ "tags": [
1522
+ "apps"
1523
+ ],
1524
+ "security": [
1525
+ {
1526
+ "developerSession": []
1527
+ }
1528
+ ],
1529
+ "parameters": [
1530
+ {
1531
+ "name": "id",
1532
+ "in": "path",
1533
+ "required": true,
1534
+ "description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
1535
+ "schema": {
1536
+ "type": "string"
1537
+ }
1538
+ },
1539
+ {
1540
+ "name": "roleId",
1541
+ "in": "path",
1542
+ "required": true,
1543
+ "description": "The role's uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`.",
1544
+ "schema": {
1545
+ "type": "string"
1546
+ }
1547
+ }
1548
+ ],
1549
+ "responses": {
1550
+ "200": {
1551
+ "description": "Success.",
1552
+ "content": {
1553
+ "application/json": {
1554
+ "schema": {
1555
+ "$ref": "#/components/schemas/role"
1556
+ }
1557
+ }
1558
+ }
1559
+ },
1560
+ "default": {
1561
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `validation_error`, `role_name_taken`.",
1562
+ "content": {
1563
+ "application/json": {
1564
+ "schema": {
1565
+ "$ref": "#/components/schemas/api-error"
1566
+ }
1567
+ }
1568
+ }
1569
+ }
1570
+ },
1571
+ "description": "Names are unique per app, compared exactly as stored after trimming. Built-in roles can be renamed.",
1572
+ "requestBody": {
1573
+ "required": true,
1574
+ "content": {
1575
+ "application/json": {
1576
+ "schema": {
1577
+ "$ref": "#/components/schemas/role-rename-request"
1578
+ }
1579
+ }
1580
+ }
1581
+ }
1582
+ },
1583
+ "delete": {
1584
+ "operationId": "delete_api_apps_id_roles_roleId",
1585
+ "summary": "Deletes a role, moving its users, pending invitations and default-role status to another role.",
1586
+ "tags": [
1587
+ "apps"
1588
+ ],
1589
+ "security": [
1590
+ {
1591
+ "developerSession": []
1592
+ }
1593
+ ],
1594
+ "parameters": [
1595
+ {
1596
+ "name": "id",
1597
+ "in": "path",
1598
+ "required": true,
1599
+ "description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
1600
+ "schema": {
1601
+ "type": "string"
1602
+ }
1603
+ },
1604
+ {
1605
+ "name": "roleId",
1606
+ "in": "path",
1607
+ "required": true,
1608
+ "description": "The role's uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`.",
1609
+ "schema": {
1610
+ "type": "string"
1611
+ }
1612
+ },
1613
+ {
1614
+ "name": "move_to",
1615
+ "in": "query",
1616
+ "required": false,
1617
+ "schema": {
1618
+ "description": "Another role of the same app that takes over the deleted role's app users, pending invitations and, when it applies, the app's default. The role itself or a role of another app answers `400 validation_error`.",
1619
+ "type": "string",
1620
+ "format": "uuid",
1621
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
1622
+ }
1623
+ }
1624
+ ],
1625
+ "responses": {
1626
+ "204": {
1627
+ "description": "Success."
1628
+ },
1629
+ "default": {
1630
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `validation_error`, `role_in_use`, `last_role`.",
1631
+ "content": {
1632
+ "application/json": {
1633
+ "schema": {
1634
+ "$ref": "#/components/schemas/api-error"
1635
+ }
1636
+ }
1637
+ }
1638
+ }
1639
+ },
1640
+ "description": "Without `move_to`, a role that app users or pending invitations hold, or that is the app's default, answers `409 role_in_use` with `{ users, invitations, is_default }`. With `move_to` — another role of the same app, else `400 validation_error` — one transaction moves `app_users.role_id`, pending invitations and `default_role_id`, then deletes the role and its permissions. The app's only role answers `409 last_role`. Built-in roles can be deleted like any other."
1641
+ }
1642
+ },
1495
1643
  "/api/apps/{id}/server-keys": {
1496
1644
  "post": {
1497
1645
  "operationId": "post_api_apps_id_server_keys",
@@ -2011,7 +2159,7 @@
2011
2159
  }
2012
2160
  },
2013
2161
  "default": {
2014
- "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `target_state_conflict`.",
2162
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `target_state_conflict`, `method_not_allowed`.",
2015
2163
  "content": {
2016
2164
  "application/json": {
2017
2165
  "schema": {
@@ -2021,7 +2169,7 @@
2021
2169
  }
2022
2170
  }
2023
2171
  },
2024
- "description": "The support door beside `POST /api/client/password/reset`: the same one-hour token and the same link, triggered by a developer for a user who asked them rather than the form. **No enumeration discipline applies** — the caller is authenticated into the app and can read the user list — so this one answers what actually happened: `{ \"mail\": mailStatus }`, where `not_configured` is a deployment without a mailer and `failed` is the state worth somebody's attention. The link points at the app's `reset_url`, or at the hosted reset page when the app has configured none. `409 target_state_conflict` names `password` with rule `not_set` for an account that has none — an OIDC-only app user, whom a reset link would hand a second, quieter door — and `status` with rule `blocked` for a blocked one, since `POST /api/client/password/reset` mails a blocked account nothing and the two doors may not disagree. Setting the password directly is deliberately not offered; a developer who could would hold their customers' credentials."
2172
+ "description": "The support door beside `POST /api/client/password/reset`, refused like it with `403 method_not_allowed` while the app has the password method off: the same one-hour token and the same link, triggered by a developer for a user who asked them rather than the form. **No enumeration discipline applies** — the caller is authenticated into the app and can read the user list — so this one answers what actually happened: `{ \"mail\": mailStatus }`, where `not_configured` is a deployment without a mailer and `failed` is the state worth somebody's attention. The link points at the app's `reset_url`, or at the hosted reset page when the app has configured none. `409 target_state_conflict` names `password` with rule `not_set` for an account that has none — an OIDC-only app user, whom a reset link would hand a second, quieter door — and `status` with rule `blocked` for a blocked one, since `POST /api/client/password/reset` mails a blocked account nothing and the two doors may not disagree. Setting the password directly is deliberately not offered; a developer who could would hold their customers' credentials."
2025
2173
  }
2026
2174
  },
2027
2175
  "/api/apps/{id}/users/{userId}/two-factor": {
@@ -3181,7 +3329,7 @@
3181
3329
  "name": "kind",
3182
3330
  "in": "path",
3183
3331
  "required": true,
3184
- "description": "Which of the three mails this template replaces — a `mailTemplateKind`: `invite`, `verify` or `reset`.",
3332
+ "description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
3185
3333
  "schema": {
3186
3334
  "type": "string"
3187
3335
  }
@@ -3236,7 +3384,7 @@
3236
3384
  "name": "kind",
3237
3385
  "in": "path",
3238
3386
  "required": true,
3239
- "description": "Which of the three mails this template replaces — a `mailTemplateKind`: `invite`, `verify` or `reset`.",
3387
+ "description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
3240
3388
  "schema": {
3241
3389
  "type": "string"
3242
3390
  }
@@ -3301,7 +3449,7 @@
3301
3449
  "name": "kind",
3302
3450
  "in": "path",
3303
3451
  "required": true,
3304
- "description": "Which of the three mails this template replaces — a `mailTemplateKind`: `invite`, `verify` or `reset`.",
3452
+ "description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
3305
3453
  "schema": {
3306
3454
  "type": "string"
3307
3455
  }
@@ -3351,7 +3499,7 @@
3351
3499
  "name": "kind",
3352
3500
  "in": "path",
3353
3501
  "required": true,
3354
- "description": "Which of the three mails this template replaces — a `mailTemplateKind`: `invite`, `verify` or `reset`.",
3502
+ "description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
3355
3503
  "schema": {
3356
3504
  "type": "string"
3357
3505
  }
@@ -3418,7 +3566,7 @@
3418
3566
  "name": "kind",
3419
3567
  "in": "path",
3420
3568
  "required": true,
3421
- "description": "Which of the three mails this template replaces — a `mailTemplateKind`: `invite`, `verify` or `reset`.",
3569
+ "description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
3422
3570
  "schema": {
3423
3571
  "type": "string"
3424
3572
  }
@@ -4767,7 +4915,7 @@
4767
4915
  }
4768
4916
  }
4769
4917
  },
4770
- "description": "**`202` and an empty body for every request the policy allows**, in status, body and timing, whether or not the address names an active account of this app — a decoy like `POST /api/client/resend-verification`, so this is no enumeration oracle. A mail goes out only for an account that may sign in. The code is six digits, valid ten minutes, takes five wrong attempts, and a new request expires the previous one for the same address; a request within sixty seconds of the last sends no second mail. The address is trimmed and compared case-insensitively. `404 not_found` is the **app identifier**, never the address; `403 method_not_allowed` when the app has the email-code method off. Limited per app, address and IP, so it cannot be used to mail somebody repeatedly.",
4918
+ "description": "**`202` and an empty body for every request the policy allows**, in status, body and timing, whether or not the address names an active account of this app — a decoy like `POST /api/client/resend-verification`, so this is no enumeration oracle. A mail goes out for an `active` account and for one still `pending_verification` — spending the code proves the address, as the verification link would — and never for a `blocked` one or an unknown address. The code is six digits, valid ten minutes, takes five wrong attempts, and a new request expires the previous one for the same address; a request within sixty seconds of the last sends no second mail. The address is trimmed and compared case-insensitively. `404 not_found` is the **app identifier**, never the address; `403 method_not_allowed` when the app has the email-code method off. Limited per app, address and IP, so it cannot be used to mail somebody repeatedly.",
4771
4919
  "requestBody": {
4772
4920
  "required": true,
4773
4921
  "content": {
@@ -4956,7 +5104,7 @@
4956
5104
  "description": "Success."
4957
5105
  },
4958
5106
  "default": {
4959
- "description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `not_found`.",
5107
+ "description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `not_found`, `method_not_allowed`.",
4960
5108
  "content": {
4961
5109
  "application/json": {
4962
5110
  "schema": {
@@ -4966,7 +5114,7 @@
4966
5114
  }
4967
5115
  }
4968
5116
  },
4969
- "description": "The pair of app identifier and address is the identifier: an app user's address is unique only within their app. Status, body and timing are identical for a known and an unknown address. An account with no password — one created through an identity provider — is mailed nothing and still answers `202`. `404 not_found` is the **app identifier**, never the address. The link points at the app's `reset_url`, or at the hosted reset page when the app has configured none.",
5117
+ "description": "The pair of app identifier and address is the identifier: an app user's address is unique only within their app. `403 method_not_allowed` when the app has the password method off — a reset link whose confirmation would be refused is not mailed; the code names the app's policy, not a person. Status, body and timing are identical for a known and an unknown address. An account with no password — one created through an identity provider — is mailed nothing and still answers `202`. `404 not_found` is the **app identifier**, never the address. The link points at the app's `reset_url`, or at the hosted reset page when the app has configured none.",
4970
5118
  "requestBody": {
4971
5119
  "required": true,
4972
5120
  "content": {
@@ -5179,7 +5327,7 @@
5179
5327
  }
5180
5328
  },
5181
5329
  "default": {
5182
- "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`, `validation_error`, `invalid_credentials`, `target_state_conflict`.",
5330
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`, `validation_error`, `invalid_credentials`, `target_state_conflict`, `method_not_allowed`.",
5183
5331
  "content": {
5184
5332
  "application/json": {
5185
5333
  "schema": {
@@ -5189,7 +5337,7 @@
5189
5337
  }
5190
5338
  }
5191
5339
  },
5192
- "description": "The guard admits all three caller kinds, but a password belongs to an app user specifically — a developer bearer or a server key reaching this is `401 unauthorized`. Every other session of the account ends; the answer is the replacement pair, so the tab that made the change stays signed in. An app user belongs to one app, so \"every session\" is this app's. An account that has **no password** — an OIDC-only app user, which the schema admits — answers `409 target_state_conflict` naming the `password` field with rule `not_set`, not `401`: the session is live and the token is fine, it is the account that has nothing to change, and telling such a caller to sign in again sends them round a loop that ends here.",
5340
+ "description": "`403 method_not_allowed` when the app has the password method off: a stored password stays stored but is not in use, so it is not changed either. The guard admits all three caller kinds, but a password belongs to an app user specifically — a developer bearer or a server key reaching this is `401 unauthorized`. Every other session of the account ends; the answer is the replacement pair, so the tab that made the change stays signed in. An app user belongs to one app, so \"every session\" is this app's. An account that has **no password** — an OIDC-only app user, which the schema admits — answers `409 target_state_conflict` naming the `password` field with rule `not_set`, not `401`: the session is live and the token is fine, it is the account that has nothing to change, and telling such a caller to sign in again sends them round a loop that ends here.",
5193
5341
  "requestBody": {
5194
5342
  "required": true,
5195
5343
  "content": {
@@ -5326,9 +5474,9 @@
5326
5474
  }
5327
5475
  }
5328
5476
  },
5329
- "description": "**Two ways in, decided in the handler.** During sign-in the body carries the `two_factor_setup_required` challenge, and that is the credential; from the app's own account settings the app user's bearer is, with no challenge. Neither is `401 unauthorized`, and a dead challenge is `410 token_spent`. `409 target_state_conflict` names `two_factor` with rule `off` when the app's policy is `off`. The secret is not in use until `POST /api/client/two-factor/setup/confirm` accepts a code from it; a second call replaces a pending secret, and an account that already has an authenticator keeps it until the new one is confirmed.",
5477
+ "description": "**Two ways in, decided in the handler.** During sign-in the body carries the `two_factor_setup_required` challenge, and that is the credential; from the app's own account settings the app user's bearer is, with no challenge and an empty or missing body. Neither is `401 unauthorized`, and a dead challenge is `410 token_spent`. `409 target_state_conflict` names `two_factor` with rule `off` when the app's policy is `off`. The secret is not in use until `POST /api/client/two-factor/setup/confirm` accepts a code from it; a second call replaces a pending secret, and an account that already has an authenticator keeps it until the new one is confirmed.",
5330
5478
  "requestBody": {
5331
- "required": true,
5479
+ "required": false,
5332
5480
  "content": {
5333
5481
  "application/json": {
5334
5482
  "schema": {
@@ -9254,6 +9402,54 @@
9254
9402
  },
9255
9403
  "description": "A window longer than `USAGE_WINDOW_MAX_DAYS` is refused naming the field, not silently capped: a caller who asked for more than the platform will answer is owed a refusal, not a shorter answer they will mistake for the whole picture. `from_day <= to_day` is a cross-field rule no JSON Schema can express and is enforced here. The window is echoed back."
9256
9404
  }
9405
+ },
9406
+ "/api/feedback": {
9407
+ "post": {
9408
+ "operationId": "post_api_feedback",
9409
+ "summary": "Sends a message from a developer to the people who build Fleetless.",
9410
+ "tags": [
9411
+ "org"
9412
+ ],
9413
+ "security": [
9414
+ {
9415
+ "developerSession": []
9416
+ }
9417
+ ],
9418
+ "parameters": [],
9419
+ "responses": {
9420
+ "202": {
9421
+ "description": "Success.",
9422
+ "content": {
9423
+ "application/json": {
9424
+ "schema": {
9425
+ "$ref": "#/components/schemas/feedback-response"
9426
+ }
9427
+ }
9428
+ }
9429
+ },
9430
+ "default": {
9431
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `validation_error`, `rate_limited`.",
9432
+ "content": {
9433
+ "application/json": {
9434
+ "schema": {
9435
+ "$ref": "#/components/schemas/api-error"
9436
+ }
9437
+ }
9438
+ }
9439
+ }
9440
+ },
9441
+ "description": "The message is stored before any mail is tried, so `202` means it is kept whatever `mail` says: `sent`, `failed`, or `not_configured` when this cloud has no feedback address. At most 10 messages per developer per hour; the 11th answers `429 rate_limited` with `retry_after_ms`. Replies come by mail, to the sender's address.",
9442
+ "requestBody": {
9443
+ "required": true,
9444
+ "content": {
9445
+ "application/json": {
9446
+ "schema": {
9447
+ "$ref": "#/components/schemas/feedback-request"
9448
+ }
9449
+ }
9450
+ }
9451
+ }
9452
+ }
9257
9453
  }
9258
9454
  },
9259
9455
  "components": {
@@ -9853,17 +10049,10 @@
9853
10049
  "description": "When the token stops working. Seven days from issue; an expired token answers exactly as an unknown one does."
9854
10050
  },
9855
10051
  "accept_url": {
9856
- "anyOf": [
9857
- {
9858
- "type": "string",
9859
- "maxLength": 500,
9860
- "format": "uri"
9861
- },
9862
- {
9863
- "type": "null"
9864
- }
9865
- ],
9866
- "description": "The link to give the invitee: the app's `invite_url` with the token substituted for `{token}`, or the Fleetless-hosted invitation page when the app has configured none. Bounded like every other URL that gets mailed, logged and rendered. Nullable for readers of the earlier shape; the cloud always fills it."
10052
+ "type": "string",
10053
+ "maxLength": 500,
10054
+ "format": "uri",
10055
+ "description": "The link to give the invitee: the app's `invite_url` with the token substituted for `{token}`, or the Fleetless-hosted invitation page when the app has configured none. Bounded like every other URL that gets mailed, logged and rendered."
9867
10056
  },
9868
10057
  "mail": {
9869
10058
  "type": "string",
@@ -15511,6 +15700,65 @@
15511
15700
  ],
15512
15701
  "additionalProperties": false
15513
15702
  },
15703
+ "feedback-request": {
15704
+ "type": "object",
15705
+ "properties": {
15706
+ "kind": {
15707
+ "type": "string",
15708
+ "enum": [
15709
+ "idea",
15710
+ "problem",
15711
+ "question",
15712
+ "other"
15713
+ ],
15714
+ "description": "What the message is: an `idea`, a `problem`, a `question` or `other`. It only sorts the inbox; it changes nothing about how the message is handled."
15715
+ },
15716
+ "message": {
15717
+ "type": "string",
15718
+ "minLength": 1,
15719
+ "maxLength": 5000,
15720
+ "description": "What the developer wrote, trimmed. At most 5000 characters; a message that is only whitespace is refused."
15721
+ },
15722
+ "page": {
15723
+ "type": "string",
15724
+ "maxLength": 512,
15725
+ "pattern": "^\\/.*",
15726
+ "description": "The console path the message was sent from, e.g. `/robots/:id/jobs` with its real id. A path, never a full URL, so no host and no query string reach the inbox by accident."
15727
+ }
15728
+ },
15729
+ "required": [
15730
+ "kind",
15731
+ "message",
15732
+ "page"
15733
+ ],
15734
+ "additionalProperties": false
15735
+ },
15736
+ "feedback-response": {
15737
+ "type": "object",
15738
+ "properties": {
15739
+ "id": {
15740
+ "type": "string",
15741
+ "format": "uuid",
15742
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
15743
+ "description": "The stored message. It exists whatever `mail` says."
15744
+ },
15745
+ "mail": {
15746
+ "type": "string",
15747
+ "enum": [
15748
+ "sent",
15749
+ "not_requested",
15750
+ "not_configured",
15751
+ "failed"
15752
+ ],
15753
+ "description": "What happened to the notification mail: `sent`, `failed`, or `not_configured` when this cloud has no feedback address. The message is stored in every case, so a client shows success for all three."
15754
+ }
15755
+ },
15756
+ "required": [
15757
+ "id",
15758
+ "mail"
15759
+ ],
15760
+ "additionalProperties": false
15761
+ },
15514
15762
  "fetch-types-request": {
15515
15763
  "type": "object",
15516
15764
  "properties": {
@@ -16577,12 +16825,26 @@
16577
16825
  "minLength": 1,
16578
16826
  "maxLength": 200,
16579
16827
  "description": "A display name taken at invoke time — the email for a Fleetless user or an app user, the key's own name for a server key. Storing it rather than joining is the point: renaming a key afterwards does not rewrite history."
16828
+ },
16829
+ "name": {
16830
+ "anyOf": [
16831
+ {
16832
+ "type": "string",
16833
+ "minLength": 1,
16834
+ "maxLength": 200
16835
+ },
16836
+ {
16837
+ "type": "null"
16838
+ }
16839
+ ],
16840
+ "description": "The person's display name when the job started: the Fleetless user's `display_name` for a developer, the app user's `display_name` for an app user. `null` for a server key, when the person had no name set, and for runs recorded before contracts 5.3.0. Show `label` when it is null."
16580
16841
  }
16581
16842
  },
16582
16843
  "required": [
16583
16844
  "kind",
16584
16845
  "id",
16585
- "label"
16846
+ "label",
16847
+ "name"
16586
16848
  ],
16587
16849
  "additionalProperties": false,
16588
16850
  "description": "Who invoked the run, and what they were acting as at the time."
@@ -19271,7 +19533,7 @@
19271
19533
  },
19272
19534
  "builtin": {
19273
19535
  "type": "boolean",
19274
- "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."
19536
+ "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. Built-in roles can be renamed and deleted like any other; the flag only records that the cloud seeded them."
19275
19537
  }
19276
19538
  },
19277
19539
  "required": [
@@ -19310,7 +19572,7 @@
19310
19572
  },
19311
19573
  "builtin": {
19312
19574
  "type": "boolean",
19313
- "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."
19575
+ "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. Built-in roles can be renamed and deleted like any other; the flag only records that the cloud seeded them."
19314
19576
  }
19315
19577
  },
19316
19578
  "required": [
@@ -19389,6 +19651,21 @@
19389
19651
  "capabilities"
19390
19652
  ]
19391
19653
  },
19654
+ "role-rename-request": {
19655
+ "type": "object",
19656
+ "properties": {
19657
+ "name": {
19658
+ "type": "string",
19659
+ "minLength": 1,
19660
+ "maxLength": 60,
19661
+ "description": "The new name, trimmed, 1 to 60 characters. Unique per app: another role of this app with the same name answers `409 role_name_taken`. The role's users keep it under its new name."
19662
+ }
19663
+ },
19664
+ "required": [
19665
+ "name"
19666
+ ],
19667
+ "additionalProperties": false
19668
+ },
19392
19669
  "server-key-list-response": {
19393
19670
  "type": "object",
19394
19671
  "properties": {