@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 +53 -3
- package/artifacts/openapi.json +306 -29
- package/artifacts/routes.json +123 -22
- package/artifacts/schema/app-invitation.schema.json +4 -11
- package/artifacts/schema/audit-query.schema.json +6 -0
- package/artifacts/schema/feedback-request.schema.json +34 -0
- package/artifacts/schema/feedback-response.schema.json +27 -0
- package/artifacts/schema/job-actor.schema.json +15 -1
- package/artifacts/schema/job-run-list-response.schema.json +15 -1
- package/artifacts/schema/job-run.schema.json +15 -1
- package/artifacts/schema/role-delete-query.schema.json +13 -0
- package/artifacts/schema/role-in-use-details.schema.json +28 -0
- package/artifacts/schema/role-list-response.schema.json +1 -1
- package/artifacts/schema/role-rename-request.schema.json +16 -0
- package/artifacts/schema/role.schema.json +1 -1
- package/dist/app-users.d.ts +3 -4
- package/dist/app-users.js +4 -5
- package/dist/apps.d.ts +38 -2
- package/dist/apps.js +46 -3
- package/dist/audit.d.ts +1 -0
- package/dist/audit.js +13 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +36 -8
- package/dist/feedback.d.ts +42 -0
- package/dist/feedback.js +35 -0
- package/dist/identity.d.ts +3 -5
- package/dist/identity.js +3 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/jobs.d.ts +3 -0
- package/dist/jobs.js +16 -0
- package/dist/routes.d.ts +10 -3
- package/dist/routes.js +79 -23
- package/package.json +1 -1
package/artifacts/routes.json
CHANGED
|
@@ -664,7 +664,7 @@
|
|
|
664
664
|
"validation_error"
|
|
665
665
|
],
|
|
666
666
|
"transport": "http",
|
|
667
|
-
"notes": "The body is `{ \"name\": string }` — non-empty, trimmed, at most
|
|
667
|
+
"notes": "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."
|
|
668
668
|
},
|
|
669
669
|
{
|
|
670
670
|
"method": "GET",
|
|
@@ -794,6 +794,77 @@
|
|
|
794
794
|
"transport": "http",
|
|
795
795
|
"notes": "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."
|
|
796
796
|
},
|
|
797
|
+
{
|
|
798
|
+
"method": "PATCH",
|
|
799
|
+
"path": "/api/apps/:id/roles/:roleId",
|
|
800
|
+
"section": "apps",
|
|
801
|
+
"summary": "Renames a role; its users keep it.",
|
|
802
|
+
"audience": "developer",
|
|
803
|
+
"auth": "developer",
|
|
804
|
+
"rateLimited": false,
|
|
805
|
+
"ownerTier": false,
|
|
806
|
+
"status": 200,
|
|
807
|
+
"params": [
|
|
808
|
+
{
|
|
809
|
+
"name": "id",
|
|
810
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`."
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
"name": "roleId",
|
|
814
|
+
"description": "The role's uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`."
|
|
815
|
+
}
|
|
816
|
+
],
|
|
817
|
+
"query": null,
|
|
818
|
+
"request": "role-rename-request",
|
|
819
|
+
"response": "role",
|
|
820
|
+
"errors": [
|
|
821
|
+
"unauthorized",
|
|
822
|
+
"token_expired",
|
|
823
|
+
"token_revoked",
|
|
824
|
+
"invalid_uuid",
|
|
825
|
+
"not_found",
|
|
826
|
+
"validation_error",
|
|
827
|
+
"role_name_taken"
|
|
828
|
+
],
|
|
829
|
+
"transport": "http",
|
|
830
|
+
"notes": "Names are unique per app, compared exactly as stored after trimming. Built-in roles can be renamed."
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
"method": "DELETE",
|
|
834
|
+
"path": "/api/apps/:id/roles/:roleId",
|
|
835
|
+
"section": "apps",
|
|
836
|
+
"summary": "Deletes a role, moving its users, pending invitations and default-role status to another role.",
|
|
837
|
+
"audience": "developer",
|
|
838
|
+
"auth": "developer",
|
|
839
|
+
"rateLimited": false,
|
|
840
|
+
"ownerTier": false,
|
|
841
|
+
"status": 204,
|
|
842
|
+
"params": [
|
|
843
|
+
{
|
|
844
|
+
"name": "id",
|
|
845
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`."
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
"name": "roleId",
|
|
849
|
+
"description": "The role's uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`."
|
|
850
|
+
}
|
|
851
|
+
],
|
|
852
|
+
"query": "role-delete-query",
|
|
853
|
+
"request": null,
|
|
854
|
+
"response": null,
|
|
855
|
+
"errors": [
|
|
856
|
+
"unauthorized",
|
|
857
|
+
"token_expired",
|
|
858
|
+
"token_revoked",
|
|
859
|
+
"invalid_uuid",
|
|
860
|
+
"not_found",
|
|
861
|
+
"validation_error",
|
|
862
|
+
"role_in_use",
|
|
863
|
+
"last_role"
|
|
864
|
+
],
|
|
865
|
+
"transport": "http",
|
|
866
|
+
"notes": "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."
|
|
867
|
+
},
|
|
797
868
|
{
|
|
798
869
|
"method": "POST",
|
|
799
870
|
"path": "/api/apps/:id/server-keys",
|
|
@@ -1114,10 +1185,11 @@
|
|
|
1114
1185
|
"token_revoked",
|
|
1115
1186
|
"invalid_uuid",
|
|
1116
1187
|
"not_found",
|
|
1117
|
-
"target_state_conflict"
|
|
1188
|
+
"target_state_conflict",
|
|
1189
|
+
"method_not_allowed"
|
|
1118
1190
|
],
|
|
1119
1191
|
"transport": "http",
|
|
1120
|
-
"notes": "The support door beside `POST /api/client/password/reset
|
|
1192
|
+
"notes": "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."
|
|
1121
1193
|
},
|
|
1122
1194
|
{
|
|
1123
1195
|
"method": "DELETE",
|
|
@@ -1800,7 +1872,7 @@
|
|
|
1800
1872
|
},
|
|
1801
1873
|
{
|
|
1802
1874
|
"name": "kind",
|
|
1803
|
-
"description": "Which of the
|
|
1875
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`."
|
|
1804
1876
|
}
|
|
1805
1877
|
],
|
|
1806
1878
|
"query": null,
|
|
@@ -1834,7 +1906,7 @@
|
|
|
1834
1906
|
},
|
|
1835
1907
|
{
|
|
1836
1908
|
"name": "kind",
|
|
1837
|
-
"description": "Which of the
|
|
1909
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`."
|
|
1838
1910
|
}
|
|
1839
1911
|
],
|
|
1840
1912
|
"query": null,
|
|
@@ -1870,7 +1942,7 @@
|
|
|
1870
1942
|
},
|
|
1871
1943
|
{
|
|
1872
1944
|
"name": "kind",
|
|
1873
|
-
"description": "Which of the
|
|
1945
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`."
|
|
1874
1946
|
}
|
|
1875
1947
|
],
|
|
1876
1948
|
"query": null,
|
|
@@ -1904,7 +1976,7 @@
|
|
|
1904
1976
|
},
|
|
1905
1977
|
{
|
|
1906
1978
|
"name": "kind",
|
|
1907
|
-
"description": "Which of the
|
|
1979
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`."
|
|
1908
1980
|
}
|
|
1909
1981
|
],
|
|
1910
1982
|
"query": null,
|
|
@@ -1940,7 +2012,7 @@
|
|
|
1940
2012
|
},
|
|
1941
2013
|
{
|
|
1942
2014
|
"name": "kind",
|
|
1943
|
-
"description": "Which of the
|
|
2015
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`."
|
|
1944
2016
|
}
|
|
1945
2017
|
],
|
|
1946
2018
|
"query": null,
|
|
@@ -2406,7 +2478,7 @@
|
|
|
2406
2478
|
"response": null,
|
|
2407
2479
|
"errors": [],
|
|
2408
2480
|
"transport": "http",
|
|
2409
|
-
"notes": "HTML, and a GET rather than the body of the authorize response — so it is reloadable, bookmarkable and survives a back button, which the inline page it replaced was not. An expired, consumed, unknown or hand-edited interaction renders one page at `410`, and so does a client whose dynamic registration lapsed in between."
|
|
2481
|
+
"notes": "HTML, and a GET rather than the body of the authorize response — so it is reloadable, bookmarkable and survives a back button, which the inline page it replaced was not. An expired, consumed, unknown or hand-edited interaction renders one page at `410`, and so does a client whose dynamic registration lapsed in between. It offers the email field and `Sign in with a passkey`, and opening it binds the interaction to this browser with the browser-proof cookie, which every sign-in step after it checks."
|
|
2410
2482
|
},
|
|
2411
2483
|
{
|
|
2412
2484
|
"method": "POST",
|
|
@@ -2425,10 +2497,11 @@
|
|
|
2425
2497
|
"errors": [
|
|
2426
2498
|
"rate_limited",
|
|
2427
2499
|
"validation_error",
|
|
2428
|
-
"token_spent"
|
|
2500
|
+
"token_spent",
|
|
2501
|
+
"wrong_browser"
|
|
2429
2502
|
],
|
|
2430
2503
|
"transport": "http",
|
|
2431
|
-
"notes": "The page answers `Check your email` **for every address**: a known one gets `Your Fleetless sign-in code`, six digits valid ten minutes; an unknown one gets a mail saying no Fleetless account uses it, with a link to sign up (or the waiting list while sign-up is closed). So the page never reveals who has an account, and the address is trimmed and compared case-insensitively. A request within sixty seconds of the last one for the same address renders the same page without a second mail. The browser-proof cookie is set here. A browser form post gets the code card; a JSON caller gets `{ \"next\": \"/mcp/oauth/code\" }`, which has no schema. A dead interaction is `410 token_spent`."
|
|
2504
|
+
"notes": "The page answers `Check your email` **for every address**: a known one gets `Your Fleetless sign-in code`, six digits valid ten minutes; an unknown one gets a mail saying no Fleetless account uses it, with a link to sign up (or the waiting list while sign-up is closed). So the page never reveals who has an account, and the address is trimmed and compared case-insensitively. A request within sixty seconds of the last one for the same address renders the same page without a second mail. The browser-proof cookie is set here when the interaction has none yet, and checked when it has. A browser form post gets the code card; a JSON caller gets `{ \"next\": \"/mcp/oauth/code\" }`, which has no schema. A dead interaction is `410 token_spent`."
|
|
2432
2505
|
},
|
|
2433
2506
|
{
|
|
2434
2507
|
"method": "POST",
|
|
@@ -2544,7 +2617,7 @@
|
|
|
2544
2617
|
"wrong_browser"
|
|
2545
2618
|
],
|
|
2546
2619
|
"transport": "http",
|
|
2547
|
-
"notes": "Before an address is known the options name no credential, so the browser offers every discoverable passkey for `fleetless.dev` (`Sign in with a passkey`); after the code step they name the account's own passkeys. User verification is required. The challenge is bound to the interaction and single-use."
|
|
2620
|
+
"notes": "Before an address is known the options name no credential, so the browser offers every discoverable passkey for `fleetless.dev` (`Sign in with a passkey`); after the code step they name the account's own passkeys. User verification is required. The challenge is bound to the interaction and single-use. **This is the first step of a passkey sign-in**, which has no email step: the browser-proof cookie is set here when the interaction has none yet, and checked when it has — so `POST /mcp/oauth/passkey` can require it."
|
|
2548
2621
|
},
|
|
2549
2622
|
{
|
|
2550
2623
|
"method": "POST",
|
|
@@ -2808,7 +2881,7 @@
|
|
|
2808
2881
|
"response": null,
|
|
2809
2882
|
"errors": [],
|
|
2810
2883
|
"transport": "http",
|
|
2811
|
-
"notes": "HTML: the email field, `Email me a code`, and `Sign in with a passkey`. An expired, consumed, unknown or hand-edited interaction renders one page at `410`: which of the four it was is not a fact a stranger may learn, and to the person it is one fact anyway. The page resolves nothing about the address typed into it, so there is no enumeration oracle here at all."
|
|
2884
|
+
"notes": "HTML: the email field, `Email me a code`, and `Sign in with a passkey`. Opening it binds the interaction to this browser with the browser-proof cookie, which every sign-in step after it checks. An expired, consumed, unknown or hand-edited interaction renders one page at `410`: which of the four it was is not a fact a stranger may learn, and to the person it is one fact anyway. The page resolves nothing about the address typed into it, so there is no enumeration oracle here at all."
|
|
2812
2885
|
},
|
|
2813
2886
|
{
|
|
2814
2887
|
"method": "POST",
|
|
@@ -2827,10 +2900,11 @@
|
|
|
2827
2900
|
"errors": [
|
|
2828
2901
|
"rate_limited",
|
|
2829
2902
|
"validation_error",
|
|
2830
|
-
"token_spent"
|
|
2903
|
+
"token_spent",
|
|
2904
|
+
"wrong_browser"
|
|
2831
2905
|
],
|
|
2832
2906
|
"transport": "http",
|
|
2833
|
-
"notes": "The page answers `Check your email` **for every address**: a known one gets `Your Fleetless sign-in code`, six digits valid ten minutes; an unknown one gets a mail saying no Fleetless account uses it, with a link to sign up (or the waiting list while sign-up is closed). So the page never reveals who has an account, and the address is trimmed and compared case-insensitively. A request within sixty seconds of the last one for the same address renders the same page without a second mail. The browser-proof cookie is set here. A browser form post gets the code card; a JSON caller gets `{ \"next\": \"/console/oauth/code\" }`, which has no schema. A dead interaction is `410 token_spent`."
|
|
2907
|
+
"notes": "The page answers `Check your email` **for every address**: a known one gets `Your Fleetless sign-in code`, six digits valid ten minutes; an unknown one gets a mail saying no Fleetless account uses it, with a link to sign up (or the waiting list while sign-up is closed). So the page never reveals who has an account, and the address is trimmed and compared case-insensitively. A request within sixty seconds of the last one for the same address renders the same page without a second mail. The browser-proof cookie is set here when the interaction has none yet, and checked when it has. A browser form post gets the code card; a JSON caller gets `{ \"next\": \"/console/oauth/code\" }`, which has no schema. A dead interaction is `410 token_spent`."
|
|
2834
2908
|
},
|
|
2835
2909
|
{
|
|
2836
2910
|
"method": "POST",
|
|
@@ -2946,7 +3020,7 @@
|
|
|
2946
3020
|
"wrong_browser"
|
|
2947
3021
|
],
|
|
2948
3022
|
"transport": "http",
|
|
2949
|
-
"notes": "Before an address is known the options name no credential, so the browser offers every discoverable passkey for `fleetless.dev` (`Sign in with a passkey`); after the code step they name the account's own passkeys. User verification is required. The challenge is bound to the interaction and single-use."
|
|
3023
|
+
"notes": "Before an address is known the options name no credential, so the browser offers every discoverable passkey for `fleetless.dev` (`Sign in with a passkey`); after the code step they name the account's own passkeys. User verification is required. The challenge is bound to the interaction and single-use. **This is the first step of a passkey sign-in**, which has no email step: the browser-proof cookie is set here when the interaction has none yet, and checked when it has — so `POST /console/oauth/passkey` can require it."
|
|
2950
3024
|
},
|
|
2951
3025
|
{
|
|
2952
3026
|
"method": "POST",
|
|
@@ -4004,7 +4078,7 @@
|
|
|
4004
4078
|
"method_not_allowed"
|
|
4005
4079
|
],
|
|
4006
4080
|
"transport": "http",
|
|
4007
|
-
"notes": "**`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
|
|
4081
|
+
"notes": "**`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."
|
|
4008
4082
|
},
|
|
4009
4083
|
{
|
|
4010
4084
|
"method": "POST",
|
|
@@ -4117,10 +4191,11 @@
|
|
|
4117
4191
|
"errors": [
|
|
4118
4192
|
"rate_limited",
|
|
4119
4193
|
"validation_error",
|
|
4120
|
-
"not_found"
|
|
4194
|
+
"not_found",
|
|
4195
|
+
"method_not_allowed"
|
|
4121
4196
|
],
|
|
4122
4197
|
"transport": "http",
|
|
4123
|
-
"notes": "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."
|
|
4198
|
+
"notes": "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."
|
|
4124
4199
|
},
|
|
4125
4200
|
{
|
|
4126
4201
|
"method": "POST",
|
|
@@ -4235,10 +4310,11 @@
|
|
|
4235
4310
|
"forbidden",
|
|
4236
4311
|
"validation_error",
|
|
4237
4312
|
"invalid_credentials",
|
|
4238
|
-
"target_state_conflict"
|
|
4313
|
+
"target_state_conflict",
|
|
4314
|
+
"method_not_allowed"
|
|
4239
4315
|
],
|
|
4240
4316
|
"transport": "http",
|
|
4241
|
-
"notes": "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."
|
|
4317
|
+
"notes": "`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."
|
|
4242
4318
|
},
|
|
4243
4319
|
{
|
|
4244
4320
|
"method": "GET",
|
|
@@ -4299,6 +4375,7 @@
|
|
|
4299
4375
|
"params": [],
|
|
4300
4376
|
"query": null,
|
|
4301
4377
|
"request": "client-two-factor-setup-request",
|
|
4378
|
+
"requestOptional": true,
|
|
4302
4379
|
"response": "two-factor-setup-response",
|
|
4303
4380
|
"errors": [
|
|
4304
4381
|
"rate_limited",
|
|
@@ -4308,7 +4385,7 @@
|
|
|
4308
4385
|
"target_state_conflict"
|
|
4309
4386
|
],
|
|
4310
4387
|
"transport": "http",
|
|
4311
|
-
"notes": "**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."
|
|
4388
|
+
"notes": "**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."
|
|
4312
4389
|
},
|
|
4313
4390
|
{
|
|
4314
4391
|
"method": "POST",
|
|
@@ -6267,6 +6344,30 @@
|
|
|
6267
6344
|
"transport": "http",
|
|
6268
6345
|
"notes": "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."
|
|
6269
6346
|
},
|
|
6347
|
+
{
|
|
6348
|
+
"method": "POST",
|
|
6349
|
+
"path": "/api/feedback",
|
|
6350
|
+
"section": "org",
|
|
6351
|
+
"summary": "Sends a message from a developer to the people who build Fleetless.",
|
|
6352
|
+
"audience": "developer",
|
|
6353
|
+
"auth": "developer",
|
|
6354
|
+
"rateLimited": true,
|
|
6355
|
+
"ownerTier": false,
|
|
6356
|
+
"status": 202,
|
|
6357
|
+
"params": [],
|
|
6358
|
+
"query": null,
|
|
6359
|
+
"request": "feedback-request",
|
|
6360
|
+
"response": "feedback-response",
|
|
6361
|
+
"errors": [
|
|
6362
|
+
"unauthorized",
|
|
6363
|
+
"token_expired",
|
|
6364
|
+
"token_revoked",
|
|
6365
|
+
"validation_error",
|
|
6366
|
+
"rate_limited"
|
|
6367
|
+
],
|
|
6368
|
+
"transport": "http",
|
|
6369
|
+
"notes": "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."
|
|
6370
|
+
},
|
|
6270
6371
|
{
|
|
6271
6372
|
"method": "POST",
|
|
6272
6373
|
"path": "/api/bridge/assets",
|
|
@@ -33,17 +33,10 @@
|
|
|
33
33
|
"description": "When the token stops working. Seven days from issue; an expired token answers exactly as an unknown one does."
|
|
34
34
|
},
|
|
35
35
|
"accept_url": {
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"format": "uri"
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"type": "null"
|
|
44
|
-
}
|
|
45
|
-
],
|
|
46
|
-
"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."
|
|
36
|
+
"type": "string",
|
|
37
|
+
"maxLength": 500,
|
|
38
|
+
"format": "uri",
|
|
39
|
+
"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."
|
|
47
40
|
},
|
|
48
41
|
"mail": {
|
|
49
42
|
"type": "string",
|
|
@@ -48,6 +48,12 @@
|
|
|
48
48
|
"minLength": 1,
|
|
49
49
|
"maxLength": 40
|
|
50
50
|
},
|
|
51
|
+
"target_id": {
|
|
52
|
+
"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.",
|
|
53
|
+
"type": "string",
|
|
54
|
+
"minLength": 1,
|
|
55
|
+
"maxLength": 200
|
|
56
|
+
},
|
|
51
57
|
"from_ms": {
|
|
52
58
|
"anyOf": [
|
|
53
59
|
{
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"kind": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"enum": [
|
|
8
|
+
"idea",
|
|
9
|
+
"problem",
|
|
10
|
+
"question",
|
|
11
|
+
"other"
|
|
12
|
+
],
|
|
13
|
+
"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."
|
|
14
|
+
},
|
|
15
|
+
"message": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"maxLength": 5000,
|
|
19
|
+
"description": "What the developer wrote, trimmed. At most 5000 characters; a message that is only whitespace is refused."
|
|
20
|
+
},
|
|
21
|
+
"page": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"maxLength": 512,
|
|
24
|
+
"pattern": "^\\/.*",
|
|
25
|
+
"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."
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": [
|
|
29
|
+
"kind",
|
|
30
|
+
"message",
|
|
31
|
+
"page"
|
|
32
|
+
],
|
|
33
|
+
"additionalProperties": false
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"id": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"format": "uuid",
|
|
8
|
+
"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)$",
|
|
9
|
+
"description": "The stored message. It exists whatever `mail` says."
|
|
10
|
+
},
|
|
11
|
+
"mail": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": [
|
|
14
|
+
"sent",
|
|
15
|
+
"not_requested",
|
|
16
|
+
"not_configured",
|
|
17
|
+
"failed"
|
|
18
|
+
],
|
|
19
|
+
"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."
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"id",
|
|
24
|
+
"mail"
|
|
25
|
+
],
|
|
26
|
+
"additionalProperties": false
|
|
27
|
+
}
|
|
@@ -23,12 +23,26 @@
|
|
|
23
23
|
"minLength": 1,
|
|
24
24
|
"maxLength": 200,
|
|
25
25
|
"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."
|
|
26
|
+
},
|
|
27
|
+
"name": {
|
|
28
|
+
"anyOf": [
|
|
29
|
+
{
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1,
|
|
32
|
+
"maxLength": 200
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "null"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"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."
|
|
26
39
|
}
|
|
27
40
|
},
|
|
28
41
|
"required": [
|
|
29
42
|
"kind",
|
|
30
43
|
"id",
|
|
31
|
-
"label"
|
|
44
|
+
"label",
|
|
45
|
+
"name"
|
|
32
46
|
],
|
|
33
47
|
"additionalProperties": false
|
|
34
48
|
}
|
|
@@ -142,12 +142,26 @@
|
|
|
142
142
|
"minLength": 1,
|
|
143
143
|
"maxLength": 200,
|
|
144
144
|
"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."
|
|
145
|
+
},
|
|
146
|
+
"name": {
|
|
147
|
+
"anyOf": [
|
|
148
|
+
{
|
|
149
|
+
"type": "string",
|
|
150
|
+
"minLength": 1,
|
|
151
|
+
"maxLength": 200
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"type": "null"
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"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."
|
|
145
158
|
}
|
|
146
159
|
},
|
|
147
160
|
"required": [
|
|
148
161
|
"kind",
|
|
149
162
|
"id",
|
|
150
|
-
"label"
|
|
163
|
+
"label",
|
|
164
|
+
"name"
|
|
151
165
|
],
|
|
152
166
|
"additionalProperties": false,
|
|
153
167
|
"description": "Who invoked the run, and what they were acting as at the time."
|
|
@@ -137,12 +137,26 @@
|
|
|
137
137
|
"minLength": 1,
|
|
138
138
|
"maxLength": 200,
|
|
139
139
|
"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."
|
|
140
|
+
},
|
|
141
|
+
"name": {
|
|
142
|
+
"anyOf": [
|
|
143
|
+
{
|
|
144
|
+
"type": "string",
|
|
145
|
+
"minLength": 1,
|
|
146
|
+
"maxLength": 200
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"type": "null"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"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."
|
|
140
153
|
}
|
|
141
154
|
},
|
|
142
155
|
"required": [
|
|
143
156
|
"kind",
|
|
144
157
|
"id",
|
|
145
|
-
"label"
|
|
158
|
+
"label",
|
|
159
|
+
"name"
|
|
146
160
|
],
|
|
147
161
|
"additionalProperties": false,
|
|
148
162
|
"description": "Who invoked the run, and what they were acting as at the time."
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"move_to": {
|
|
6
|
+
"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`.",
|
|
7
|
+
"type": "string",
|
|
8
|
+
"format": "uuid",
|
|
9
|
+
"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)$"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"additionalProperties": false
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"users": {
|
|
6
|
+
"type": "integer",
|
|
7
|
+
"minimum": 0,
|
|
8
|
+
"maximum": 9007199254740991,
|
|
9
|
+
"description": "App users whose role this is."
|
|
10
|
+
},
|
|
11
|
+
"invitations": {
|
|
12
|
+
"type": "integer",
|
|
13
|
+
"minimum": 0,
|
|
14
|
+
"maximum": 9007199254740991,
|
|
15
|
+
"description": "Pending invitations that would grant this role when accepted."
|
|
16
|
+
},
|
|
17
|
+
"is_default": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"description": "`true` when this is the app's `default_role_id`; the default then moves with the users to `move_to`."
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"users",
|
|
24
|
+
"invitations",
|
|
25
|
+
"is_default"
|
|
26
|
+
],
|
|
27
|
+
"additionalProperties": false
|
|
28
|
+
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"builtin": {
|
|
29
29
|
"type": "boolean",
|
|
30
|
-
"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.
|
|
30
|
+
"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."
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"required": [
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"name": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"minLength": 1,
|
|
8
|
+
"maxLength": 60,
|
|
9
|
+
"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."
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"required": [
|
|
13
|
+
"name"
|
|
14
|
+
],
|
|
15
|
+
"additionalProperties": false
|
|
16
|
+
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"builtin": {
|
|
24
24
|
"type": "boolean",
|
|
25
|
-
"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.
|
|
25
|
+
"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."
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"required": [
|
package/dist/app-users.d.ts
CHANGED
|
@@ -177,9 +177,8 @@ export type CreateAppInvitationRequest = z.infer<typeof createAppInvitationReque
|
|
|
177
177
|
*
|
|
178
178
|
* The link points into the developer's app, at their configured `invite_url`,
|
|
179
179
|
* or at the Fleetless-hosted invitation page (`appAuthConfig.hosted_pages`)
|
|
180
|
-
* when the app has configured none
|
|
181
|
-
*
|
|
182
|
-
* in every case now that a hosted page always exists.
|
|
180
|
+
* when the app has configured none — so there is always a link, and
|
|
181
|
+
* `accept_url` is never `null`.
|
|
183
182
|
*/
|
|
184
183
|
export declare const appInvitation: z.ZodObject<{
|
|
185
184
|
id: z.ZodUUID;
|
|
@@ -187,7 +186,7 @@ export declare const appInvitation: z.ZodObject<{
|
|
|
187
186
|
email: z.ZodEmail;
|
|
188
187
|
role_id: z.ZodUUID;
|
|
189
188
|
expires_at: z.ZodISODateTime;
|
|
190
|
-
accept_url: z.
|
|
189
|
+
accept_url: z.ZodURL;
|
|
191
190
|
mail: z.ZodEnum<{
|
|
192
191
|
sent: "sent";
|
|
193
192
|
not_requested: "not_requested";
|
package/dist/app-users.js
CHANGED
|
@@ -210,9 +210,8 @@ export const createAppInvitationRequest = z
|
|
|
210
210
|
*
|
|
211
211
|
* The link points into the developer's app, at their configured `invite_url`,
|
|
212
212
|
* or at the Fleetless-hosted invitation page (`appAuthConfig.hosted_pages`)
|
|
213
|
-
* when the app has configured none
|
|
214
|
-
*
|
|
215
|
-
* in every case now that a hosted page always exists.
|
|
213
|
+
* when the app has configured none — so there is always a link, and
|
|
214
|
+
* `accept_url` is never `null`.
|
|
216
215
|
*/
|
|
217
216
|
export const appInvitation = z.object({
|
|
218
217
|
id: z.uuid().meta({ description: 'The invitation, as listed and revoked by the developer.' }),
|
|
@@ -220,8 +219,8 @@ export const appInvitation = z.object({
|
|
|
220
219
|
email: z.email().meta({ description: 'The address the invitation was addressed to.' }),
|
|
221
220
|
role_id: z.uuid().meta({ description: 'The role the invitee holds once they accept. Resolved at creation, so a later change to the app\'s default role does not silently re-aim an outstanding invitation.' }),
|
|
222
221
|
expires_at: z.iso.datetime().meta({ description: 'When the token stops working. Seven days from issue; an expired token answers exactly as an unknown one does.' }),
|
|
223
|
-
accept_url: z.url().max(500).
|
|
224
|
-
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.
|
|
222
|
+
accept_url: z.url().max(500).meta({
|
|
223
|
+
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.',
|
|
225
224
|
}),
|
|
226
225
|
mail: mailStatus.meta({
|
|
227
226
|
description: 'What happened to the mail: `sent` means the SMTP server accepted it, not that it was delivered; `not_requested` means none was attempted because the caller asked for none; `not_configured` is an expected state and not a failure; `failed` is the one worth somebody\'s attention.',
|