@apifuse/provider-sdk 2.2.0-beta.67 → 2.2.0-beta.68

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 (35) hide show
  1. package/AUTHORING.md +28 -1
  2. package/CHANGELOG.md +4 -0
  3. package/README.md +8 -4
  4. package/bin/apifuse-record.ts +81 -21
  5. package/bin/apifuse-submit-check.ts +170 -28
  6. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +138 -23
  7. package/dist/cli/templates/provider/README.md.tpl +17 -9
  8. package/dist/config/loader.d.ts +16 -0
  9. package/dist/config/loader.js +25 -8
  10. package/dist/error-resolution.d.ts +13 -0
  11. package/dist/error-resolution.js +72 -35
  12. package/dist/lint.d.ts +2 -0
  13. package/dist/lint.js +493 -2
  14. package/dist/runtime/executor.js +3 -0
  15. package/dist/runtime/http.d.ts +2 -0
  16. package/dist/runtime/http.js +2 -1
  17. package/dist/runtime/provider.d.ts +7 -0
  18. package/dist/runtime/provider.js +14 -0
  19. package/dist/server/index.d.ts +1 -0
  20. package/dist/server/serve-implementation.d.ts +2 -1
  21. package/dist/server/serve-implementation.js +43 -29
  22. package/dist/server/state-backend-report.d.ts +70 -0
  23. package/dist/server/state-backend-report.js +113 -0
  24. package/package.json +1 -1
  25. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +138 -23
  26. package/src/cli/templates/provider/README.md.tpl +17 -9
  27. package/src/config/loader.ts +49 -12
  28. package/src/error-resolution.ts +89 -35
  29. package/src/lint.ts +541 -1
  30. package/src/runtime/executor.ts +3 -0
  31. package/src/runtime/http.ts +2 -1
  32. package/src/runtime/provider.ts +15 -0
  33. package/src/server/index.ts +4 -0
  34. package/src/server/serve-implementation.ts +56 -30
  35. package/src/server/state-backend-report.ts +185 -0
@@ -1,3 +1,4 @@
1
+ import type { ProviderErrorCategory } from "./observability.js";
1
2
  import type { ProviderErrorStatus } from "./types.js";
2
3
 
3
4
  // This set suppresses the unregistered-provider-error-code signal for codes
@@ -120,11 +121,89 @@ export const SDK_RUNTIME_OWNED_ERROR_CODES = new Set([
120
121
  "not_found",
121
122
  ]);
122
123
 
124
+ /** One registered upstream code: the status the SDK serves and who failed. */
125
+ export type SdkUpstreamErrorCodeRegistration = {
126
+ readonly status: ProviderErrorStatus;
127
+ readonly category: ProviderErrorCategory;
128
+ };
129
+
130
+ // Registered codes that name an upstream outcome. A row states the status the
131
+ // SDK serves and the observability category together, so an upstream code
132
+ // cannot be registered without saying who failed. Serve reads the category
133
+ // for a `ProviderError` that carries one of these codes and no explicit
134
+ // `category`, and the public `source` follows from it (`sourceForCategory`).
135
+ // Without a row the category defaults to `provider_error` → `source:
136
+ // "apifuse"`, and the health monitor cannot tie a real upstream failure to the
137
+ // upstream: it publishes `monitoring_unavailable` instead of `down`.
138
+ //
139
+ // The category is `categoryForStatus(status)` unless the code names a
140
+ // narrower class than its status can: a schema change, a bot wall, an
141
+ // upstream refusal of the platform-managed key (served 400, see below).
142
+ // Every row is a code the fleet throws for something the upstream did or
143
+ // said; caller-side and SDK-internal codes stay in the table below.
144
+ export const SDK_UPSTREAM_ERROR_CODE_REGISTRY: ReadonlyMap<
145
+ string,
146
+ SdkUpstreamErrorCodeRegistration
147
+ > = new Map<string, SdkUpstreamErrorCodeRegistration>([
148
+ // The SDK's own unknown-operation and unknown-route throws reuse the
149
+ // not-found spellings; they set an explicit category and stay APIFuse's.
150
+ ["NOT_FOUND", { status: 404, category: "upstream_http" }],
151
+ ["not_found", { status: 404, category: "upstream_http" }],
152
+ ["NO_DATA", { status: 404, category: "upstream_http" }],
153
+ ["RATE_LIMITED", { status: 429, category: "upstream_rate_limited" }],
154
+ ["UPSTREAM_RATE_LIMIT", { status: 429, category: "upstream_rate_limited" }],
155
+ [
156
+ "LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR",
157
+ { status: 429, category: "upstream_rate_limited" },
158
+ ],
159
+ // Deterministic upstream business refusal (honest-provider-error-
160
+ // contract): the upstream evaluated the request and said no under its
161
+ // own rules — a conflict with upstream state, never a 5xx.
162
+ ["UPSTREAM_REJECTED", { status: 409, category: "upstream_rejected" }],
163
+ ["UPSTREAM_ERROR", { status: 502, category: "upstream_http" }],
164
+ // The upstream's bot wall or request protection refused the request. The
165
+ // status says only "upstream"; the code says which kind.
166
+ ["BLOCKED", { status: 502, category: "anti_bot_blocked" }],
167
+ // Fleet-consensus provider codes. These are thrown by provider code, not
168
+ // by the SDK, so they are registered here (status mapping) and not in
169
+ // SDK_RUNTIME_OWNED_ERROR_CODES: an operation-declared status still wins,
170
+ // exactly as it does for UPSTREAM_ERROR and BLOCKED above. Registering
171
+ // them stops the fleet from re-declaring the same rows on every
172
+ // operation, and stops an undeclared throw from being served as 500.
173
+ //
174
+ // A platform-managed upstream service key the upstream refuses. With
175
+ // `auth.mode: "platform-managed"` the caller holds no credential, so 401
176
+ // ("re-authenticate") tells the caller to do something it cannot do, and
177
+ // 502 ("upstream is sick") promises a recovery that will never come. It is
178
+ // a deployment/config defect — the same class as MISSING_SECRET below,
179
+ // which is already an explicit 400 for that reason. The upstream is still
180
+ // the one refusing, so the category is `upstream_auth`; the gateway
181
+ // records it as `provider_error` for every non-`required` connection mode
182
+ // so the refusal stays provider-alertable.
183
+ ["UPSTREAM_AUTH_ERROR", { status: 400, category: "upstream_auth" }],
184
+ // The upstream changed its response shape and the provider cannot
185
+ // normalize it. 502 because the fault is upstream of us, non-retryable
186
+ // because a retry returns the same broken payload.
187
+ ["UPSTREAM_SCHEMA_ERROR", { status: 502, category: "upstream_schema_drift" }],
188
+ ]);
189
+
190
+ /**
191
+ * The category a registered upstream code carries when its `ProviderError`
192
+ * sets none. `undefined` for every other code, which keeps its existing
193
+ * default.
194
+ */
195
+ export function registeredUpstreamErrorCategory(
196
+ code: string | undefined,
197
+ ): ProviderErrorCategory | undefined {
198
+ return code === undefined ? undefined : SDK_UPSTREAM_ERROR_CODE_REGISTRY.get(code)?.category;
199
+ }
200
+
123
201
  // Canonical SDK status mapping for recognized provider-thrown error codes.
124
202
  // serve.ts toStatusCode consults this map (after operation-declared overrides
125
203
  // for non-SDK-owned codes), and the authoring lint treats these codes as
126
204
  // SDK-registered. Add new codes here instead of duplicating literals in
127
- // either consumer.
205
+ // either consumer — an upstream-attributed code goes in
206
+ // SDK_UPSTREAM_ERROR_CODE_REGISTRY above, which feeds this map.
128
207
  export const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES: ReadonlyMap<string, ProviderErrorStatus> =
129
208
  new Map<string, ProviderErrorStatus>([
130
209
  ["AUTH_REQUIRED", 401],
@@ -132,16 +211,6 @@ export const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES: ReadonlyMap<string, Provide
132
211
  // Unprovisioned declared secret: a deployment/config defect, never an
133
212
  // upstream failure — explicit 400.
134
213
  ["MISSING_SECRET", 400],
135
- ["NOT_FOUND", 404],
136
- ["not_found", 404],
137
- ["NO_DATA", 404],
138
- ["RATE_LIMITED", 429],
139
- ["UPSTREAM_RATE_LIMIT", 429],
140
- ["LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR", 429],
141
- // Deterministic upstream business refusal (honest-provider-error-
142
- // contract): the upstream evaluated the request and said no under its
143
- // own rules — a conflict with upstream state, never a 5xx.
144
- ["UPSTREAM_REJECTED", 409],
145
214
  // Only an unverifiable or foreign handle is the caller's: key and binding
146
215
  // faults stay unmapped (500) because the engine host, not the caller, owns them.
147
216
  ["EGRESS_LEASE_INVALID", 409],
@@ -157,30 +226,11 @@ export const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES: ReadonlyMap<string, Provide
157
226
  ["HANDLE_EXPIRED", 410],
158
227
  ["HANDLE_COMMITTED", 409],
159
228
  ["HANDLE_BUSY", 409],
160
- ["UPSTREAM_ERROR", 502],
161
- ["BLOCKED", 502],
162
- // Fleet-consensus provider codes. These are thrown by provider code, not
163
- // by the SDK, so they are registered here (status mapping) and not in
164
- // SDK_RUNTIME_OWNED_ERROR_CODES: an operation-declared status still wins,
165
- // exactly as it does for UPSTREAM_ERROR and BLOCKED above. Registering
166
- // them stops the fleet from re-declaring the same three rows on every
167
- // operation, and stops an undeclared throw from being served as 500.
168
- //
169
- // A platform-managed upstream service key the upstream refuses. With
170
- // `auth.mode: "platform-managed"` the caller holds no credential, so 401
171
- // ("re-authenticate") tells the caller to do something it cannot do, and
172
- // 502 ("upstream is sick") promises a recovery that will never come. It is
173
- // a deployment/config defect — the same class as MISSING_SECRET above,
174
- // which is already an explicit 400 for that reason.
175
- ["UPSTREAM_AUTH_ERROR", 400],
176
- // The upstream changed its response shape and the provider cannot
177
- // normalize it. 502 because the fault is upstream of us, non-retryable
178
- // because a retry returns the same broken payload.
179
- ["UPSTREAM_SCHEMA_ERROR", 502],
180
- // Caller-side bad input rejected by the provider. The minority spellings
181
- // (INVALID_INPUT / VALIDATION_ERROR / BAD_REQUEST) are deliberately not
182
- // registered: they migrate to this spelling on the contract track, and
183
- // registering them here would freeze the divergence.
229
+ // Caller-side bad input rejected by the provider (fleet consensus, like
230
+ // UPSTREAM_AUTH_ERROR and UPSTREAM_SCHEMA_ERROR above). The minority
231
+ // spellings (INVALID_INPUT / VALIDATION_ERROR / BAD_REQUEST) are
232
+ // deliberately not registered: they migrate to this spelling on the
233
+ // contract track, and registering them here would freeze the divergence.
184
234
  ["INVALID_REQUEST", 400],
185
235
  ["OCR_UNAVAILABLE", 503],
186
236
  ["UNSUPPORTED_OCR_BACKEND", 503],
@@ -194,6 +244,10 @@ export const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES: ReadonlyMap<string, Provide
194
244
  // retry can clear, and a retryable 5xx there would turn one bad rollout
195
245
  // into gateway-driven load.
196
246
  ["PROVIDER_ENGINE_UNAVAILABLE", 503],
247
+ ...Array.from(
248
+ SDK_UPSTREAM_ERROR_CODE_REGISTRY,
249
+ ([code, registration]) => [code, registration.status] as const,
250
+ ),
197
251
  ]);
198
252
 
199
253
  // Canonical retryability for the fleet-consensus codes registered above.