Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6be10e7
Add HTTP error representation dispatch
ayden94 Aug 3, 2026
fe332cf
Exclude HTTP test fixtures from package builds
ayden94 Aug 3, 2026
641a011
Wire error representations through runtime bootstrap
ayden94 Aug 3, 2026
f688a6b
Add buffered React error document adapter
ayden94 Aug 3, 2026
850387c
Add error representation portability coverage
ayden94 Aug 3, 2026
5b678c6
Cover Express error representation portability
ayden94 Aug 3, 2026
b51fa06
Cover Fastify error representation portability
ayden94 Aug 3, 2026
b639bf7
Cover Node error representation portability
ayden94 Aug 3, 2026
e9a04eb
Document HTTP error representations
ayden94 Aug 3, 2026
0a10c0c
Document runtime error representation wiring
ayden94 Aug 3, 2026
0f7cd39
Document React error documents
ayden94 Aug 3, 2026
d74cf8e
Document error representation portability
ayden94 Aug 3, 2026
7ad8304
Define the HTTP error representation decision
ayden94 Aug 3, 2026
fb47ae3
Align canonical HTTP error response docs
ayden94 Aug 3, 2026
ffb34fa
Align HTTP runtime representation ownership docs
ayden94 Aug 3, 2026
29a986a
Align React error representation policy docs
ayden94 Aug 3, 2026
dabcd2f
Make error representation docs discoverable
ayden94 Aug 3, 2026
654c4dc
Record HTTP error representation release intent
ayden94 Aug 3, 2026
5658192
Propagate HTTP representation writer failures
ayden94 Aug 3, 2026
bb11d6e
Document HTTP error negotiation exports
ayden94 Aug 3, 2026
278a7e4
Harden network error representation portability
ayden94 Aug 3, 2026
76b3ad2
Add Web error representation abort portability
ayden94 Aug 3, 2026
6bb50c5
Lock public portability option types
ayden94 Aug 3, 2026
c5ecc95
Cover Node error representation abort portability
ayden94 Aug 3, 2026
9aabec1
Cover Fastify error representation abort portability
ayden94 Aug 3, 2026
f8e591f
Preserve Express error representation boundaries
ayden94 Aug 3, 2026
6488e0c
Cover Bun error representation abort portability
ayden94 Aug 3, 2026
b372c74
Cover Deno error representation abort portability
ayden94 Aug 3, 2026
8228d29
Lock React error representation ownership
ayden94 Aug 3, 2026
9b8627c
Clarify HTTP error representation architecture
ayden94 Aug 3, 2026
c51343f
Document safe HTTP HTML representations
ayden94 Aug 3, 2026
a7926cf
Document safe runtime HTML representations
ayden94 Aug 3, 2026
f3b9ccb
Document React error document trust boundaries
ayden94 Aug 3, 2026
219d45c
Document abort portability assertions
ayden94 Aug 3, 2026
55ae1b8
Lock error representation documentation governance
ayden94 Aug 3, 2026
4f0b89f
Wait for request lifecycle completion in abort portability
ayden94 Aug 3, 2026
b3acba6
Expose Node.js request observer portability options
ayden94 Aug 3, 2026
679926d
Expose Express request observer portability options
ayden94 Aug 3, 2026
7e21046
Record Express error representation release intent
ayden94 Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/issue-2889-http-error-representations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@fluojs/http": minor
"@fluojs/platform-express": patch
"@fluojs/react": minor
"@fluojs/runtime": minor
"@fluojs/testing": minor
---

Add an HTTP-owned, content-negotiated error representation seam that preserves canonical JSON by default, optionally renders application-owned HTML for classified errors and route misses, and keeps status, headers, `HEAD`, abort, commit, and one-shot fallback behavior in the dispatcher.

Expose runtime bootstrap wiring, a buffered React error-document provider adapter, and typed network/fetch-style portability assertions for the new representation contract.

Preserve existing Express response `Vary` values when HTTP error representation negotiation adds `Accept`.
19 changes: 15 additions & 4 deletions docs/CONTEXT.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ Cron lifecycle contract는 committed scheduler handle token으로 callback을 ga

[`docs/architecture/http-catch-all-route-grammar.ko.md`](./architecture/http-catch-all-route-grammar.ko.md)는 catch-all 도입을 유예한다. `@fluojs/http`는 literal 및 full-segment `:param` route segment만 계속 허용하고, `@fluojs/react/client`의 실제 anchor는 client route grammar를 만들지 않으면서 명시적인 server route로 일반 full-document fallback을 제공한다. 재검토에는 HTTP-owned syntax, `static > param > catch-all` ordering, string params, OpenAPI policy, adapter parity, native fast path 결정, performance evidence가 필요하다.

## HTTP Error Representation Decision

채택된 [HTTP error representation decision](./architecture/http-error-representations.ko.md)은
classification, `Accept` negotiation, status/header, request scope, `HEAD`, abort, commit ownership을
`@fluojs/http`에 유지한다. Application은 `errorRepresentation.html`을 등록할 수 있고 canonical JSON은
default, wildcard/tie winner, 406 fallback, one-shot provider-failure fallback으로 유지된다.
`@fluojs/react`는 `createReactErrorRepresentationProvider(...)`를 통해 application error document를
buffer할 수 있지만 route를 match하거나 page policy를 조회하거나 HTTP outcome을 override하지 않는다.

## React Render Policy Decision

채택된 [React render policy decorator decision](./architecture/react-render-policy-decorators.ko.md)은
Expand All @@ -175,10 +184,11 @@ post-shell recoverable error는 기존 owner와 phase를 유지합니다.
후속 [React page render policy decision](./architecture/react-page-render-policies.ko.md)은 synchronous
request-aware `@PageMetadata(...)` factory와 bounded title/meta/link resolution, ordinary React element
creation을 채택합니다. Factory는 active request, optional request id, request-scope container를 받지만
response authority는 받지 않으며 matched application renderer만 이를 consume합니다. Generic error
presentation과 page-local not-found presentation은 거부하므로 unmatched request, handler-thrown
`NotFoundException`, SSR diagnostic phase, Vite asset discovery, inline serialization은 기존 owner와 boundary를
유지합니다.
response authority는 받지 않으며 matched application renderer만 이를 consume합니다. Generic page error
presentation과 page-local not-found presentation은 거부한다. 후속 HTTP-owned application seam은 HTTP
classification 이후에만 optional React-produced document를 선택할 수 있으므로 unmatched request,
handler-thrown `NotFoundException`, SSR diagnostic phase, Vite asset discovery, inline serialization은 기존
owner와 boundary를 유지한다.

## React RSC Graduation Gate

Expand Down Expand Up @@ -208,6 +218,7 @@ HTTP route 및 #2506 navigation ownership, dual-import test, bilingual docs, Cha
| 저장소 정체성과 위반 불가 규칙 확인 | `docs/CONTEXT.md` | `docs/contracts/behavioral-contract-policy.md` |
| 아키텍처 모델, 요청 흐름, 런타임 경계 확인 | `docs/architecture/architecture-overview.md` | `docs/reference/glossary-and-mental-model.md` |
| HTTP catch-all grammar 결정과 재검토 gate 확인 | `docs/architecture/http-catch-all-route-grammar.ko.md` | 활성 explicit-route contract는 `packages/http/README.ko.md` 및 `packages/react/README.ko.md` |
| HTTP JSON/HTML error representation ownership와 negotiation 확인 | `docs/architecture/http-error-representations.ko.md` | `docs/architecture/error-responses.ko.md`, `packages/http/README.ko.md`, `packages/react/README.ko.md` |
| 패키지 계열 조회 또는 런타임 범위 확인 | `docs/reference/package-surface.md` | 선택 로직이 필요하면 `docs/reference/package-chooser.md` |
| i18n ecosystem bridge compatibility와 migration boundary 확인 | `docs/reference/i18n-ecosystem-bridges.ko.md` | third-party bridge 작성 시 `docs/contracts/third-party-extension-contract.ko.md` |
| behavioral guarantee, Changesets 릴리스 흐름, 버전 정책 확인 | `docs/contracts/behavioral-contract-policy.md` | `docs/contracts/release-governance.md` |
Expand Down
18 changes: 15 additions & 3 deletions docs/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,16 @@ The Cron lifecycle contract gates callbacks with the committed scheduler handle

[`docs/architecture/http-catch-all-route-grammar.md`](./architecture/http-catch-all-route-grammar.md) defers catch-all adoption. `@fluojs/http` continues to accept only literal and full-segment `:param` route segments, while `@fluojs/react/client` real anchors provide ordinary full-document fallback to explicit server routes without creating a client route grammar. Reconsideration requires an HTTP-owned syntax, `static > param > catch-all` ordering, string params, OpenAPI policy, adapter parity, native fast path decisions, and performance evidence.

## HTTP Error Representation Decision

The accepted [HTTP error representation decision](./architecture/http-error-representations.md)
keeps classification, `Accept` negotiation, status/headers, request scope, `HEAD`, abort, and commit
ownership in `@fluojs/http`. Applications may register `errorRepresentation.html`; canonical JSON
remains the default, wildcard/tie winner, 406 fallback, and one-shot provider-failure fallback.
`@fluojs/react` may buffer an application error document through
`createReactErrorRepresentationProvider(...)`, but it does not match routes, consult page policies,
or override the HTTP outcome.

## React Render Policy Decision

The accepted [React render policy decorator decision](./architecture/react-render-policy-decorators.md)
Expand All @@ -176,9 +186,10 @@ The follow-up [React page render policy decision](./architecture/react-page-rend
accepts synchronous request-aware `@PageMetadata(...)` factories plus bounded title/meta/link
resolution and ordinary React element creation. Factories receive the active request, optional
request id, and request-scope container but no response authority; only the matched application
renderer consumes them. Generic error presentation and page-local not-found presentation are
rejected, so unmatched requests, handler-thrown `NotFoundException`, SSR diagnostic phases, Vite
asset discovery, and inline serialization keep their existing owners and boundaries.
renderer consumes them. Generic page error presentation and page-local not-found presentation are
rejected. The later HTTP-owned application seam may select an optional React-produced document only
after HTTP classification, so unmatched requests, handler-thrown `NotFoundException`, SSR diagnostic
phases, Vite asset discovery, and inline serialization keep their existing owners and boundaries.

## React RSC Graduation Gate

Expand Down Expand Up @@ -208,6 +219,7 @@ re-export for the documented deprecation window.
| Repository identity and non-negotiable rules | `docs/CONTEXT.md` | `docs/contracts/behavioral-contract-policy.md` |
| Architecture model, request flow, and runtime boundaries | `docs/architecture/architecture-overview.md` | `docs/reference/glossary-and-mental-model.md` |
| HTTP catch-all grammar decision and revisit gates | `docs/architecture/http-catch-all-route-grammar.md` | `packages/http/README.md` and `packages/react/README.md` for the active explicit-route contract |
| HTTP JSON/HTML error representation ownership and negotiation | `docs/architecture/http-error-representations.md` | `docs/architecture/error-responses.md`, `packages/http/README.md`, and `packages/react/README.md` |
| Package family lookup or runtime coverage | `docs/reference/package-surface.md` | `docs/reference/package-chooser.md` when selection logic is needed |
| i18n ecosystem bridge compatibility and migration boundaries | `docs/reference/i18n-ecosystem-bridges.md` | `docs/contracts/third-party-extension-contract.md` when authoring a third-party bridge |
| Behavioral guarantees, Changesets release flow, and versioning policy | `docs/contracts/behavioral-contract-policy.md` | `docs/contracts/release-governance.md` |
Expand Down
1 change: 1 addition & 0 deletions docs/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- 패키지 표면: [`reference/package-surface.ko.md`](./reference/package-surface.ko.md)
- 패키지 선택기: [`reference/package-chooser.ko.md`](./reference/package-chooser.ko.md)
- Behavioral contract: [`contracts/behavioral-contract-policy.ko.md`](./contracts/behavioral-contract-policy.ko.md)
- HTTP error representation decision: [`architecture/http-error-representations.ko.md`](./architecture/http-error-representations.ko.md)
- React render policy decision: [`architecture/react-render-policy-decorators.ko.md`](./architecture/react-render-policy-decorators.ko.md)
- React page metadata/error/not-found policy decision: [`architecture/react-page-render-policies.ko.md`](./architecture/react-page-render-policies.ko.md)
- React RSC graduation policy: [`contracts/react-rsc-graduation.ko.md`](./contracts/react-rsc-graduation.ko.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This directory contains governed repository documentation for fluo. The official
- Package surface: [`reference/package-surface.md`](./reference/package-surface.md)
- Package chooser: [`reference/package-chooser.md`](./reference/package-chooser.md)
- Behavioral contracts: [`contracts/behavioral-contract-policy.md`](./contracts/behavioral-contract-policy.md)
- HTTP error representation decision: [`architecture/http-error-representations.md`](./architecture/http-error-representations.md)
- React render policy decision: [`architecture/react-render-policy-decorators.md`](./architecture/react-render-policy-decorators.md)
- React page metadata/error/not-found policy decision: [`architecture/react-page-render-policies.md`](./architecture/react-page-render-policies.md)
- React RSC graduation policy: [`contracts/react-rsc-graduation.md`](./contracts/react-rsc-graduation.md)
Expand Down
29 changes: 25 additions & 4 deletions docs/architecture/error-responses.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,43 @@ interface ErrorResponse {
| `429` | `TOO_MANY_REQUESTS` | `TooManyRequestsException` |
| `500` | `INTERNAL_SERVER_ERROR` | `InternalServerErrorException` |

`packages/http/src/dispatch/dispatch-error-policy.ts`의 dispatcher 정규화 규칙은 다음 매핑을 추가합니다.
`packages/http/src/dispatch/dispatch-error-representation.ts`의 dispatcher 정규화 규칙은 다음 매핑을 추가합니다.

| Input failure | Output code | Output status | Rule |
| --- | --- | --- | --- |
| 기존 `HttpException` | 기존 code | 기존 status | 재매핑 없이 직렬화됩니다. |
| `HandlerNotFoundError` | `NOT_FOUND` | `404` | `NotFoundException`으로 변환됩니다. |
| 그 외 모든 throw 값 | `INTERNAL_SERVER_ERROR` | `500` | `InternalServerErrorException`으로 변환됩니다. |

## Representation Selection

Application이 `errorRepresentation.html`을 등록해도 위 envelope가 canonical contract로 유지된다. 기존
`HttpException`과 route miss에 대해 HTTP는 먼저 canonical outcome을 만든 다음 `application/json`과 사용
가능한 `text/html` provider를 negotiate한다. Provider는 `HttpErrorRepresentationContext`에서 같은
`ErrorResponse`를 받으므로 HTML document가 status, code, details, metadata, request id를 다시 정의하지
않고 outcome을 표현할 수 있다.

`Accept`가 없거나 wildcard tie이면 JSON을 선택한다. Quality와 media-range specificity는 deterministic하며,
specific `q=0` rejection이 해당 representation에 대해 broader wildcard보다 우선한다. 등록된 acceptable
offer가 없으면 HTML을 재귀 호출하지 않고 canonical JSON `406 NOT_ACCEPTABLE`을 보낸다. `HEAD`는 선택된
status/header를 보존하지만 render하거나 body를 보내지 않는다. Provider failure는 원래 canonical JSON
outcome으로 한 번만 fallback한다.

알 수 없는 throw 값은 canonical JSON 500으로 유지되고 HTML representation phase에 들어가지 않는다.
Configured filter와 `onError`가 계속 우선하며 already-committed 또는 aborted request는 다시 쓰지 않는다.
전체 ownership 및 React integration 계약은
[HTTP error representation decision](./http-error-representations.ko.md)을 참고한다.

## Handling Rules

| Rule | Statement | Source anchor |
| --- | --- | --- |
| Serialization boundary | HTTP 클라이언트는 `createErrorResponse(...)`가 만든 `{ error: ... }` envelope를 받습니다. | `packages/http/src/exceptions.ts` |
| Unknown failure masking | `HttpException`이 아닌 값은 `Internal server error.` 메시지와 `INTERNAL_SERVER_ERROR`로 정규화됩니다. | `packages/http/src/dispatch/dispatch-error-policy.ts` |
| Route miss mapping | 누락된 handler는 가공되지 않은 런타임 에러가 아니라 `NOT_FOUND`로 노출됩니다. | `packages/http/src/dispatch/dispatch-error-policy.ts` |
| Response commit guard | 응답이 이미 committed 상태이면 `writeErrorResponse(...)`는 아무 것도 쓰지 않고 반환합니다. | `packages/http/src/dispatch/dispatch-error-policy.ts` |
| Unknown failure masking | `HttpException`이 아닌 값은 `Internal server error.` 메시지와 `INTERNAL_SERVER_ERROR`로 정규화됩니다. | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Route miss mapping | 누락된 handler는 가공되지 않은 런타임 에러가 아니라 `NOT_FOUND`로 노출됩니다. | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Response commit guard | 응답이 이미 committed 상태이면 `writeErrorResponse(...)`는 아무 것도 쓰지 않고 반환합니다. | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Representation negotiation | HTML provider가 등록된 경우에만 eligible HTTP outcome이 deterministic JSON/HTML selection을 사용합니다. | `packages/http/src/dispatch/dispatch-error-negotiation.ts` |
| Provider fallback | Pre-commit provider failure는 configured dispatcher logger로 기록되고 원래 JSON outcome으로 한 번만 fallback합니다. | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Request correlation | dispatcher는 `requestContext.requestId`를 에러 직렬화에 전달하고, correlation middleware는 가능할 때 inbound header에서 그 값을 채웁니다. | `packages/http/src/dispatch/dispatcher.ts`, `packages/http/src/middleware/correlation.ts` |
| Binding diagnostics | 누락된 요청 필드, 잘못된 body shape, 위험한 key, 미지원 body field는 구조화된 `details`와 함께 `BAD_REQUEST`를 생성합니다. | `packages/http/src/adapters/binding.ts` |
| Validation diagnostics | DTO 유효성 검사 실패는 매핑된 issue detail을 포함한 `BadRequestException`으로 변환됩니다. | `packages/http/src/adapters/dto-validation-adapter.ts` |
Expand Down
30 changes: 26 additions & 4 deletions docs/architecture/error-responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,44 @@ Built-in exception classes in `packages/http/src/exceptions.ts` currently serial
| `429` | `TOO_MANY_REQUESTS` | `TooManyRequestsException` |
| `500` | `INTERNAL_SERVER_ERROR` | `InternalServerErrorException` |

Normalization rules in `packages/http/src/dispatch/dispatch-error-policy.ts` add these dispatcher mappings:
Normalization rules in `packages/http/src/dispatch/dispatch-error-representation.ts` add these dispatcher mappings:

| Input failure | Output code | Output status | Rule |
| --- | --- | --- | --- |
| Existing `HttpException` | existing code | existing status | Serialized without remapping. |
| `HandlerNotFoundError` | `NOT_FOUND` | `404` | Converted to `NotFoundException`. |
| Any other thrown value | `INTERNAL_SERVER_ERROR` | `500` | Converted to `InternalServerErrorException`. |

## Representation Selection

The envelope above remains canonical even when an application registers
`errorRepresentation.html`. For existing `HttpException` values and route misses, HTTP first creates
the canonical outcome and then negotiates `application/json` versus an available `text/html`
provider. The provider receives the same `ErrorResponse` in `HttpErrorRepresentationContext`, so an
HTML document can present the outcome without redefining its status, code, details, metadata, or
request id.

Absent `Accept` and wildcard ties select JSON. Quality values and media-range specificity are
deterministic; a specific `q=0` rejection overrides a broader wildcard for that representation.
When no registered offer is acceptable, HTTP emits canonical JSON `406 NOT_ACCEPTABLE` without
invoking HTML recursively. `HEAD` preserves the selected status and headers without rendering or
emitting a body. Provider failures fall back once to the original canonical JSON outcome.

Unknown thrown values remain canonical JSON 500 and do not enter the HTML representation phase.
Configured filters and `onError` retain precedence, and already-committed or aborted requests are
not rewritten. See the [HTTP error representation decision](./http-error-representations.md) for the
complete ownership and React integration contract.

## Handling Rules

| Rule | Statement | Source anchor |
| --- | --- | --- |
| Serialization boundary | HTTP clients receive the `{ error: ... }` envelope created by `createErrorResponse(...)`. | `packages/http/src/exceptions.ts` |
| Unknown failure masking | Non-`HttpException` values are normalized to `INTERNAL_SERVER_ERROR` with the message `Internal server error.` | `packages/http/src/dispatch/dispatch-error-policy.ts` |
| Route miss mapping | Missing handlers are exposed as `NOT_FOUND` instead of raw runtime errors. | `packages/http/src/dispatch/dispatch-error-policy.ts` |
| Response commit guard | `writeErrorResponse(...)` returns without writing when the response is already committed. | `packages/http/src/dispatch/dispatch-error-policy.ts` |
| Unknown failure masking | Non-`HttpException` values are normalized to `INTERNAL_SERVER_ERROR` with the message `Internal server error.` | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Route miss mapping | Missing handlers are exposed as `NOT_FOUND` instead of raw runtime errors. | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Response commit guard | `writeErrorResponse(...)` returns without writing when the response is already committed. | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Representation negotiation | Eligible HTTP outcomes use deterministic JSON/HTML selection only when an HTML provider is registered. | `packages/http/src/dispatch/dispatch-error-negotiation.ts` |
| Provider fallback | A pre-commit provider failure logs through the configured dispatcher logger and falls back once to the original JSON outcome. | `packages/http/src/dispatch/dispatch-error-representation.ts` |
| Request correlation | The dispatcher passes `requestContext.requestId` into error serialization, and the correlation middleware populates that value from inbound headers when available. | `packages/http/src/dispatch/dispatcher.ts`, `packages/http/src/middleware/correlation.ts` |
| Binding diagnostics | Missing request fields, invalid body shapes, dangerous keys, and unknown body fields produce `BAD_REQUEST` with structured `details`. | `packages/http/src/adapters/binding.ts` |
| Validation diagnostics | DTO validation failures are converted to `BadRequestException` with mapped issue details. | `packages/http/src/adapters/dto-validation-adapter.ts` |
Expand Down
Loading