@fleetless/contracts 1.0.0 → 1.0.2

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +68 -2
  2. package/CODE_OF_CONDUCT.md +83 -0
  3. package/CONTRIBUTING.md +136 -0
  4. package/README.md +49 -13
  5. package/SECURITY.md +55 -0
  6. package/artifacts/openapi.json +1 -1
  7. package/artifacts/routes.json +1 -1
  8. package/dist/alerts.d.ts +19 -24
  9. package/dist/alerts.js +18 -24
  10. package/dist/app-users.d.ts +7 -6
  11. package/dist/app-users.js +6 -6
  12. package/dist/apps.d.ts +21 -25
  13. package/dist/apps.js +40 -51
  14. package/dist/assets.d.ts +70 -132
  15. package/dist/assets.js +130 -223
  16. package/dist/audit.d.ts +11 -11
  17. package/dist/audit.js +25 -51
  18. package/dist/client-auth.d.ts +4 -4
  19. package/dist/client-auth.js +3 -4
  20. package/dist/common.d.ts +27 -35
  21. package/dist/common.js +26 -35
  22. package/dist/config-issues.d.ts +4 -3
  23. package/dist/config-issues.js +7 -6
  24. package/dist/config.d.ts +31 -37
  25. package/dist/config.js +81 -110
  26. package/dist/errors.d.ts +4 -3
  27. package/dist/errors.js +61 -87
  28. package/dist/identity.d.ts +18 -21
  29. package/dist/identity.js +17 -21
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.js +12 -13
  32. package/dist/introspection.d.ts +7 -6
  33. package/dist/introspection.js +6 -6
  34. package/dist/jobs.d.ts +12 -12
  35. package/dist/jobs.js +20 -25
  36. package/dist/mcp.d.ts +11 -12
  37. package/dist/mcp.js +10 -12
  38. package/dist/oauth.d.ts +13 -18
  39. package/dist/oauth.js +13 -19
  40. package/dist/protocol.d.ts +51 -62
  41. package/dist/protocol.js +107 -139
  42. package/dist/realtime.d.ts +53 -68
  43. package/dist/realtime.js +77 -103
  44. package/dist/rest.d.ts +182 -243
  45. package/dist/rest.js +301 -395
  46. package/dist/routes.d.ts +4 -3
  47. package/dist/routes.js +3 -2
  48. package/package.json +12 -7
package/CHANGELOG.md CHANGED
@@ -5,15 +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.2] — 2026-09-07
9
+
10
+ A documentation and packaging release. (1.0.1 was tagged and never published:
11
+ its `verify` job went red on a licence-header guard that swept the pipeline's
12
+ own scratch file. The tag pattern is protected and cannot be moved, so the
13
+ release carries the next number. Nothing was ever served as 1.0.1.)
14
+
15
+ **No wire shape changes**, and nothing generated from a schema changes either — `artifacts/schema/` and
16
+ `artifacts/schema-outgoing/` are byte-identical to 1.0.0. What changes is what
17
+ the package says about itself.
18
+
19
+ ### Fixed
20
+
21
+ - **The internal engineering prose is gone from the published bytes.** Doc
22
+ comments in the source were written for the people who built this and `tsc`
23
+ carries them into `dist/*.js` and `dist/*.d.ts`, so 1.0.0 shipped German
24
+ paragraphs, a reference robot's workspace paths, measured mesh sizes and
25
+ internal tracker ids to anyone who hovered a symbol in an editor. Every
26
+ comment that ships has been rewritten for a reader who has only this package.
27
+ A test now greps the built `dist/` and `artifacts/` for those markers and
28
+ fails on a hit.
29
+ - **The `artifacts/` table in the README described `schema-outgoing/`
30
+ backwards.** It said those files were the messages the cloud sends to a
31
+ bridge. They are the opposite: the frames a bridge **sends**, rendered in
32
+ output mode, and validating incoming cloud frames against them fails on every
33
+ real frame. The table now says what each directory holds and why there are
34
+ two.
35
+ - **The README claimed the robot-side bridge validates incoming frames against
36
+ `artifacts/schema/` at runtime with Python's `jsonschema`.** It does not. The
37
+ bridge does not depend on this package at all — it vendors its own copies —
38
+ and only its test harness imports `jsonschema`. There is no runtime validation
39
+ of incoming frames against these schemas.
40
+ - **The security reporting address was in nothing the package served.**
41
+ `SECURITY.md` was not in `files`, and the README linked to it relatively.
42
+ `SECURITY.md`, `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md` now ship, and the
43
+ address is written out in the README under its own heading rather than behind
44
+ a link.
45
+ - **Twenty-two of the forty published files carried no SPDX header.**
46
+ Declaration emit drops a leading `//` comment and two `.js` files had theirs
47
+ elided, so more than half of what a licence scanner sees was unmarked. Every
48
+ file in `dist/` now carries `// SPDX-License-Identifier: Apache-2.0`, and the
49
+ pack check asserts it over the tarball's own bytes.
50
+ - **The CHANGELOG's account of 1.0.0 was wrong about which server it matched**
51
+ — see the corrected note below.
52
+
53
+ ### Changed
54
+
55
+ - `zod` moves from `^4.0.0` to `^4.4.3`. It stays a range on purpose, and the
56
+ README now says why: zod is a peer in everything but name, and an exact pin
57
+ forces a second copy on any consumer whose lockfile resolves a different
58
+ patch. `^4.4.3` is the floor these schemas are built, tested and exported
59
+ against, rather than a version nobody has run.
60
+ - `bugs` gains an email address, so a report has somewhere to go from the npm
61
+ page.
62
+
8
63
  ## [1.0.0] — 2026-09-07
9
64
 
10
- The first published version. Nothing about the shapes changes with it: they are
11
- exactly the shapes the Fleetless cloud serves at release 0.17.0, and they have
65
+ The first published version. Nothing about the shapes changes with it: they have
12
66
  been the contract between the cloud, the console, the SDK and the robot-side
13
67
  bridge for as long as those have existed. What changes is who can read them —
14
68
  until now this package was resolvable only from a private git URL, so nobody
15
69
  outside the project could build a Fleetless client from source.
16
70
 
71
+ **Corrected in 1.0.2:** this entry originally said these were "exactly the
72
+ shapes the Fleetless cloud serves at release 0.17.0". They are not. This package
73
+ tracks the cloud's `main` branch, not its releases, and 1.0.0 was cut from a
74
+ commit that already carried the MCP consent-withdrawal change — so the route
75
+ notes and the OpenAPI description for
76
+ `DELETE /api/client/mcp/grants/:clientId` and its developer twin state that
77
+ withdrawal ends a session at the client's very next call, which the deployed
78
+ 0.17.0 does not do. On 0.17.0 a withdrawn client keeps working for the remaining
79
+ lifetime of its access token, up to fifteen minutes. **A shape or a note here may
80
+ precede the release that serves it**; check the platform's own release notes
81
+ before treating one as an operational control.
82
+
17
83
  Consumers should pin an exact version. A range cannot express "compatible with
18
84
  the server you are talking to", and that is the only compatibility question
19
85
  these schemas answer. Any change that makes a previously valid message invalid
@@ -0,0 +1,83 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or advances of any kind
22
+ * Trolling, insulting or derogatory comments, and personal or political attacks
23
+ * Public or private harassment
24
+ * Publishing others' private information, such as a physical or email address, without their explicit permission
25
+ * Other conduct which could reasonably be considered inappropriate in a professional setting
26
+
27
+ ## Enforcement Responsibilities
28
+
29
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32
+
33
+ ## Scope
34
+
35
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36
+
37
+ ## Enforcement
38
+
39
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at hello@fleetless.dev. All complaints will be reviewed and investigated promptly and fairly.
40
+
41
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42
+
43
+ ## Enforcement Guidelines
44
+
45
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46
+
47
+ ### 1. Correction
48
+
49
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
50
+
51
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
52
+
53
+ ### 2. Warning
54
+
55
+ **Community Impact**: A violation through a single incident or series of actions.
56
+
57
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
58
+
59
+ ### 3. Temporary Ban
60
+
61
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
62
+
63
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
64
+
65
+ ### 4. Permanent Ban
66
+
67
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
68
+
69
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
70
+
71
+ ## Attribution
72
+
73
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
74
+
75
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
76
+
77
+ For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
78
+
79
+ [homepage]: https://www.contributor-covenant.org
80
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
81
+ [Mozilla CoC]: https://github.com/mozilla/diversity
82
+ [FAQ]: https://www.contributor-covenant.org/faq
83
+ [translations]: https://www.contributor-covenant.org/translations
@@ -0,0 +1,136 @@
1
+ # Contributing to `@fleetless/contracts`
2
+
3
+ This package is the single source of wire truth for Fleetless: every shape the
4
+ API accepts or returns, and every message the robot-side bridge exchanges with
5
+ the cloud, is written here once as a zod schema and exported as JSON Schema and
6
+ OpenAPI for consumers that are not TypeScript.
7
+
8
+ That makes a change here a cross-repository change. A schema is not a detail of
9
+ one program; it is the agreement between several.
10
+
11
+ ## Setup
12
+
13
+ Node 22 and pnpm (through corepack):
14
+
15
+ ```sh
16
+ nvm use 22
17
+ corepack enable
18
+ pnpm install
19
+ ```
20
+
21
+ There are no private dependencies. `pnpm install` works from a clean checkout
22
+ with nothing but a network connection to the npm registry.
23
+
24
+ ## The checks
25
+
26
+ | Command | What it does |
27
+ |---|---|
28
+ | `pnpm typecheck` | `tsc --noEmit` over `src/`, then again over `test/` with its own project. Both halves, always — the root project is `src`-only and says nothing about test files. |
29
+ | `pnpm test` | The vitest suite. No network, no server. |
30
+ | `pnpm build` | `tsc` into `dist/`. |
31
+ | `pnpm artifacts` | Regenerates `artifacts/` from the zod schemas. |
32
+ | `pnpm run test:pack` | Packs the tarball, asserts what is and is not inside it, then installs it into a scratch project and imports it for real. |
33
+
34
+ To run one test file, use `pnpm vitest run test/<name>.test.ts`. Do **not**
35
+ use `pnpm test -- <pattern>`: it does not filter, it runs the whole suite, and
36
+ the exit code you read is the suite's.
37
+
38
+ ## Changing a schema
39
+
40
+ 1. Edit the zod schema in `src/`.
41
+ 2. Run `pnpm artifacts`. This regenerates `artifacts/`, which is **committed**.
42
+ A test fails if the committed artifacts are stale, and so does CI.
43
+ 3. Run `pnpm typecheck && pnpm test`.
44
+ 4. Commit the schema change and the regenerated artifacts together. A commit
45
+ that changes one without the other is a commit that publishes a lie to every
46
+ consumer validating against the artifact.
47
+
48
+ Every source file carries the SPDX header `// SPDX-License-Identifier:
49
+ Apache-2.0` as its first line. A test asserts this over the whole set — and the
50
+ directory list is derived from the repository rather than written down, so a new
51
+ top-level directory is swept the day it exists.
52
+
53
+ The published files carry it too, which `tsc` does not do on its own:
54
+ declaration emit drops a leading comment, so `scripts/stamp-dist.mjs` puts the
55
+ header back on every file in `dist/` and `pnpm run test:pack` asserts it over
56
+ the tarball's own bytes.
57
+
58
+ **Nothing internal reaches the published bytes.** Doc comments in `src/` are
59
+ carried into `dist/*.js` and `dist/*.d.ts` by `tsc`, and every
60
+ `.meta({ description })` is copied into the JSON Schema and OpenAPI artifacts —
61
+ so a comment written for the people who build this is a comment an editor shows
62
+ to somebody who installed it. Write a description as what a field means to a
63
+ caller, never as how the behaviour was found, on which machine, or under which
64
+ internal ticket. `test/published-prose.test.ts` greps the built output and fails
65
+ on German prose, an internal ticket id, a developer machine path or a
66
+ first-name attribution.
67
+
68
+ ## Pull requests
69
+
70
+ **Pull requests are welcome on GitHub**, at
71
+ <https://github.com/fleetless/contracts>. Open an issue first for anything that
72
+ changes an existing shape, so we can say what else has to move with it.
73
+
74
+ **CI runs on GitLab.** This repository is mirrored from an internal GitLab
75
+ instance, which is where the pipeline that verifies and publishes it lives. You
76
+ will not see a check run on your GitHub pull request; a maintainer runs the
77
+ same checks, and the result comes back as a review comment. Run `pnpm
78
+ typecheck && pnpm test && pnpm artifacts` yourself before you open the pull
79
+ request and you will have seen everything CI would tell you.
80
+
81
+ ## The CLA
82
+
83
+ The first pull request you open will ask you to sign a Contributor Licence
84
+ Agreement. It grants Dehne Robotik GmbH the right to license your contribution
85
+ under terms other than Apache-2.0 in future — that is the whole of what it is
86
+ for, and it is why the project can relicense its own code later without having
87
+ to find every past contributor. It does not take your copyright away and it
88
+ does not stop you from using your own contribution however you like.
89
+
90
+ ## Commit messages
91
+
92
+ [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/):
93
+ `feat:`, `fix:`, `docs:`, `chore:`, `ci:`, `refactor:`, `test:`. A change that
94
+ alters an existing wire shape is a breaking change; say so with a `!` and a
95
+ `BREAKING CHANGE:` footer, because it decides the next version number.
96
+
97
+ Everything in this repository is written in **English** — code, comments,
98
+ commit messages, documentation.
99
+
100
+ ## Code of conduct
101
+
102
+ By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
103
+
104
+
105
+ ## Releasing (maintainers)
106
+
107
+ The pipeline publishes, and `npm publish` from a working tree is refused by a
108
+ `prepublishOnly` script — the rule has a mechanism rather than only a sentence.
109
+ (The pipeline is unaffected: it publishes the tarball `verify` packed, and npm
110
+ runs no prepare lifecycle for a tarball argument.)
111
+
112
+ 1. Update `CHANGELOG.md` and set the new version in `package.json`. **CI checks
113
+ both**, in `scripts/verify-version-tag.mjs`: `package.json` must equal the
114
+ tag without its `v`, and `CHANGELOG.md` must carry a dated heading reading
115
+ exactly `## [X.Y.Z] — YYYY-MM-DD`. `CHANGELOG.md` ships inside the tarball,
116
+ so a forgotten entry documents the wrong version to every consumer and npm
117
+ will not take a version back.
118
+
119
+ The same script refuses a release tag that would move npm's `latest`
120
+ backwards — a backported `v1.0.1` published while `latest` is `2.0.0` would
121
+ make `npm i @fleetless/contracts`, the command the README gives outsiders,
122
+ install a package a major version behind.
123
+ 2. Commit, push, and let the `verify` job go green on the branch.
124
+ 3. Tag `vX.Y.Z` (or `vX.Y.Z-beta.N` for a pre-release, which publishes to the
125
+ `next` dist-tag) and push the tag. The tag pipeline runs `verify` again and
126
+ then `publish`.
127
+
128
+ `publish` needs an `NPM_TOKEN` variable, protected and masked. Protected means
129
+ an unprotected ref receives an empty value rather than a missing one, so the
130
+ tag pattern must be protected too; the job names that case before it can fail
131
+ on it obliquely.
132
+
133
+ **If the publish job goes red after `npm publish` has run, do not press
134
+ retry.** npm refuses to republish a version, so a retry fails with a 403 that
135
+ reads like a broken pipeline rather than like a release that already happened.
136
+ Check `npm view @fleetless/contracts@<version>` first.
package/README.md CHANGED
@@ -49,15 +49,28 @@ import openapi from '@fleetless/contracts/artifacts/openapi.json' with { type: '
49
49
 
50
50
  | Path | What it is |
51
51
  |---|---|
52
- | `artifacts/openapi.json` | The OpenAPI description of the REST API. |
53
- | `artifacts/routes.json` | Every route with its request and response schema names. |
54
- | `artifacts/schema/*.json` | One JSON Schema per wire shape, named after the shape. |
55
- | `artifacts/schema-outgoing/*.json` | The messages the cloud sends to a bridge. |
52
+ | `artifacts/openapi.json` | The OpenAPI 3.1 description of the REST API, generated from the route manifest and the schemas. |
53
+ | `artifacts/routes.json` | Every route with its request and response schema names, and the notes that explain each one. |
54
+ | `artifacts/schema/*.json` | One JSON Schema per wire shape, named after the shape, rendered as **a receiver validates an incoming document**. |
55
+ | `artifacts/schema-outgoing/*.json` | Twelve of those same shapes rendered again as **a sender must produce them** — the frames a robot's bridge sends to the cloud. |
56
56
 
57
- The robot-side bridge validates against `artifacts/schema/` with Python's
58
- `jsonschema`, which is why the artifacts are generated and committed rather
59
- than produced at install time: a consumer in another language needs them
60
- without running a TypeScript build.
57
+ The two schema directories are the same shapes in zod's two rendering modes,
58
+ and the difference matters if you are writing a bridge.
59
+
60
+ `artifacts/schema/` is *input* mode: what a receiver accepts. Fleetless
61
+ receivers strip unknown keys rather than refusing them, so these documents omit
62
+ `additionalProperties: false`, and a field with a default is not marked
63
+ required.
64
+
65
+ `artifacts/schema-outgoing/` is *output* mode, for the twelve frames a bridge
66
+ **sends**. There a relaxed schema points the wrong way: a misspelled key in an
67
+ outgoing frame would pass an input-mode check and then be silently dropped by
68
+ the server. Validate what you send against `schema-outgoing/`, and what you
69
+ receive against `schema/`.
70
+
71
+ The artifacts are generated and committed rather than produced at install time
72
+ because a consumer in another language needs them without running a TypeScript
73
+ build.
61
74
 
62
75
  ## Versioning
63
76
 
@@ -69,6 +82,13 @@ client may ignore, or a description is a minor or a patch.
69
82
  Pin an exact version. These shapes describe a running server, and "compatible
70
83
  with the server you are talking to" is not something a range can express.
71
84
 
85
+ **The one dependency, `zod`, is deliberately a range and not a pin.** zod is a
86
+ peer in everything but name: a consumer that imports both this package and zod
87
+ must get one copy, or a schema from here fails an `instanceof` check against
88
+ their own. An exact pin here forces a second copy on anyone whose lockfile
89
+ resolves a different patch. The range floor is the version these schemas are
90
+ built, tested and exported against, and a zod major is a major here too.
91
+
72
92
  ## Documentation
73
93
 
74
94
  The API reference at
@@ -76,12 +96,28 @@ The API reference at
76
96
  generated from these artifacts and is the place to read what a field means.
77
97
  This package is the place to read what it *is*.
78
98
 
79
- ## Contributing and security
99
+ ## Reporting a security issue
100
+
101
+ Email **security@fleetless.dev**. Please do not open a public issue for a
102
+ security report.
103
+
104
+ **A wrong schema is a security report.** If a schema here accepts a value that
105
+ should never reach a server — an identifier that escapes its own grammar, a
106
+ missing bound, a union that admits a shape the API does not expect — that is a
107
+ vulnerability in this package, even though nothing in it executes anything. So
108
+ is a generated artifact that disagrees with the zod schema it came from, because
109
+ consumers that are not TypeScript validate against the artifact and never see
110
+ the zod.
111
+
112
+ `SECURITY.md` ships inside the published package and has the full policy: what
113
+ is in scope, what is not, and the response times we hold ourselves to.
114
+
115
+ ## Contributing
80
116
 
81
- [CONTRIBUTING.md](CONTRIBUTING.md) has the setup, the checks and the pull
82
- request process. [SECURITY.md](SECURITY.md) has the reporting address and what
83
- counts as a security report here — a schema that lets a bad value through is
84
- one, even though nothing in this package executes it.
117
+ `CONTRIBUTING.md` ships inside the published package and has the setup, the
118
+ checks and the pull request process. In short: `pnpm install`, then `pnpm
119
+ typecheck && pnpm test`; every schema change regenerates `artifacts/` with
120
+ `pnpm artifacts` and commits it in the same commit.
85
121
 
86
122
  ## Licence
87
123
 
package/SECURITY.md ADDED
@@ -0,0 +1,55 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Email **security@fleetless.dev**. Please do not open a public GitHub issue for
6
+ a security report.
7
+
8
+ Include what you found, the schema or artifact it concerns, and — if you have
9
+ one — a value that the schema accepts and should not, or rejects and should
10
+ not. A minimal reproduction against a published version of this package is the
11
+ most useful thing you can send.
12
+
13
+ We acknowledge every report within **3 working days** and follow up with
14
+ either a fix or a written plan within **30 days**. If a report leads to a
15
+ released fix, we credit you by name unless you ask us not to.
16
+
17
+ ## What is in scope
18
+
19
+ This repository is a schema library. It defines the wire shapes of the
20
+ Fleetless API and the bridge–cloud protocol as zod schemas, and exports them
21
+ as JSON Schema and OpenAPI documents under `artifacts/`.
22
+
23
+ **A wrong schema is a security report.** If a schema accepts a value that
24
+ should never reach a server — an identifier that escapes its own grammar, a
25
+ bound that is missing, a union that admits a shape the API does not expect, a
26
+ field that a validator lets through unchecked — that is a vulnerability in
27
+ this package, even though nothing here executes it. The same is true of a
28
+ generated artifact that disagrees with the zod schema it came from, because
29
+ non-TypeScript consumers validate against the artifact and never see the zod.
30
+
31
+ So, in scope:
32
+
33
+ - the zod schemas in `src/`;
34
+ - the JSON Schema and OpenAPI artifacts in `artifacts/`, including any
35
+ disagreement between an artifact and its source schema;
36
+ - the build and export tooling in `scripts/`;
37
+ - the published npm package `@fleetless/contracts` and its contents.
38
+
39
+ ## What is not in scope
40
+
41
+ **The Fleetless cloud is not in this repository.** A server that fails to
42
+ enforce a schema, an authentication or authorisation flaw, a rate limit, a
43
+ data leak from an API endpoint — none of those live here, and none of them can
44
+ be fixed by a change to this package. Report them to the same address; say
45
+ which service you were looking at, and we will route it. What we cannot do is
46
+ treat this repository's issue tracker as the place where they are tracked.
47
+
48
+ Also out of scope here: the Fleetless console, the bridge, the SDK, the
49
+ documentation site, and any deployment of Fleetless operated by someone else.
50
+
51
+ ## Supported versions
52
+
53
+ The latest published minor of `@fleetless/contracts` receives fixes. Older
54
+ minors do not; a security fix is released as a new patch on the current minor,
55
+ and consumers pin exact versions, so upgrading is the remedy.
@@ -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 W8's second cloud instance is where a per-process session map would break — 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 difference was measured: 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 the failure this project keeps paying for. 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 difference was measured: 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 the failure this project keeps paying for. 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",
@@ -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 W8's second cloud instance is where a per-process session map would break — 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 difference was measured: 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 the failure this project keeps paying for. 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 difference was measured: 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 the failure this project keeps paying for. 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",
package/dist/alerts.d.ts CHANGED
@@ -1,28 +1,24 @@
1
+ // SPDX-License-Identifier: Apache-2.0
1
2
  import { z } from 'zod';
2
3
  /**
3
- * Datapoint alerts and per-datapoint chart display config (spec
4
- * `2026-08-28-alerts-and-datapoint-modal-design`, D1/D2/D5).
4
+ /**
5
+ * Datapoint alerts and per-datapoint chart display config.
5
6
  *
6
- * An alert is a **state machine** (`ok ⇄ firing`), not a fire-once event —
7
- * the definition (this file's request/entity shapes) and the runtime state
8
- * (`state`, `state_since`, `last_value`) share one row, evaluated by the
9
- * cloud at ingest.
7
+ * An alert is a **state machine** (`ok ⇄ firing`), not a fire-once event. The
8
+ * definition and the runtime state (`state`, `state_since`, `last_value`) are
9
+ * read together, and the cloud evaluates the alert at ingest.
10
10
  *
11
- * **Both tables moved into `robotConfigDoc` in FL-002.** The alert definition
12
- * is `config.ts`'s `datapointAlert`, nested under the datapoint it watches;
13
- * the chart bounds are `datapointChart`. They therefore take effect on
14
- * publish rather than immediately, and in exchange every change to them is
15
- * versioned, comparable and revertible. The runtime state stays wherever the
16
- * definition goes: it belongs in the database and has no business in a
17
- * versioned document.
11
+ * **The definitions live in the configuration document.** The alert definition
12
+ * is `config.ts`'s `datapointAlert`, nested under the datapoint it watches; the
13
+ * chart bounds are `datapointChart`. They therefore take effect on publish
14
+ * rather than immediately, and in exchange every change to them is versioned,
15
+ * comparable and revertible. The runtime state stays in the database: it has no
16
+ * business in a versioned document.
18
17
  *
19
- * **What is left here is the read surface**, which FL-002 wave 4 kept rather
20
- * than deleted: `GET /api/robots/:id/alerts` and `GET /api/org/alerts` still
21
- * answer with the definition joined to its state, and the shapes below are
22
- * what they answer with. What wave 4 did remove is the mail path — the fields
23
- * `cooldown_minutes`, `recipients` and `notify_on_resolve`, and the two
24
- * bounds that guarded them. No alert can send mail, so nothing here describes
25
- * one.
18
+ * **What is here is the read surface.** `GET /api/robots/:id/alerts` and
19
+ * `GET /api/org/alerts` answer with the definition joined to its state, and the
20
+ * shapes below are what they answer with. No alert sends mail, so nothing here
21
+ * describes one.
26
22
  */
27
23
  /**
28
24
  * `above`/`below` compare the numeric sample value (already scale/offset
@@ -104,10 +100,9 @@ export type AlertState = z.infer<typeof alertState>;
104
100
  * document and the runtime state out of `datapoint_alert_state`, and the
105
101
  * cloud joins the two per request (`routes/alerts.ts`'s `toWire`).
106
102
  *
107
- * **It carried three mail settings — `cooldown_minutes`, `recipients` and
108
- * `notify_on_resolve` — and FL-002 wave 4 removed them with the mail path.**
109
- * The format has no mail fields, so no alert could be configured to send one;
110
- * the three had nothing behind them well before they were deleted.
103
+ * **There are no mail settings here.** The configuration format has no mail
104
+ * fields, so no alert can be configured to send one, and a shape describing
105
+ * recipients would describe a delivery path that does not exist.
111
106
  */
112
107
  export declare const datapointAlertRow: z.ZodObject<{
113
108
  id: z.ZodUUID;
package/dist/alerts.js CHANGED
@@ -2,29 +2,24 @@
2
2
  import { z } from 'zod';
3
3
  import { slug } from './common.js';
4
4
  /**
5
- * Datapoint alerts and per-datapoint chart display config (spec
6
- * `2026-08-28-alerts-and-datapoint-modal-design`, D1/D2/D5).
5
+ /**
6
+ * Datapoint alerts and per-datapoint chart display config.
7
7
  *
8
- * An alert is a **state machine** (`ok ⇄ firing`), not a fire-once event —
9
- * the definition (this file's request/entity shapes) and the runtime state
10
- * (`state`, `state_since`, `last_value`) share one row, evaluated by the
11
- * cloud at ingest.
8
+ * An alert is a **state machine** (`ok ⇄ firing`), not a fire-once event. The
9
+ * definition and the runtime state (`state`, `state_since`, `last_value`) are
10
+ * read together, and the cloud evaluates the alert at ingest.
12
11
  *
13
- * **Both tables moved into `robotConfigDoc` in FL-002.** The alert definition
14
- * is `config.ts`'s `datapointAlert`, nested under the datapoint it watches;
15
- * the chart bounds are `datapointChart`. They therefore take effect on
16
- * publish rather than immediately, and in exchange every change to them is
17
- * versioned, comparable and revertible. The runtime state stays wherever the
18
- * definition goes: it belongs in the database and has no business in a
19
- * versioned document.
12
+ * **The definitions live in the configuration document.** The alert definition
13
+ * is `config.ts`'s `datapointAlert`, nested under the datapoint it watches; the
14
+ * chart bounds are `datapointChart`. They therefore take effect on publish
15
+ * rather than immediately, and in exchange every change to them is versioned,
16
+ * comparable and revertible. The runtime state stays in the database: it has no
17
+ * business in a versioned document.
20
18
  *
21
- * **What is left here is the read surface**, which FL-002 wave 4 kept rather
22
- * than deleted: `GET /api/robots/:id/alerts` and `GET /api/org/alerts` still
23
- * answer with the definition joined to its state, and the shapes below are
24
- * what they answer with. What wave 4 did remove is the mail path — the fields
25
- * `cooldown_minutes`, `recipients` and `notify_on_resolve`, and the two
26
- * bounds that guarded them. No alert can send mail, so nothing here describes
27
- * one.
19
+ * **What is here is the read surface.** `GET /api/robots/:id/alerts` and
20
+ * `GET /api/org/alerts` answer with the definition joined to its state, and the
21
+ * shapes below are what they answer with. No alert sends mail, so nothing here
22
+ * describes one.
28
23
  */
29
24
  /**
30
25
  * `above`/`below` compare the numeric sample value (already scale/offset
@@ -102,10 +97,9 @@ export const alertState = z.enum(['ok', 'firing']);
102
97
  * document and the runtime state out of `datapoint_alert_state`, and the
103
98
  * cloud joins the two per request (`routes/alerts.ts`'s `toWire`).
104
99
  *
105
- * **It carried three mail settings — `cooldown_minutes`, `recipients` and
106
- * `notify_on_resolve` — and FL-002 wave 4 removed them with the mail path.**
107
- * The format has no mail fields, so no alert could be configured to send one;
108
- * the three had nothing behind them well before they were deleted.
100
+ * **There are no mail settings here.** The configuration format has no mail
101
+ * fields, so no alert can be configured to send one, and a shape describing
102
+ * recipients would describe a delivery path that does not exist.
109
103
  */
110
104
  export const datapointAlertRow = z.object({
111
105
  id: z.uuid(),
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: Apache-2.0
1
2
  import { z } from 'zod';
2
3
  /**
3
4
  * **App users: the per-app identity space** (spec `2026-09-05-app-user-auth`,
@@ -47,12 +48,12 @@ export declare const providerSlug: z.ZodString;
47
48
  /**
48
49
  * **The three states an app user can be in, and the order is the lifecycle.**
49
50
  *
50
- * - `pending_verification` — self-registered, mail sent, cannot log in yet
51
- * (D6). Without this state the domain whitelist would prove nothing: anybody
52
- * could claim any address at an allowed domain.
51
+ * - `pending_verification` — self-registered, mail sent, cannot log in yet.
52
+ * Without this state a domain allow-list would prove nothing: anybody could
53
+ * claim any address at an allowed domain.
53
54
  * - `active` — may log in.
54
55
  * - `blocked` — may not, and every refusal is the same `invalid_credentials`
55
- * a wrong password gets (§4). A block that announced itself would be an
56
+ * a wrong password gets. A block that announced itself would be an
56
57
  * account-enumeration oracle with an extra step.
57
58
  *
58
59
  * `pending_verification` is reached exactly once and left only by spending the
@@ -442,7 +443,7 @@ export type MailTemplateKind = z.infer<typeof mailTemplateKind>;
442
443
  */
443
444
  export declare const MAIL_TEMPLATE_VARIABLES: readonly ["app.name", "org.name", "user.email", "user.display_name", "role.name", "link", "expires_in_hours"];
444
445
  /**
445
- * **The Fleetless default text for the three app mails** (spec D5, §6).
446
+ * **The Fleetless default text for the three app mails.**
446
447
  *
447
448
  * It lives here rather than in the cloud because two products send the same
448
449
  * words: the cloud renders these when an app has no template of its own, and
@@ -476,7 +477,7 @@ export declare const MAIL_TEMPLATE_VARIABLES: readonly ["app.name", "org.name",
476
477
  * that one is written by an authenticated developer about somebody they
477
478
  * invited.
478
479
  *
479
- * **`expires_in_hours` is the only lifetime variable the spec offers**, and
480
+ * **`expires_in_hours` is the only lifetime variable a template gets**, and
480
481
  * the three values are 1, 24 and 168. "The next 168 hours" is not how a person
481
482
  * says a week, so each default converts: 48 and up reads in days, exactly one
482
483
  * reads "1 hour", everything else reads in hours. The conversion is in the