@fleetless/contracts 1.0.3 → 1.0.5

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
@@ -5,6 +5,81 @@ 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
6
  the wire shapes.
7
7
 
8
+ ## [1.0.5] — 2026-09-07
9
+
10
+ The guard was rebuilt around the set of bytes that become public rather than
11
+ around three directory names, and it found 190 things the old shape could not
12
+ see. **No wire shape changes**: two `notes` sentences are repaired and nothing
13
+ else in `artifacts/` moves.
14
+
15
+ ### Fixed
16
+
17
+ - **Two sentences an earlier sweep broke, in the published API reference.**
18
+ `POST /mcp/:appIdentifier` ended "one answer for two states, which is one
19
+ answer for two states", a tautology left behind when a clause was removed;
20
+ `GET /api/robots/:id/jobs/history` read "`history` is a syntactically valid
21
+ slug and The router matches a static segment first", a mid-sentence capital
22
+ left behind when a product name was replaced. Both are in `routes.json` and
23
+ `openapi.json`, so both were in every client generated from 1.0.4 and on the
24
+ public reference page.
25
+ - **Six internal schedule references on exported schemas**, in `alerts.ts`,
26
+ `config.ts`, `config-issues.ts`, `errors.ts` and `identity.ts`. They ship in
27
+ the declarations, so an editor showed them on hover to anyone who installed
28
+ the package. A private repository path in an `errors.ts` comment went with
29
+ them.
30
+ - **`test/` and `scripts/` were outside the guard entirely**, and both mirror
31
+ in full. 160 further references swept: internal decision labels, schedule
32
+ labels, citations of two repositories that stay private and of the
33
+ maintainer-only files, the reference robot's name in two fixtures, and
34
+ internal task ids.
35
+
36
+ ### Changed
37
+
38
+ - **The scanned set is computed, not named.** It is the union of what `npm
39
+ pack` reports, what `git ls-files` reports and a walk of every directory in
40
+ `files`. A file in none of the three is out of scope; a file in any of them
41
+ is scanned. The predecessor named `src`, `dist`, `artifacts` and five
42
+ markdown files, which left `package.json`, `.gitlab-ci.yml`, the tsconfigs
43
+ and both other trees unswept.
44
+ - **Only the German scan strips anything**, and only URLs and single-token code
45
+ spans. Stripping links and backticks before every class made the shipped
46
+ documents blind to an internal hostname inside a markdown link.
47
+ - **Eighteen detectors, each carrying its own fixtures.** Suffixed decision
48
+ labels (`D3a` never matched), bare review codenames, schedule labels in the
49
+ three spellings this codebase writes, dotfile and bare two-segment paths into
50
+ a sibling repository, task ids and CI pipeline numbers. A floor over the
51
+ count makes deleting a detector red.
52
+ - **`scripts/verify-pack.mjs`** now reads the *packed* manifest rather than the
53
+ one on disk, checks every dependency section rather than `dependencies`
54
+ alone, runs the marker detectors over that manifest, asserts each published
55
+ file declares exactly **one** SPDX identifier rather than reading its first
56
+ line, and verifies that every relative link in every shipped document
57
+ resolves inside the tarball.
58
+
59
+ ### Added
60
+
61
+ - **`scripts/verify-commit-messages.mjs`**, wired into the verify job. A commit
62
+ message is public the moment it is pushed, and this history mirrors.
63
+
64
+ ## [1.0.4] — 2026-09-07
65
+
66
+ Two things a grep over the published 1.0.3 tarball found that the guard was not
67
+ looking for. **No wire shape changes**; `artifacts/` is byte-identical to 1.0.3.
68
+
69
+ ### Fixed
70
+
71
+ - **A published `dist/` comment cited a maintainer-only file** and two internal server
72
+ symbols by name. Rewritten to say what the rule is rather than where it is
73
+ written down.
74
+ - **The markdown this package ships was outside the guard.** `README.md`,
75
+ `CHANGELOG.md`, `SECURITY.md`, `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md` are
76
+ published bytes like any other, and nothing was scanning them. They are now
77
+ swept for every marker class — with the stance classes deliberately exempt,
78
+ because those documents are legitimately *about* this repository and a guard
79
+ that reddened on "this repository is a schema library" would be demanding they
80
+ stop addressing their reader.
81
+ - **A new marker class**: a reference to a file only the maintainers have.
82
+
8
83
  ## [1.0.3] — 2026-09-07
9
84
 
10
85
  The second half of 1.0.2's sweep. **No wire shape changes**: every file under
@@ -3643,7 +3643,7 @@
3643
3643
  }
3644
3644
  }
3645
3645
  },
3646
- "description": "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions — the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance — so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* — one answer for two states, which is one answer for two states. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* — the app, its switch, then the bearer, in the order `POST` describes — and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
3646
+ "description": "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions — the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance — so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* — so an unregistered verb and an unknown app would answer identically. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* — the app, its switch, then the bearer, in the order `POST` describes — and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
3647
3647
  },
3648
3648
  "delete": {
3649
3649
  "operationId": "delete_mcp_appIdentifier",
@@ -6406,7 +6406,7 @@
6406
6406
  }
6407
6407
  }
6408
6408
  },
6409
- "description": "Needs the `action_history` capability, and **this route is what makes that switch mean something** — it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug and The router matches a static segment first, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
6409
+ "description": "Needs the `action_history` capability, and **this route is what makes that switch mean something** — it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug, and a static segment matches before a parameter, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
6410
6410
  }
6411
6411
  },
6412
6412
  "/api/robots/{id}/jobs/{slug}": {
@@ -2370,7 +2370,7 @@
2370
2370
  "forbidden"
2371
2371
  ],
2372
2372
  "transport": "http",
2373
- "notes": "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions — the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance — so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* — one answer for two states, which is one answer for two states. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* — the app, its switch, then the bearer, in the order `POST` describes — and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
2373
+ "notes": "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions — the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance — so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* — so an unregistered verb and an unknown app would answer identically. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* — the app, its switch, then the bearer, in the order `POST` describes — and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
2374
2374
  },
2375
2375
  {
2376
2376
  "method": "DELETE",
@@ -3788,7 +3788,7 @@
3788
3788
  "validation_error"
3789
3789
  ],
3790
3790
  "transport": "http",
3791
- "notes": "Needs the `action_history` capability, and **this route is what makes that switch mean something** — it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug and The router matches a static segment first, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
3791
+ "notes": "Needs the `action_history` capability, and **this route is what makes that switch mean something** — it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug, and a static segment matches before a parameter, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
3792
3792
  },
3793
3793
  {
3794
3794
  "method": "POST",
package/dist/alerts.d.ts CHANGED
@@ -53,7 +53,7 @@ import { z } from 'zod';
53
53
  * derives it from the document (`alert-definitions.ts`'s `toRowCondition`) and
54
54
  * derives it nowhere else.
55
55
  *
56
- * **It was renamed for a wave 4 deletion that did not happen**, and the name
56
+ * **It was renamed for a deletion that did not happen**, and the name
57
57
  * is kept because the distinction it draws is still needed: two condition
58
58
  * shapes coexist, and only one of them is authored. Nothing is stored under
59
59
  * this shape any more — the database keeps runtime state only — so read `Row`
package/dist/alerts.js CHANGED
@@ -54,7 +54,7 @@ import { slug } from './common.js';
54
54
  * derives it from the document (`alert-definitions.ts`'s `toRowCondition`) and
55
55
  * derives it nowhere else.
56
56
  *
57
- * **It was renamed for a wave 4 deletion that did not happen**, and the name
57
+ * **It was renamed for a deletion that did not happen**, and the name
58
58
  * is kept because the distinction it draws is still needed: two condition
59
59
  * shapes coexist, and only one of them is authored. Nothing is stored under
60
60
  * this shape any more — the database keeps runtime state only — so read `Row`
@@ -150,8 +150,8 @@ export declare function splitFormatPath(path: string): Array<string | number>;
150
150
  * A stable hash of a schema object, for asking *is the thing running the one
151
151
  * I think it is?*
152
152
  *
153
- * Wave 5's browser sweep enumerates positions against a schema it holds and
154
- * has to know that the editor is running the same one; the manifest that
153
+ * A browser sweep enumerates positions against a schema it holds and has to
154
+ * know that the editor is running the same one; the manifest that
155
155
  * makes a schema-side change announce itself uses the same number as its
156
156
  * baseline. Both are the same question, so there is one implementation of it:
157
157
  * a second one on the sweep side would drift, and the gate would then go red
@@ -293,8 +293,8 @@ function valueAt(root, path) {
293
293
  * A stable hash of a schema object, for asking *is the thing running the one
294
294
  * I think it is?*
295
295
  *
296
- * Wave 5's browser sweep enumerates positions against a schema it holds and
297
- * has to know that the editor is running the same one; the manifest that
296
+ * A browser sweep enumerates positions against a schema it holds and has to
297
+ * know that the editor is running the same one; the manifest that
298
298
  * makes a schema-side change announce itself uses the same number as its
299
299
  * baseline. Both are the same question, so there is one implementation of it:
300
300
  * a second one on the sweep side would drift, and the gate would then go red
package/dist/config.d.ts CHANGED
@@ -74,7 +74,7 @@ export type ParameterType = z.infer<typeof parameterType>;
74
74
  *
75
75
  * There is no `required` field. A placeholder cannot be left unfilled, so
76
76
  * "required" is exactly "has no `default`" — a second spelling of one fact
77
- * is the defect this file has spent two waves removing.
77
+ * is the defect this file has been removing a spelling at a time.
78
78
  */
79
79
  export declare const parameterSpec: z.ZodObject<{
80
80
  type: z.ZodEnum<{
package/dist/config.js CHANGED
@@ -5,8 +5,8 @@ import { applyError, slug, rosName, rosTypeName, fieldPath, SLUG_RULE, ROS_NAME_
5
5
  * `alertSeverity` is identical for the stored row and this document-nested
6
6
  * definition — `z.enum(['warning', 'error'])`, nothing more to say twice —
7
7
  * so it is imported rather than redefined. Not re-exported from here: the
8
- * barrel already carries it from `alerts.ts`, and wave 4 moves the
9
- * definition itself into this file once `alerts.ts` retires.
8
+ * barrel already carries it from `alerts.ts`, and the definition itself moves
9
+ * into this file once `alerts.ts` retires.
10
10
  */
11
11
  import { alertSeverity } from './alerts.js';
12
12
  /**
@@ -97,8 +97,8 @@ import { alertSeverity } from './alerts.js';
97
97
  * says. They are the same sentence, which is the point of there being one.
98
98
  *
99
99
  * The tense matters because the two states look identical from inside this
100
- * file. Whoever reads it after wave 3 should find a claim that was true when
101
- * written and stayed true, not one that quietly became false.
100
+ * file. Whoever reads it later should find a claim that was true when written
101
+ * and stayed true, not one that quietly became false.
102
102
  *
103
103
  * Each one states the rule in words and gives one example, and none of them
104
104
  * quotes its own regex — `config-messages.test.ts` asserts that over every
@@ -474,7 +474,7 @@ const PARAMETER_SNIPPET = {
474
474
  *
475
475
  * There is no `required` field. A placeholder cannot be left unfilled, so
476
476
  * "required" is exactly "has no `default`" — a second spelling of one fact
477
- * is the defect this file has spent two waves removing.
477
+ * is the defect this file has been removing a spelling at a time.
478
478
  */
479
479
  export const parameterSpec = strictObject({
480
480
  type: parameterType.meta({
package/dist/errors.js CHANGED
@@ -420,14 +420,14 @@ export const ERROR_CODES = [
420
420
  * no producer is not harmless, because a reader arriving at it takes it for
421
421
  * a live refusal. Say which it is, and say when it changes.
422
422
  *
423
- * **It has one producer already, one train early**: `POST /mcp` answers it to
424
- * an `mcp_session` token whose subject is an app user, because the central
425
- * endpoint serves the team only. No client can hold such a token yet — only
426
- * a test mints one — and the per-app train adds the
427
- * `appAuthConfig.mcp_enabled` gate this comment describes.
423
+ * **It has one producer already, ahead of the gate it describes**: `POST
424
+ * /mcp` answers it to an `mcp_session` token whose subject is an app user,
425
+ * because the central endpoint serves the team only. No client can hold such
426
+ * a token yet — only a test mints one — and the `appAuthConfig.mcp_enabled`
427
+ * gate arrives with the per-app endpoint.
428
428
  *
429
429
  * `tool_not_available` below still has no producer — `grep` finds it nowhere
430
- * in `cloud/src`. Named as unproduced, for the same reason.
430
+ * in the cloud's source. Named as unproduced, for the same reason.
431
431
  */
432
432
  'mcp_disabled',
433
433
  /**
@@ -536,7 +536,7 @@ export const ERROR_CODES = [
536
536
  // what the cloud actually sends.
537
537
  //
538
538
  // They are listed in one block, with their producer named, rather than filed
539
- // among the waves that introduced them — the honest record is *when this list
539
+ // by the release that introduced each — the honest record is *when this list
540
540
  // learned about them*, not when the cloud started sending them.
541
541
  /**
542
542
  * `409` from the configuration routes: the draft parses as YAML but its root
@@ -502,8 +502,8 @@ export type IdpIssuer = z.infer<typeof idpIssuer>;
502
502
  * The per-app OIDC vocabulary is `clientOidcErrorCode` in `client-auth.ts`: a
503
503
  * different list, for a different flow, redirected to the developer's own page
504
504
  * rather than rendered by Fleetless. Two callback error enums coexisting is how
505
- * the wrong one gets picked up by the train that adds per-app OIDC, which is
506
- * why this one is deleted rather than left standing for it.
505
+ * the wrong one gets picked up when per-app OIDC arrives, which is why this
506
+ * one is deleted rather than left standing for it.
507
507
  */
508
508
  /**
509
509
  * `GET /api/auth/me` — named so the console can validate it.
package/dist/identity.js CHANGED
@@ -479,8 +479,8 @@ export const idpIssuer = z
479
479
  * The per-app OIDC vocabulary is `clientOidcErrorCode` in `client-auth.ts`: a
480
480
  * different list, for a different flow, redirected to the developer's own page
481
481
  * rather than rendered by Fleetless. Two callback error enums coexisting is how
482
- * the wrong one gets picked up by the train that adds per-app OIDC, which is
483
- * why this one is deleted rather than left standing for it.
482
+ * the wrong one gets picked up when per-app OIDC arrives, which is why this
483
+ * one is deleted rather than left standing for it.
484
484
  */
485
485
  /**
486
486
  * `GET /api/auth/me` — named so the console can validate it.
package/dist/routes.js CHANGED
@@ -79,20 +79,18 @@ export const IN_HANDLER_ROUTES = [
79
79
  * `developer_or_client` or MCP surfaces, which is why `CLIENT_GUARD` keeps it.
80
80
  *
81
81
  * **Not `invalid_token`.** That code exists in `ERROR_CODES` and this guard has
82
- * never sent it; the three above are what `sendTokenRefusal` actually maps to.
83
- * Said plainly because the planning note for this file assumed otherwise, and a
84
- * documented refusal a caller cannot receive is the third failure mode in
85
- * CLAUDE.md's list.
82
+ * never sent it; the three above are what the token refusal actually maps to.
83
+ * Said plainly, because a documented refusal a caller cannot receive is worse
84
+ * than an undocumented one: a consumer branches on it and the branch is dead.
86
85
  */
87
86
  const DEVELOPER_GUARD = ['unauthorized', 'token_expired', 'token_revoked'];
88
87
  /**
89
- * The same, for `auth: 'developer_or_client'` — `createRequireDeveloperOrClient`,
90
- * which resolves a developer bearer, an end-user bearer **or** a server key
91
- * through one `resolveAnyToken`.
88
+ * The same, for `auth: 'developer_or_client'` — one guard resolving a developer
89
+ * bearer, an end-user bearer **or** a server key through a single token lookup.
92
90
  *
93
- * **The same four codes, not five.** `sendTokenRefusal` has a fifth arm,
94
- * `account_blocked`, and this list carried it for exactly one commit. Nothing
95
- * reaches it: `TokenRefusalReason` admits `'blocked'`, but no site in
91
+ * **The same four codes, not five.** The token refusal has a fifth arm,
92
+ * `account_blocked`, which this list must not carry. Nothing reaches it: the
93
+ * refusal type admits `'blocked'`, but no site in
96
94
  * the cloud constructs one — the only reasons ever returned are `'invalid'`,
97
95
  * `'revoked'` and `'forbidden'`. Access is withdrawn by removing an assignment
98
96
  * rather than by blocking an account, so an app user who loses access loses it
@@ -1177,8 +1175,8 @@ export const ROUTES = [
1177
1175
  '\n\n**The `405` is this cloud\'s own answer, not the SDK\'s**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on ' +
1178
1176
  '`GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any ' +
1179
1177
  'business doing, so the cloud writes the `405` itself in the transport\'s own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers ' +
1180
- '`404`, and at a path whose last segment is an app identifier a `404` already means *no such app* — one answer for two states, which is ' +
1181
- 'one answer for two states. Registering the verb lets the endpoint say "this app\'s server is here; this verb is not ' +
1178
+ '`404`, and at a path whose last segment is an app identifier a `404` already means *no such app* — so an unregistered verb and an ' +
1179
+ 'unknown app would answer identically. Registering the verb lets the endpoint say "this app\'s server is here; this verb is not ' +
1182
1180
  'part of it". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its ' +
1183
1181
  '`404`. \n\n**The `405` body is the transport\'s JSON-RPC error object, not the `apiError` envelope.** The three codes above are the ' +
1184
1182
  'refusals that come *first* — the app, its switch, then the bearer, in the order `POST` describes — and they are `apiError` because ' +
@@ -1936,8 +1934,8 @@ export const ROUTES = [
1936
1934
  query: jobRunQuery, request: null, response: jobRunListResponse,
1937
1935
  errors: [...CLIENT_GUARD, 'invalid_uuid', 'not_found', 'capability_required', 'validation_error'], transport: 'http',
1938
1936
  notes: 'Needs the `action_history` capability, and **this route is what makes that switch mean something** — it was unkeepable while nothing ' +
1939
- 'durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug and ' +
1940
- 'The router matches a static segment first, so a robot with a service literally slugged `history` can no longer be **read** through ' +
1937
+ 'durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug, and ' +
1938
+ 'a static segment matches before a parameter, so a robot with a service literally slugged `history` can no longer be **read** through ' +
1941
1939
  '`GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, ' +
1942
1940
  'so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with ' +
1943
1941
  'the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetless/contracts",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Fleetless wire contracts: the bridge-cloud protocol, the REST API schemas and the error codes, as zod schemas with generated JSON Schema and OpenAPI artifacts.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dehne Robotik GmbH",
@@ -52,6 +52,7 @@
52
52
  "test": "vitest run",
53
53
  "artifacts": "tsx scripts/export-schemas.ts",
54
54
  "test:pack": "node scripts/verify-pack.mjs",
55
+ "verify:commits": "node scripts/verify-commit-messages.mjs",
55
56
  "prepublishOnly": "node scripts/refuse-manual-publish.mjs"
56
57
  },
57
58
  "dependencies": {