Skip to content

Commit f70647e

Browse files
committed
docs: audit and tighten error reference pages
Simplify every DF error page (trim verbose Cause/Fix/Example, positive framing, normalize inline throw markers), add missing pages for DF0035 and DF0072, and rebuild the error index to list all codes with severity and titles across the Devframe and Hub ranges.
1 parent 26544e6 commit f70647e

50 files changed

Lines changed: 230 additions & 266 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/content/6.errors/DF0014.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ description: 'RPC function "{name}" has an invalid agent field — description m
99
1010
## Cause
1111

12-
An RPC function was defined with an `agent` field (opting it in for exposure to agents via the MCP adapter), but the required `description` property is missing or empty.
13-
14-
Agents rely on the description to decide when to invoke a tool. Empty or placeholder descriptions would produce unusable agent surface.
12+
An RPC function opts into agent exposure with an `agent` field, but its required `description` is missing or empty. Agents rely on the description to decide when to invoke a tool.
1513

1614
## Example
1715

@@ -28,7 +26,7 @@ defineRpcFunction({
2826

2927
## Fix
3028

31-
Provide a non-empty `description` (~1–3 sentences) explaining what the tool does and when agents should invoke it:
29+
Provide a non-empty `description` (~1–3 sentences) explaining what the tool does and when agents should invoke it, or remove the `agent` field to keep it RPC-only.
3230

3331
```ts
3432
defineRpcFunction({
@@ -41,8 +39,6 @@ defineRpcFunction({
4139
})
4240
```
4341

44-
If you didn't intend for this function to be agent-exposed, remove the `agent` field entirely (default-deny).
45-
4642
## Source
4743

4844
- [`packages/devframe/src/node/host-agent.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/host-agent.ts) — agent registration throws `DF0014` when a tool's `agent.description` is missing or empty.

docs/content/6.errors/DF0017.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ description: 'Failed to start MCP server ({transport}): {reason}'
1111

1212
The MCP server failed while initializing. Common reasons:
1313

14-
- `@modelcontextprotocol/server` is not installed. This is a peer dependency — add it to your devtool's dependencies.
15-
- The stdio transport threw during `connect()` (e.g. stdin/stdout is not available).
16-
- The route-based MCP server (`cli.mcp`) could not load its transport module — usually the missing SDK peer dependency.
14+
- The `@modelcontextprotocol/server` peer dependency is missing (the stdio and route-based transports both need it).
15+
- The stdio transport threw during `connect()` (e.g. stdin/stdout unavailable).
1716

1817
## Fix
1918

20-
- **Missing SDK**: `pnpm add @modelcontextprotocol/server` (or npm/yarn equivalent) in the package that imports `devframe/adapters/mcp` or enables `cli.mcp`.
21-
- **Transport init failure**: check the underlying error (attached as `cause`) for specifics.
19+
- **Missing SDK**: `pnpm add @modelcontextprotocol/server` in the package that imports `devframe/adapters/mcp` or enables `cli.mcp`.
20+
- **Transport failure**: inspect the underlying error attached as `cause`.
2221

2322
## Source
2423

docs/content/6.errors/DF0019.md

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,21 @@ description: 'RPC function "{name}" has agent set but jsonSerializable is not tr
99
1010
## Cause
1111

12-
The `agent` field exposes an RPC function as an MCP tool. MCP only consumes JSON-shaped data. Functions whose payloads can include `Map`, `Set`, `Date`, `BigInt`, circular references, or class instances cannot be safely advertised to agents.
13-
14-
A registered function is rejected when `agent` is present and `jsonSerializable` is not explicitly `true`.
12+
The `agent` field exposes an RPC function as an MCP tool, and MCP only consumes JSON-shaped data. A function with `agent` set is rejected unless it also declares `jsonSerializable: true`.
1513

1614
## Example
1715

1816
```ts
1917
defineRpcFunction({
2018
name: 'my-plugin:summary',
2119
agent: { description: 'Returns a summary' },
22-
// missing `jsonSerializable: true` → registration throws DF0019
23-
handler: () => ({ items: [1, 2, 3] }),
20+
handler: () => ({ items: [1, 2, 3] }), // ✗ throws DF0019 — missing jsonSerializable: true
2421
})
2522
```
2623

2724
## Fix
2825

29-
Either declare the payload as JSON-safe:
26+
Set `jsonSerializable: true` if the payload is JSON-safe, or remove `agent` to keep it RPC-only.
3027

3128
```ts
3229
defineRpcFunction({
@@ -37,15 +34,6 @@ defineRpcFunction({
3734
})
3835
```
3936

40-
Or remove `agent` to keep the function as an internal RPC (no agent exposure):
41-
42-
```ts
43-
defineRpcFunction({
44-
name: 'my-plugin:summary',
45-
handler: () => new Map([['a', 1]]),
46-
})
47-
```
48-
4937
## Source
5038

5139
- [`packages/devframe/src/rpc/collector.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/rpc/collector.ts)`RpcFunctionsCollectorBase.register()` throws `DF0019` when a definition has `agent` set but is not declared `jsonSerializable: true`.

docs/content/6.errors/DF0020.md

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@ description: 'RPC function "{name}" declares jsonSerializable: true but the valu
99
1010
## Cause
1111

12-
The function is declared `jsonSerializable: true`, which means its args and return value are encoded with strict `JSON.stringify` (both on the wire and in build dumps). The strict serializer rejects any value that JSON cannot round-trip losslessly:
12+
A `jsonSerializable: true` function encodes its args and return value with strict `JSON.stringify` (on the wire and in build dumps). The serializer throws at the offending value rather than emit a corrupt payload when it hits anything JSON cannot round-trip losslessly:
1313

1414
- `Map`, `Set`, `WeakMap`, `WeakSet`
15-
- `Date` (silently coerced to ISO string by JSON)
16-
- `BigInt`
15+
- `Date` (JSON coerces it to an ISO string)
16+
- `BigInt`, `Symbol`, `Function`
1717
- circular references
1818
- non-plain class instances
1919
- `undefined` leaves
20-
- `Symbol`
21-
- `Function`
22-
23-
When the strict serializer encounters one of these, it throws synchronously at the offending call rather than producing a corrupt payload.
2420

2521
## Example
2622

@@ -29,26 +25,14 @@ defineRpcFunction({
2925
name: 'my-plugin:graph',
3026
jsonSerializable: true,
3127
handler: () => ({
32-
nodes: new Map([['a', 1]]), // throws DF0020 with type=Map, path="nodes"
28+
nodes: new Map([['a', 1]]), // throws DF0020 type=Map, path="nodes"
3329
}),
3430
})
3531
```
3632

3733
## Fix
3834

39-
Either drop `jsonSerializable: true` so the function uses `structured-clone-es` (round-trips `Map`, `Set`, etc.):
40-
41-
```ts
42-
defineRpcFunction({
43-
name: 'my-plugin:graph',
44-
// jsonSerializable: false (default) — Map/Set survive the wire and the dump
45-
handler: () => ({
46-
nodes: new Map([['a', 1]]),
47-
}),
48-
})
49-
```
50-
51-
Or convert the payload to a JSON-safe shape (e.g. an array of entries, an ISO string, a plain object) before returning. Note: removing `jsonSerializable: true` also disables `agent` exposure; if you need MCP, you must use a JSON-safe shape.
35+
Drop `jsonSerializable: true` to fall back to `structured-clone-es` (round-trips `Map`, `Set`, etc.), or convert the payload to a JSON-safe shape before returning. Keeping `agent` exposure requires the JSON-safe shape.
5236

5337
## Source
5438

docs/content/6.errors/DF0029.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@ description: 'Stream "{channel}#{id}" dropped {dropped} chunk(s) after exceeding
99
1010
## Cause
1111

12-
A streaming subscriber's queue grew past its `highWaterMark` because the consumer is slower than the producer. The oldest chunks were dropped to keep memory bounded.
13-
14-
This is a soft warning — the stream keeps running and remaining chunks still flow.
12+
The consumer is slower than the producer, so the subscriber's queue grew past its `highWaterMark` and the oldest chunks were dropped to keep memory bounded. The stream keeps running and remaining chunks still flow.
1513

1614
## Fix
1715

1816
- Raise `highWaterMark` on `rpc.streaming.subscribe(channel, id, { highWaterMark })` if the consumer can occasionally catch up.
19-
- Slow the producer so it doesn't outpace the wire (e.g. throttle, debounce, or batch chunks server-side).
20-
- Switch to `sharedState` if you only need the latest value rather than every intermediate chunk.
17+
- Slow the producer throttle, debounce, or batch chunks server-side.
18+
- Use `sharedState` if you only need the latest value rather than every chunk.
2119

2220
## Source
2321

docs/content/6.errors/DF0030.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ description: 'Stream "{channel}#{id}" is unknown — no producer has called chan
99
1010
## Cause
1111

12-
A client subscribed to a stream id that the server-side channel doesn't know about. Either the producer never started a stream with that id, the producer already ended it and `replayWindow` is `0`, or the client passed the wrong id.
12+
A client subscribed to a stream id no server-side producer has started. Either the producer never called `channel.start({ id })`, it already ended the stream and `replayWindow` is `0`, or the client passed the wrong id.
1313

1414
## Fix
1515

16-
- Make sure the action that returns the stream id runs **before** the client subscribes — typically by awaiting `rpc.call('your-action')` and using the returned id.
17-
- Bump `replayWindow` on `ctx.rpc.streaming.create(name, { replayWindow })` if you need clients to resume after the producer has finished but kept the buffer warm.
18-
- Check the id is propagated correctly across boundaries (action return value → component prop → subscribe call).
16+
- Run the producer before clients subscribe — typically await `rpc.call('your-action')` and use the returned id.
17+
- Bump `replayWindow` on `ctx.rpc.streaming.create(name, { replayWindow })` to let clients resume after the producer finishes.
18+
- Verify the id propagates correctly (action return → component prop → subscribe call).
1919

2020
## Source
2121

docs/content/6.errors/DF0033.md

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,12 @@ description: 'Failed to start dev RPC bridge for "{id}": {reason}'
99
1010
## Cause
1111

12-
`devframeViteBridge()` (from `@devframes/vite`) could not bring up the bridge dev server that pairs a host-served SPA (Vite, Nuxt, Astro, etc.) with devframe's RPC backend. Common reasons:
13-
14-
- The preferred port is in use and no fallback range was configured.
15-
- Calling `def.setup(ctx)` threw — the devframe's own setup logic surfaced an error.
16-
- A required peer (e.g. `get-port-please` or `h3`) is missing or mismatched.
17-
18-
This is a soft warning — the surrounding Vite dev server keeps running, but the host-served SPA will fail its `__connection.json` lookup until the bridge starts.
12+
`devframeViteBridge()` (from `@devframes/vite`) could not bring up the bridge dev server that pairs a host-served SPA with devframe's RPC backend — usually because the preferred port is taken with no fallback range, or `def.setup(ctx)` threw. The surrounding Vite dev server keeps running, but the SPA's `__connection.json` lookup fails until the bridge starts.
1913

2014
## Fix
2115

22-
- Pin a port via `cli.port` / `cli.portRange` on the devframe definition, or via `port` on `devframeViteBridge`.
23-
- Inspect the `reason` (or the attached `cause`) for the underlying error — fix the setup function or free the port.
24-
- For Nuxt: pass `devMiddleware: { port: <free-port> }` to the `@devframes/nuxt` module.
16+
- Pin a port via `cli.port` / `cli.portRange` on the definition, or `port` on `devframeViteBridge`.
17+
- Inspect `reason` (or the attached `cause`) to fix the setup function or free the port.
2518

2619
## Source
2720

docs/content/6.errors/DF0035.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
title: 'DF0035: Storage File Persist Failed'
3+
description: 'Failed to persist storage file: {filepath}'
4+
---
5+
6+
## Message
7+
8+
> Failed to persist storage file: `{filepath}`
9+
10+
## Cause
11+
12+
A shared-state store's debounced write to disk failed — the directory could not be created, or the temp-file write / atomic rename threw. Usually the storage directory is not writable or the disk is full.
13+
14+
## Fix
15+
16+
Check that the storage directory is writable and has free space.
17+
18+
## Source
19+
20+
- [`packages/devframe/src/node/storage.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/storage.ts) — the debounced `updated` handler reports this when writing the temp file or renaming it into place fails.

docs/content/6.errors/DF0036.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,23 @@ description: 'RPC call to "{name}" was rejected: the caller is not authorized.'
99
1010
## Cause
1111

12-
The RPC server was configured with an `authorize` gate (either directly, or via a [`DevframeAuthHandler`](/guide/security) passed as `auth`) and the calling session hasn't satisfied it — the call is neither to an `anonymous:`-prefixed method (see `isAnonymousRpcMethod`) nor made by a trusted session.
12+
An `authorize` gate (set directly or via an [`auth` handler](/guide/security)) rejected the call: the session is untrusted and the method is not `anonymous:`-prefixed (see `isAnonymousRpcMethod`).
1313

1414
## Example
1515

1616
```ts
17-
import { createDevServer } from 'devframe/adapters/dev'
18-
19-
// `auth` defaults to devframe's interactive gate; `initDevframe` / `initHub`
20-
// gate hosted instances the same way through their own `auth` option.
21-
await createDevServer(def)
22-
23-
// A browser that hasn't completed the handshake yet can still reach the
24-
// handshake methods themselves…
17+
// Handshake methods stay reachable before auth completes…
2518
await client.call('anonymous:devframe:auth', { authToken: '', ua, origin })
2619

27-
// …but any other method throws DF0036 until the handshake succeeds.
20+
// …but trusted methods throw until the handshake succeeds.
2821
await client.call('some-plugin:do-something') // ✗ throws DF0036
2922
```
3023

3124
## Fix
3225

33-
- Complete the auth handshake — call `anonymous:devframe:auth` with a previously-issued token, or `anonymous:devframe:auth:exchange` with a one-time code — before calling a trusted method.
34-
- Connect with a static/pre-shared token (`createInteractiveAuth`'s `clientAuthTokens` option) for CI or shared-machine setups that should skip the interactive prompt.
35-
- If you supplied a custom `authorize` function, verify it allows the method you expect — it receives the raw method name and the session's `meta` (`isTrusted`, `clientAuthToken`, …).
26+
- Complete the auth handshake before calling a trusted method.
27+
- Connect with a static/pre-shared token (`createInteractiveAuth`'s `clientAuthTokens`) for CI or shared machines.
28+
- With a custom `authorize`, confirm it allows the method — it receives the method name and the session's `meta`.
3629

3730
## Source
3831

docs/content/6.errors/DF0038.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,21 @@ description: 'JSON-render view "{id}" received invalid props on element "{key}":
99
1010
## Cause
1111

12-
`@devframes/json-render` validates every element's props against the base catalog's per-component Zod schema at spec ingress (`createJsonRenderView` / `view.update`). Upstream `@json-render/core` only checks component *names*, so this per-component prop check is the one validation Devframes adds. An element whose props don't match its component's schema is rejected here rather than failing silently at render.
12+
`@devframes/json-render` validates every element's props against the base catalog's per-component schema at spec ingress (`createJsonRenderView` / `view.update`). An element whose props don't match its component's schema is rejected here.
1313

1414
## Example
1515

1616
```ts
17-
// ✗ Bad — `variant` is not one of the Button variants
1817
createJsonRenderView(ctx, {
1918
id: 'toolbar',
19+
// ✗ throws DF0038 — `variant` is not a Button variant
2020
spec: { root: 'a', elements: { a: { type: 'Button', props: { variant: 'nope' }, children: [] } } },
2121
})
22-
23-
// ✓ Good
24-
createJsonRenderView(ctx, {
25-
id: 'toolbar',
26-
spec: { root: 'a', elements: { a: { type: 'Button', props: { variant: 'primary', label: 'Save' }, children: [] } } },
27-
})
2822
```
2923

3024
## Fix
3125

32-
Match the element props to the base catalog's prop schema for that component. Dynamic `$state` / `$bindState` expressions are accepted wherever a scalar prop is expected, so a valid binding never triggers this.
26+
Match the element props to the base catalog's prop schema for that component. Dynamic `$state` / `$bindState` expressions are accepted wherever a scalar prop is expected.
3327

3428
## Source
3529

0 commit comments

Comments
 (0)