dify/knowledge-fs/docs/public-error-contract.md

105 lines
5.8 KiB
Markdown

# KnowledgeFS public error contract
KnowledgeFS exposes a stable, additive failure contract to Dify. Internal exception messages remain
in server diagnostics and must not cross the product boundary because provider responses can contain
credentials, headers, signed URLs, database details, or other deployment data.
## HTTP contract
Dify requests version 2 by sending:
```http
X-KnowledgeFS-Error-Contract: 2
```
An unsuccessful JSON response retains the legacy `code` and `error` properties and adds `failure`:
```json
{
"code": "MODEL_SELECTION_NOT_FOUND",
"error": "The selected model is no longer available in this workspace. Select another model before retrying.",
"failure": {
"action": "configure_model",
"category": "configuration",
"code": "MODEL_SELECTION_NOT_FOUND",
"message": "The selected model is no longer available in this workspace. Select another model before retrying.",
"retryPolicy": "after_configuration",
"stage": "model_preflight",
"traceId": "trace-reference"
}
}
```
Direct clients that do not request version 2 continue to receive the legacy response shape during the
compatibility window. Dify always requests version 2 and validates the response before forwarding it.
Malformed structured failures are discarded and replaced by the existing generic BFF error.
The BFF also replaces every upstream `message` with category-owned copy; a registered code alone is
not sufficient to make arbitrary upstream text trustworthy.
## Failure fields
| Field | Required | Meaning |
| --- | --- | --- |
| `code` | yes | Registered, stable machine code. Unknown diagnostic codes become a registered family fallback. |
| `category` | yes | One of `authorization`, `canceled`, `configuration`, `conflict`, `dependency`, `internal`, `not_found`, `rate_limit`, `timeout`, or `validation`. |
| `message` | yes | Safe server fallback. Web clients should prefer localized copy selected from category and action. |
| `retryPolicy` | yes | `automatic`, `manual`, `after_configuration`, or `never`. |
| `action` | no | Suggested recovery: `configure_model`, `configure_parser`, `configure_source`, `contact_admin`, `reupload`, or `retry`. |
| `parameters` | no | At most eight allowlisted scalar hints. Arbitrary keys and secret-bearing values are rejected. |
| `stage` | no | Identifier-only public processing stage matching `^[a-z][a-z0-9_.-]{0,127}$`. |
| `traceId` | no | Identifier-only support reference matching `^[A-Za-z0-9._:-]{1,128}$`; it is not an authorization token. |
## Retry semantics
- `automatic`: the failure is transient. Workers may retry, and a user may retry a terminal task.
- `manual`: automatic attempts are exhausted or inappropriate, but an explicit retry is safe.
- `after_configuration`: retry is disabled until the indicated configuration is changed.
- `never`: retrying the same operation cannot succeed; the user must change the input or permissions.
The UI must derive its Retry button from `retryPolicy`, not merely from `state == failed`.
## Background tasks and streams
Document, source, bulk, semantic-enrichment, and research task responses expose the same `failure`
shape. Legacy `errorCode` and `errorMessage` remain during migration; `errorMessage` is always replaced
with the catalog fallback before it is returned. A terminal document-task SSE event contains the same
failure object as polling, so changing transport cannot change the user-visible diagnosis. Research
task polling and progress SSE follow the same rule. Query-stream `answer.error` events also include a
structured `failure`; no raw model, provider, or runtime exception is written into the stream.
Nonterminal research progress events deliberately omit worker retry exception text; only bounded
stage and retry metadata are public.
Some asynchronous failures are returned inside successful HTTP responses. Failed source workflows,
source imports, source credential tests, document compilation jobs, staged commits, and knowledge
space status diagnostics therefore use the same `failure` contract and sanitize their legacy fields
before serialization. A `200` status never makes a stored provider or parser message public.
The public `code` field is a closed set generated from the central KnowledgeFS catalog. Dify rejects
unknown or malformed structured failures instead of forwarding their message, and Web has an
exhaustive mapping for every generated code. Adding a new public code therefore requires updating the
catalog, generated contracts, and product presentation together.
## Security and observability rules
- Only messages declared in the central catalog, or an explicitly reviewed `publicMessage`, may be
returned or persisted as a product-facing message.
- Unknown exceptions and unknown codes become `KNOWLEDGE_FS_INTERNAL_ERROR` or a registered family
fallback. The original exception stays in structured server logs.
- Public parameters use an explicit key allowlist. File contents, filenames, URLs, credentials,
headers, provider payloads, SQL text, stack traces, and exception messages are prohibited.
- `stage` and `traceId` accept identifiers only. Secret-bearing prose is dropped even when supplied
in a structurally valid failure object.
- Dify logs only the upstream status, registered code, category, action, operation ID, and trace ID.
- Web renders localized category/action copy and may display `traceId` for internal failures. It never
renders the legacy raw message.
## Adding a failure
1. Add a stable descriptor to `packages/api/src/knowledge-fs-errors.ts`.
2. Choose the HTTP status, category, retry policy, and recovery action deliberately.
3. Map the internal exception to the code without copying its diagnostic message.
4. Add tests for normalization, polling/SSE parity, retry behavior, BFF validation, and UI fallback as
applicable.
5. Regenerate the KnowledgeFS OpenAPI and Dify contract artifacts and update the contract lock after
reviewing the diff.