@fleetless/contracts 2.0.0 → 4.0.0-next.1
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 +36 -1
- package/CONTRIBUTING.md +47 -30
- package/artifacts/constants.json +10 -2
- package/artifacts/openapi.json +301 -30
- package/artifacts/routes.json +124 -5
- package/artifacts/schema/app-deletion-summary.schema.json +51 -0
- package/artifacts/schema/bridge-job-lost.schema.json +17 -0
- package/artifacts/schema/put-app-auth-mcp-request.schema.json +27 -0
- package/artifacts/schema/put-app-auth-registration-request.schema.json +36 -0
- package/artifacts/schema/put-app-auth-urls-request.schema.json +48 -0
- package/artifacts/schema-outgoing/bridge-job-lost.schema.json +18 -0
- package/dist/app-users.d.ts +25 -7
- package/dist/app-users.js +24 -6
- package/dist/apps.d.ts +22 -0
- package/dist/apps.js +33 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +38 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/protocol.d.ts +67 -6
- package/dist/protocol.js +66 -7
- package/dist/realtime.d.ts +2 -2
- package/dist/rest.d.ts +2 -0
- package/dist/rest.js +2 -0
- package/dist/routes.js +72 -13
- package/package.json +1 -1
- package/artifacts/schema/put-app-auth-config-request.schema.json +0 -93
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,42 @@
|
|
|
3
3
|
All notable changes to `@fleetless/contracts` are recorded here. The format
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
|
|
5
5
|
project uses [semantic versioning](https://semver.org/spec/v2.0.0.html) over
|
|
6
|
-
the wire shapes.
|
|
6
|
+
the wire shapes. A pull request that changes what a consumer sees adds its
|
|
7
|
+
entry under `## [Unreleased]`; the release renames that heading to the
|
|
8
|
+
version.
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Protocol 4: a job heartbeat, and a vanished action server ends its job.**
|
|
15
|
+
Protocol bumped: bridges from `4.1.0`, and protocol 3 sunsets
|
|
16
|
+
2026-12-27 (protocol 2 sunset 2026-12-21). The bridge now sends a
|
|
17
|
+
`job_update` heartbeat every `JOB_HEARTBEAT_INTERVAL_MS` for every running
|
|
18
|
+
job; `job_lost` gains an optional `error`, so a bridge that finds its
|
|
19
|
+
action server gone can say `action_server_lost` instead of leaving the
|
|
20
|
+
cloud to guess. `JOB_HEARTBEAT_TIMEOUT_MS` bounds a protocol-4 job's
|
|
21
|
+
silence once it has been heard from at all; `patience_ms` now bounds only
|
|
22
|
+
the acceptance gap on such a job (unchanged for protocol 3, which sends no
|
|
23
|
+
heartbeat). `JOB_OFFLINE_GRACE_MS` (five minutes) replaces the informal
|
|
24
|
+
one-minute disconnect grace a job got before. `errors.ts` documents
|
|
25
|
+
`action_server_lost` and every job error code already in use that had
|
|
26
|
+
never been written down: `action_failed`, `goal_rejected`,
|
|
27
|
+
`goal_send_failed`, `result_failed`, `goal_uncontrollable`,
|
|
28
|
+
`bridge_disconnected`, `config_changed`.
|
|
29
|
+
|
|
30
|
+
## [3.0.0] — 2026-09-22
|
|
31
|
+
|
|
32
|
+
The protocol window carries over unchanged: `LATEST_BRIDGE_VERSION` is still `4.0.0`, and protocol 2 still sunsets 2026-12-21. Everything below is the REST surface.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **Auth-config as three slices, not one document.** `putAppAuthRegistrationRequest` (`self_registration`, `allowed_domains`, `allowed_origins`), `putAppAuthUrlsRequest` (`invite_url`, `verify_url`, `reset_url`) and `putAppAuthMcpRequest` (`mcp_enabled`, `mcp_login_url`) are three `.strict()` replaces behind three new routes — `PUT /api/apps/:id/auth-config/registration`, `/urls` and `/mcp` — each merged server-side against the stored row, so a write to one slice can no longer clear a field it never showed. `GET /api/apps/:id/auth-config` is unchanged and still answers the whole document.
|
|
37
|
+
- **A deletion preview and a delete.** `appDeletionSummary` — six independent counts (`user_count`, `role_count`, `server_key_count`, `invitation_count`, `oidc_provider_count`, `mail_template_count`), deliberately not summed — is what `GET /api/apps/:id/deletion-preview` (`200`) answers and what the `app.deleted` audit event carries, computed by the same function so the confirmation dialog and the eventual receipt cannot quietly disagree. `DELETE /api/apps/:id` (Owner tier, `204`) runs the cascade: an app's users, roles, server keys, invitations, OIDC configuration and mail templates all go; its robots do not, since they belong to the org, not the app. **No `force` parameter** — unlike the robot deletion pair this is modelled on, an app has no open-session state to force past, and inventing one would be a guess wearing a guard's clothes.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
41
|
+
- **`putAppAuthConfigRequest` and `PUT /api/apps/:id/auth-config`.** Replaced by the three slice requests and routes above — `PUT /api/apps/:id/auth-config/registration`, `PUT /api/apps/:id/auth-config/urls` and `PUT /api/apps/:id/auth-config/mcp`. This is the break that makes this release a major: a caller still sending the old whole-document body finds no route left to send it to.
|
|
7
42
|
|
|
8
43
|
## [2.0.0] — 2026-09-22
|
|
9
44
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -73,8 +73,9 @@ an issue first — so we can say what else has to move with it.
|
|
|
73
73
|
|
|
74
74
|
**CI runs on GitHub Actions**, in this repository
|
|
75
75
|
(`.github/workflows/verify.yml`) — the suite, on every push and every pull
|
|
76
|
-
request. `release.yml`
|
|
77
|
-
|
|
76
|
+
request. `release.yml` (the **Release** button) calls that same file on the
|
|
77
|
+
commit it publishes, so a release is never checked by a different pipeline
|
|
78
|
+
than a push.
|
|
78
79
|
|
|
79
80
|
**Your pull request is verified, a fork's included** — the same file, the
|
|
80
81
|
same suite. The first run by a first-time contributor waits for a maintainer
|
|
@@ -82,10 +83,11 @@ to press approve on it; that is a button on your run, not a setting anybody
|
|
|
82
83
|
has to change, so checks sitting idle for a while are the queue and not a
|
|
83
84
|
failure. The run reads code and reaches nothing else: it is granted
|
|
84
85
|
`contents: read`, no secret is exposed to it, and publishing lives in a
|
|
85
|
-
workflow only a
|
|
86
|
+
workflow only a maintainer's own **Run workflow** press can trigger.
|
|
86
87
|
|
|
87
|
-
Run `pnpm typecheck && pnpm build && pnpm test &&
|
|
88
|
-
test
|
|
88
|
+
Run `pnpm typecheck && pnpm build && pnpm test && node --test
|
|
89
|
+
'.github/release/*.test.mjs' && pnpm artifacts && pnpm run test:pack`
|
|
90
|
+
yourself first and you've seen everything `verify` will tell you.
|
|
89
91
|
Mind `pnpm artifacts`: `artifacts/` is generated *and* committed, and CI
|
|
90
92
|
fails if regenerating it changes a file — so commit whatever it writes
|
|
91
93
|
together with the schema you changed. That is the single most common reason
|
|
@@ -123,22 +125,31 @@ is refused by a `prepublishOnly` script — the rule has a mechanism rather
|
|
|
123
125
|
than only a sentence. (`publish` is unaffected: it publishes the tarball
|
|
124
126
|
`verify` packed, and npm runs no prepare lifecycle for a tarball argument.)
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
128
|
+
**Release is a button**, not a tag you push. Press **Run workflow** on
|
|
129
|
+
`release` (the Actions tab), on `main`. The version comes from the
|
|
130
|
+
Conventional Commits since the last tag; a release PR turns
|
|
131
|
+
`CHANGELOG.md`'s `## [Unreleased]` into that version, dated, and sets it in
|
|
132
|
+
`package.json` — the same two things `scripts/verify-version-tag.mjs` always
|
|
133
|
+
checked, now written by the release PR instead of by hand. That PR merges
|
|
134
|
+
itself once the required `verify` check passes; the merge commit is tagged,
|
|
135
|
+
`verify.yml` runs again on it and packs the tarball, and `publish` ships
|
|
136
|
+
exactly that tarball.
|
|
137
|
+
|
|
138
|
+
A person still writes the `## [Unreleased]` entries — in the feature's own
|
|
139
|
+
pull request, as the change goes in — because the release only renames that
|
|
140
|
+
heading to a version; it never writes prose. An empty `## [Unreleased]`
|
|
141
|
+
refuses the release outright, before any branch or commit exists.
|
|
142
|
+
|
|
143
|
+
A pull request that moves the protocol window writes one of those entries
|
|
144
|
+
too, and `test/changelog.test.ts` is red until it does: some section has to
|
|
145
|
+
name the newest `bridge_from` together with the date the version before it
|
|
146
|
+
sunsets. It need not be the newest section — a release that leaves the
|
|
147
|
+
protocol alone has nothing true to restate about it.
|
|
148
|
+
|
|
149
|
+
For a pre-release — a branch elsewhere that must pin this change before it
|
|
150
|
+
is final — check `prerelease` among the workflow's inputs: it publishes
|
|
151
|
+
`X.Y.Z-next.N` under the `next` dist-tag, from any branch, with no tag, no
|
|
152
|
+
release PR and no changelog entry.
|
|
142
153
|
|
|
143
154
|
`publish` carries no npm token. It authenticates by **trusted publishing**:
|
|
144
155
|
GitHub mints a short-lived credential for the job, npm checks it against the
|
|
@@ -150,12 +161,18 @@ The job refuses a missing credential by name, rather than failing on an opaque
|
|
|
150
161
|
error from deep inside `npm publish`. There is no repository secret to add and
|
|
151
162
|
no `.npmrc` anywhere.
|
|
152
163
|
|
|
153
|
-
**If the publish job goes red after `npm publish` already ran,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
**If the publish job goes red after `npm publish` already ran, run Release
|
|
165
|
+
again.** It sees that npm already has the version and does not publish
|
|
166
|
+
twice — npm refuses to republish a version, and a second attempt would only
|
|
167
|
+
read like a broken run. The rerun continues from there: it waits for the
|
|
168
|
+
registry to serve it, then finishes. Check `npm view
|
|
169
|
+
@fleetless/contracts@<version>` first if you want to see for yourself before
|
|
170
|
+
pressing anything.
|
|
171
|
+
|
|
172
|
+
Creating or deleting a `v*` tag is restricted — the organisation ruleset
|
|
173
|
+
`release-tags` allows only admins and the release App, which is how the
|
|
174
|
+
workflow tags a release without a maintainer pushing one by hand. An admin
|
|
175
|
+
can still remove a bad tag; ask one if you are not. What removing it does
|
|
176
|
+
*not* undo is a publish: the tag is retractable, the npm version is not —
|
|
177
|
+
and Release computes the next version from the newest tag, so deleting one
|
|
178
|
+
changes what a later run proposes.
|
package/artifacts/constants.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"AUDIT_RETENTION_DAYS": 90,
|
|
3
|
-
"PROTOCOL_VERSION":
|
|
3
|
+
"PROTOCOL_VERSION": 4,
|
|
4
4
|
"PROTOCOL_VERSIONS": [
|
|
5
5
|
{
|
|
6
6
|
"version": 2,
|
|
@@ -10,13 +10,21 @@
|
|
|
10
10
|
{
|
|
11
11
|
"version": 3,
|
|
12
12
|
"bridge_from": "4.0.0",
|
|
13
|
+
"deprecated_at": "2026-09-28"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"version": 4,
|
|
17
|
+
"bridge_from": "4.1.0",
|
|
13
18
|
"deprecated_at": null
|
|
14
19
|
}
|
|
15
20
|
],
|
|
16
21
|
"PROTOCOL_SUNSET_DAYS": 90,
|
|
17
|
-
"LATEST_BRIDGE_VERSION": "4.
|
|
22
|
+
"LATEST_BRIDGE_VERSION": "4.1.0",
|
|
18
23
|
"CLOSE_ROBOT_DELETED": 4004,
|
|
19
24
|
"CLOSE_TOKEN_ROTATED": 4005,
|
|
25
|
+
"JOB_HEARTBEAT_INTERVAL_MS": 1000,
|
|
26
|
+
"JOB_HEARTBEAT_TIMEOUT_MS": 5000,
|
|
27
|
+
"JOB_OFFLINE_GRACE_MS": 300000,
|
|
20
28
|
"ASSET_UPLOAD_HEADERS": {
|
|
21
29
|
"kind": "x-fleetless-asset-kind",
|
|
22
30
|
"name": "x-fleetless-asset-name",
|
package/artifacts/openapi.json
CHANGED
|
@@ -924,6 +924,93 @@
|
|
|
924
924
|
}
|
|
925
925
|
}
|
|
926
926
|
}
|
|
927
|
+
},
|
|
928
|
+
"delete": {
|
|
929
|
+
"operationId": "delete_api_apps_id",
|
|
930
|
+
"summary": "Deletes an app and everything it produced.",
|
|
931
|
+
"tags": [
|
|
932
|
+
"apps"
|
|
933
|
+
],
|
|
934
|
+
"security": [
|
|
935
|
+
{
|
|
936
|
+
"developerSession": []
|
|
937
|
+
}
|
|
938
|
+
],
|
|
939
|
+
"parameters": [
|
|
940
|
+
{
|
|
941
|
+
"name": "id",
|
|
942
|
+
"in": "path",
|
|
943
|
+
"required": true,
|
|
944
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
945
|
+
"schema": {
|
|
946
|
+
"type": "string"
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
],
|
|
950
|
+
"responses": {
|
|
951
|
+
"204": {
|
|
952
|
+
"description": "Success."
|
|
953
|
+
},
|
|
954
|
+
"default": {
|
|
955
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `tier_required`, `invalid_uuid`, `not_found`.",
|
|
956
|
+
"content": {
|
|
957
|
+
"application/json": {
|
|
958
|
+
"schema": {
|
|
959
|
+
"$ref": "#/components/schemas/api-error"
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
},
|
|
965
|
+
"description": "Owner tier, and the gate runs **after** the org-scoped lookup: a developer-tier admin therefore sees the same `404` a stranger would for an app outside their org, rather than a tier refusal that confirms the id exists. A full cascade — its users, roles, server keys, invitations, OIDC provider configuration and mail templates all go, recorded once as `app.deleted` carrying an `appDeletionSummary`. Its robots are untouched: they belong to the org, not to the app. \n\n**No `force` parameter** — see `GET /api/apps/:id/deletion-preview`."
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
"/api/apps/{id}/deletion-preview": {
|
|
969
|
+
"get": {
|
|
970
|
+
"operationId": "get_api_apps_id_deletion_preview",
|
|
971
|
+
"summary": "Reports what deleting the app would destroy, without destroying it.",
|
|
972
|
+
"tags": [
|
|
973
|
+
"apps"
|
|
974
|
+
],
|
|
975
|
+
"security": [
|
|
976
|
+
{
|
|
977
|
+
"developerSession": []
|
|
978
|
+
}
|
|
979
|
+
],
|
|
980
|
+
"parameters": [
|
|
981
|
+
{
|
|
982
|
+
"name": "id",
|
|
983
|
+
"in": "path",
|
|
984
|
+
"required": true,
|
|
985
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
986
|
+
"schema": {
|
|
987
|
+
"type": "string"
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
],
|
|
991
|
+
"responses": {
|
|
992
|
+
"200": {
|
|
993
|
+
"description": "Success.",
|
|
994
|
+
"content": {
|
|
995
|
+
"application/json": {
|
|
996
|
+
"schema": {
|
|
997
|
+
"$ref": "#/components/schemas/app-deletion-summary"
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
"default": {
|
|
1003
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`.",
|
|
1004
|
+
"content": {
|
|
1005
|
+
"application/json": {
|
|
1006
|
+
"schema": {
|
|
1007
|
+
"$ref": "#/components/schemas/api-error"
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
},
|
|
1013
|
+
"description": "The same shape the delete's own audit event carries, computed by the same function on purpose: the confirmation dialog and the eventual receipt agree by construction, and any difference between them is real drift rather than two estimates that quietly disagree. \n\n**No `force` parameter, unlike the robot pair this is modelled on.** A robot's open live session is a single nameable state whose interruption is its own hazard, which is why that route makes the caller pass `force` explicitly. An app has no equivalent state to force past, and inventing one would be a guess wearing a guard's clothes — this preview is the guard."
|
|
927
1014
|
}
|
|
928
1015
|
},
|
|
929
1016
|
"/api/apps/{id}/roles": {
|
|
@@ -2376,11 +2463,13 @@
|
|
|
2376
2463
|
}
|
|
2377
2464
|
}
|
|
2378
2465
|
},
|
|
2379
|
-
"description": "One row per app, created with the app and never absent — an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider."
|
|
2380
|
-
}
|
|
2466
|
+
"description": "One row per app, created with the app and never absent — an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider. It stays read-only on every slice write below for a second reason: a writable callback URL would let a caller point the return leg of an OIDC sign-in, which carries an authorization code, at a host they own. `updated_at` is read-only for a duller one: the server stamps it on every write, and a client-supplied value would be a lie about when the row last changed."
|
|
2467
|
+
}
|
|
2468
|
+
},
|
|
2469
|
+
"/api/apps/{id}/auth-config/registration": {
|
|
2381
2470
|
"put": {
|
|
2382
|
-
"operationId": "
|
|
2383
|
-
"summary": "Replaces
|
|
2471
|
+
"operationId": "put_api_apps_id_auth_config_registration",
|
|
2472
|
+
"summary": "Replaces who may self-register, and from where.",
|
|
2384
2473
|
"tags": [
|
|
2385
2474
|
"apps"
|
|
2386
2475
|
],
|
|
@@ -2422,13 +2511,129 @@
|
|
|
2422
2511
|
}
|
|
2423
2512
|
}
|
|
2424
2513
|
},
|
|
2425
|
-
"description": "**A replace, not a merge, and `.strict()`**:
|
|
2514
|
+
"description": "**A replace, not a merge, and `.strict()`**: `self_registration`, `allowed_domains` and `allowed_origins` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`400 validation_error` is where the two field rules land: an entry in `allowed_domains` must be lowercase, since a capitalised one can never match a lowercased address, and an entry in `allowed_origins` must be a bare scheme-host-port with no path, since a browser sends nothing longer in its `Origin` header. Each refuses at configuration time rather than failing silently later. \n\nThe merge is server-side against the stored row, so this write never disturbs the urls or mcp slice.",
|
|
2426
2515
|
"requestBody": {
|
|
2427
2516
|
"required": true,
|
|
2428
2517
|
"content": {
|
|
2429
2518
|
"application/json": {
|
|
2430
2519
|
"schema": {
|
|
2431
|
-
"$ref": "#/components/schemas/put-app-auth-
|
|
2520
|
+
"$ref": "#/components/schemas/put-app-auth-registration-request"
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
},
|
|
2527
|
+
"/api/apps/{id}/auth-config/urls": {
|
|
2528
|
+
"put": {
|
|
2529
|
+
"operationId": "put_api_apps_id_auth_config_urls",
|
|
2530
|
+
"summary": "Replaces the three pages Fleetless's mails point at.",
|
|
2531
|
+
"tags": [
|
|
2532
|
+
"apps"
|
|
2533
|
+
],
|
|
2534
|
+
"security": [
|
|
2535
|
+
{
|
|
2536
|
+
"developerSession": []
|
|
2537
|
+
}
|
|
2538
|
+
],
|
|
2539
|
+
"parameters": [
|
|
2540
|
+
{
|
|
2541
|
+
"name": "id",
|
|
2542
|
+
"in": "path",
|
|
2543
|
+
"required": true,
|
|
2544
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
2545
|
+
"schema": {
|
|
2546
|
+
"type": "string"
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
],
|
|
2550
|
+
"responses": {
|
|
2551
|
+
"200": {
|
|
2552
|
+
"description": "Success.",
|
|
2553
|
+
"content": {
|
|
2554
|
+
"application/json": {
|
|
2555
|
+
"schema": {
|
|
2556
|
+
"$ref": "#/components/schemas/app-auth-config"
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
},
|
|
2561
|
+
"default": {
|
|
2562
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `validation_error`, `not_found`.",
|
|
2563
|
+
"content": {
|
|
2564
|
+
"application/json": {
|
|
2565
|
+
"schema": {
|
|
2566
|
+
"$ref": "#/components/schemas/api-error"
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
},
|
|
2572
|
+
"description": "**A replace, not a merge, and `.strict()`**: `invite_url`, `verify_url` and `reset_url` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`400 validation_error` is where the field rule lands: a URL template must be https (or `http` on `localhost`) and carry its placeholder exactly once — a second occurrence leaves one literal in a mailed link, refused here rather than failing silently once the mail is sent. \n\nThe merge is server-side against the stored row, so this write never disturbs the registration or mcp slice.",
|
|
2573
|
+
"requestBody": {
|
|
2574
|
+
"required": true,
|
|
2575
|
+
"content": {
|
|
2576
|
+
"application/json": {
|
|
2577
|
+
"schema": {
|
|
2578
|
+
"$ref": "#/components/schemas/put-app-auth-urls-request"
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
},
|
|
2585
|
+
"/api/apps/{id}/auth-config/mcp": {
|
|
2586
|
+
"put": {
|
|
2587
|
+
"operationId": "put_api_apps_id_auth_config_mcp",
|
|
2588
|
+
"summary": "Replaces the MCP switch and its login URL together.",
|
|
2589
|
+
"tags": [
|
|
2590
|
+
"apps"
|
|
2591
|
+
],
|
|
2592
|
+
"security": [
|
|
2593
|
+
{
|
|
2594
|
+
"developerSession": []
|
|
2595
|
+
}
|
|
2596
|
+
],
|
|
2597
|
+
"parameters": [
|
|
2598
|
+
{
|
|
2599
|
+
"name": "id",
|
|
2600
|
+
"in": "path",
|
|
2601
|
+
"required": true,
|
|
2602
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
2603
|
+
"schema": {
|
|
2604
|
+
"type": "string"
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
],
|
|
2608
|
+
"responses": {
|
|
2609
|
+
"200": {
|
|
2610
|
+
"description": "Success.",
|
|
2611
|
+
"content": {
|
|
2612
|
+
"application/json": {
|
|
2613
|
+
"schema": {
|
|
2614
|
+
"$ref": "#/components/schemas/app-auth-config"
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
},
|
|
2619
|
+
"default": {
|
|
2620
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `validation_error`, `not_found`.",
|
|
2621
|
+
"content": {
|
|
2622
|
+
"application/json": {
|
|
2623
|
+
"schema": {
|
|
2624
|
+
"$ref": "#/components/schemas/api-error"
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
},
|
|
2630
|
+
"description": "**A replace, not a merge, and `.strict()`**: `mcp_enabled` and `mcp_login_url` both arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`mcp_login_url` answers to the same rule as the `urls` slice's three templates — https (or `http` on `localhost`), its placeholder exactly once — refused as `400 validation_error` rather than left to fail mid-OAuth, in a client's browser where no console screen is watching. \n\nThe merge is server-side against the stored row, so this write never disturbs the registration or urls slice.",
|
|
2631
|
+
"requestBody": {
|
|
2632
|
+
"required": true,
|
|
2633
|
+
"content": {
|
|
2634
|
+
"application/json": {
|
|
2635
|
+
"schema": {
|
|
2636
|
+
"$ref": "#/components/schemas/put-app-auth-mcp-request"
|
|
2432
2637
|
}
|
|
2433
2638
|
}
|
|
2434
2639
|
}
|
|
@@ -8674,6 +8879,56 @@
|
|
|
8674
8879
|
],
|
|
8675
8880
|
"additionalProperties": false
|
|
8676
8881
|
},
|
|
8882
|
+
"app-deletion-summary": {
|
|
8883
|
+
"type": "object",
|
|
8884
|
+
"properties": {
|
|
8885
|
+
"user_count": {
|
|
8886
|
+
"type": "integer",
|
|
8887
|
+
"minimum": 0,
|
|
8888
|
+
"maximum": 9007199254740991,
|
|
8889
|
+
"description": "App users deleted with the app. They are the developer's own customers, not Fleetless users, and exist in no other app."
|
|
8890
|
+
},
|
|
8891
|
+
"role_count": {
|
|
8892
|
+
"type": "integer",
|
|
8893
|
+
"minimum": 0,
|
|
8894
|
+
"maximum": 9007199254740991,
|
|
8895
|
+
"description": "Roles deleted with the app, each with its per-robot slug grants."
|
|
8896
|
+
},
|
|
8897
|
+
"server_key_count": {
|
|
8898
|
+
"type": "integer",
|
|
8899
|
+
"minimum": 0,
|
|
8900
|
+
"maximum": 9007199254740991,
|
|
8901
|
+
"description": "Server keys deleted with the app. A client still holding one is refused at its next request."
|
|
8902
|
+
},
|
|
8903
|
+
"invitation_count": {
|
|
8904
|
+
"type": "integer",
|
|
8905
|
+
"minimum": 0,
|
|
8906
|
+
"maximum": 9007199254740991,
|
|
8907
|
+
"description": "Outstanding invitations — unspent and unexpired — that will never be accepted."
|
|
8908
|
+
},
|
|
8909
|
+
"oidc_provider_count": {
|
|
8910
|
+
"type": "integer",
|
|
8911
|
+
"minimum": 0,
|
|
8912
|
+
"maximum": 9007199254740991,
|
|
8913
|
+
"description": "Identity providers configured for this app. The providers themselves are somebody else's; only this app's configuration of them goes."
|
|
8914
|
+
},
|
|
8915
|
+
"mail_template_count": {
|
|
8916
|
+
"type": "integer",
|
|
8917
|
+
"minimum": 0,
|
|
8918
|
+
"maximum": 9007199254740991,
|
|
8919
|
+
"description": "Custom mail templates, of at most three. A kind using the Fleetless default text is not counted — there is no row to lose."
|
|
8920
|
+
}
|
|
8921
|
+
},
|
|
8922
|
+
"required": [
|
|
8923
|
+
"user_count",
|
|
8924
|
+
"role_count",
|
|
8925
|
+
"server_key_count",
|
|
8926
|
+
"invitation_count",
|
|
8927
|
+
"oidc_provider_count",
|
|
8928
|
+
"mail_template_count"
|
|
8929
|
+
],
|
|
8930
|
+
"additionalProperties": false
|
|
8931
|
+
},
|
|
8677
8932
|
"app-invitation": {
|
|
8678
8933
|
"type": "object",
|
|
8679
8934
|
"properties": {
|
|
@@ -16318,7 +16573,33 @@
|
|
|
16318
16573
|
"message"
|
|
16319
16574
|
]
|
|
16320
16575
|
},
|
|
16321
|
-
"put-app-auth-
|
|
16576
|
+
"put-app-auth-mcp-request": {
|
|
16577
|
+
"type": "object",
|
|
16578
|
+
"properties": {
|
|
16579
|
+
"mcp_enabled": {
|
|
16580
|
+
"type": "boolean",
|
|
16581
|
+
"description": "Whether this app serves an MCP endpoint at `/mcp/<identifier>`. Off refuses the whole OAuth surface for the app, not merely the tool calls, and is re-read on every request rather than cached off a token."
|
|
16582
|
+
},
|
|
16583
|
+
"mcp_login_url": {
|
|
16584
|
+
"anyOf": [
|
|
16585
|
+
{
|
|
16586
|
+
"type": "string",
|
|
16587
|
+
"maxLength": 500
|
|
16588
|
+
},
|
|
16589
|
+
{
|
|
16590
|
+
"type": "null"
|
|
16591
|
+
}
|
|
16592
|
+
],
|
|
16593
|
+
"description": "The page an MCP authorization redirects to, with `{interaction}` where the interaction id goes. Not a token: the id names a pending request the server already holds, and the app authenticates the user itself before approving it."
|
|
16594
|
+
}
|
|
16595
|
+
},
|
|
16596
|
+
"required": [
|
|
16597
|
+
"mcp_enabled",
|
|
16598
|
+
"mcp_login_url"
|
|
16599
|
+
],
|
|
16600
|
+
"additionalProperties": false
|
|
16601
|
+
},
|
|
16602
|
+
"put-app-auth-registration-request": {
|
|
16322
16603
|
"type": "object",
|
|
16323
16604
|
"properties": {
|
|
16324
16605
|
"self_registration": {
|
|
@@ -16344,11 +16625,18 @@
|
|
|
16344
16625
|
"maxLength": 200
|
|
16345
16626
|
},
|
|
16346
16627
|
"description": "The origins the client auth API answers CORS for, and the only origins an OIDC `redirect_uri` may name. Bare origins: scheme, host and port, with no path — a browser sends nothing longer, so an entry carrying one could never match."
|
|
16347
|
-
}
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16628
|
+
}
|
|
16629
|
+
},
|
|
16630
|
+
"required": [
|
|
16631
|
+
"self_registration",
|
|
16632
|
+
"allowed_domains",
|
|
16633
|
+
"allowed_origins"
|
|
16634
|
+
],
|
|
16635
|
+
"additionalProperties": false
|
|
16636
|
+
},
|
|
16637
|
+
"put-app-auth-urls-request": {
|
|
16638
|
+
"type": "object",
|
|
16639
|
+
"properties": {
|
|
16352
16640
|
"invite_url": {
|
|
16353
16641
|
"anyOf": [
|
|
16354
16642
|
{
|
|
@@ -16384,29 +16672,12 @@
|
|
|
16384
16672
|
}
|
|
16385
16673
|
],
|
|
16386
16674
|
"description": "The page that takes a new password, with `{token}` where the token goes."
|
|
16387
|
-
},
|
|
16388
|
-
"mcp_login_url": {
|
|
16389
|
-
"anyOf": [
|
|
16390
|
-
{
|
|
16391
|
-
"type": "string",
|
|
16392
|
-
"maxLength": 500
|
|
16393
|
-
},
|
|
16394
|
-
{
|
|
16395
|
-
"type": "null"
|
|
16396
|
-
}
|
|
16397
|
-
],
|
|
16398
|
-
"description": "The page an MCP authorization redirects to, with `{interaction}` where the interaction id goes. Not a token: the id names a pending request the server already holds, and the app authenticates the user itself before approving it."
|
|
16399
16675
|
}
|
|
16400
16676
|
},
|
|
16401
16677
|
"required": [
|
|
16402
|
-
"self_registration",
|
|
16403
|
-
"allowed_domains",
|
|
16404
|
-
"allowed_origins",
|
|
16405
|
-
"mcp_enabled",
|
|
16406
16678
|
"invite_url",
|
|
16407
16679
|
"verify_url",
|
|
16408
|
-
"reset_url"
|
|
16409
|
-
"mcp_login_url"
|
|
16680
|
+
"reset_url"
|
|
16410
16681
|
],
|
|
16411
16682
|
"additionalProperties": false
|
|
16412
16683
|
},
|