@oneciel-ai/ciel-runtime 0.2.46 → 0.2.47
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.
- package/CHANGELOG.md +10 -0
- package/ciel_runtime_support/openai_responses_router.py +4 -0
- package/ciel_runtime_support/openai_responses_stream.py +17 -6
- package/ciel_runtime_support/response_stream_context.py +2 -0
- package/ciel_runtime_support/runtime_constants.py +1 -1
- package/docs/journal/2026/09/12/releases/auth-boundary/verification.okf +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ capability, followed by the complete commit ledger merged into each release.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.2.47 — 2026-09-12
|
|
9
|
+
|
|
10
|
+
- Report non-native upstream authentication failures on Responses routes as
|
|
11
|
+
HTTP 424 dependency failures, preserving the provider, upstream HTTP 401,
|
|
12
|
+
error type and message. Prevent Codex from hiding these failures behind an
|
|
13
|
+
unrelated ChatGPT token refresh. Native OpenAI and router access authentication
|
|
14
|
+
retain their original HTTP semantics.
|
|
15
|
+
|
|
16
|
+
## Earlier unreleased changes
|
|
17
|
+
|
|
8
18
|
- Allow complete transcript records larger than the batch target (separate
|
|
9
19
|
max_record_bytes memory bound, default 16 MiB, configurable up to 64 MiB).
|
|
10
20
|
- Forward structured runtime errors to the shared event stream and display
|
|
@@ -261,6 +261,7 @@ def handle_openai_responses_request(
|
|
|
261
261
|
stream=stream,
|
|
262
262
|
status=exc.code,
|
|
263
263
|
error_type=_responses_error_type(exc.code),
|
|
264
|
+
**({"upstream_provider": provider} if exc.code == 401 else {}),
|
|
264
265
|
)
|
|
265
266
|
except UpstreamFailure as exc:
|
|
266
267
|
# The upstream status, type and message were all read before this was
|
|
@@ -273,6 +274,7 @@ def handle_openai_responses_request(
|
|
|
273
274
|
stream=stream,
|
|
274
275
|
status=exc.status_code,
|
|
275
276
|
error_type=exc.anthropic_error_type,
|
|
277
|
+
**({"upstream_provider": provider} if exc.status_code == 401 else {}),
|
|
276
278
|
)
|
|
277
279
|
except UpstreamStreamReadError as exc:
|
|
278
280
|
core.event_bus.publish(
|
|
@@ -467,6 +469,7 @@ def _handle_provider_responses_route(
|
|
|
467
469
|
stream=bool(body.get("stream", True)),
|
|
468
470
|
status=exc.code,
|
|
469
471
|
error_type=_responses_error_type(exc.code),
|
|
472
|
+
**({"upstream_provider": provider} if exc.code == 401 else {}),
|
|
470
473
|
)
|
|
471
474
|
except UpstreamFailure as exc:
|
|
472
475
|
delivery.mark_failed(
|
|
@@ -478,6 +481,7 @@ def _handle_provider_responses_route(
|
|
|
478
481
|
stream=bool(body.get("stream", True)),
|
|
479
482
|
status=exc.status_code,
|
|
480
483
|
error_type=exc.anthropic_error_type,
|
|
484
|
+
**({"upstream_provider": provider} if exc.status_code == 401 else {}),
|
|
481
485
|
response_started=exc.output_started,
|
|
482
486
|
response_id=exc.response_id,
|
|
483
487
|
)
|
|
@@ -51,26 +51,37 @@ def write_openai_responses_error(
|
|
|
51
51
|
error_type: str = "api_error",
|
|
52
52
|
response_started: bool = False,
|
|
53
53
|
response_id: str | None = None,
|
|
54
|
+
upstream_provider: str | None = None,
|
|
54
55
|
services: OpenAIResponsesStreamServices,
|
|
55
56
|
) -> None:
|
|
57
|
+
error = {"type": error_type, "message": message}
|
|
58
|
+
if upstream_provider is not None and status == 401:
|
|
59
|
+
# The client authenticated to this router; a different credential at
|
|
60
|
+
# the upstream dependency failed. HTTP 401 here makes Codex refresh
|
|
61
|
+
# its unrelated OpenAI login, hiding the actual provider failure.
|
|
62
|
+
error.update(
|
|
63
|
+
code="upstream_authentication_error",
|
|
64
|
+
upstream_status=status,
|
|
65
|
+
upstream_provider=upstream_provider,
|
|
66
|
+
message=f"Upstream provider {upstream_provider} rejected authentication (HTTP {status}): {message}",
|
|
67
|
+
)
|
|
68
|
+
status = 424
|
|
69
|
+
if not response_started:
|
|
70
|
+
stream = False
|
|
56
71
|
if response_started:
|
|
57
72
|
failed = {
|
|
58
73
|
"type": "response.failed",
|
|
59
74
|
"response": {
|
|
60
75
|
"id": str(response_id or "resp_ciel_upstream_failure"),
|
|
61
76
|
"status": "failed",
|
|
62
|
-
"error": {
|
|
63
|
-
"type": error_type,
|
|
64
|
-
"code": error_type,
|
|
65
|
-
"message": message,
|
|
66
|
-
},
|
|
77
|
+
"error": {"code": error_type, **error},
|
|
67
78
|
},
|
|
68
79
|
}
|
|
69
80
|
handler.wfile.write(b"\n\n")
|
|
70
81
|
_emit(handler, "response.failed", failed)
|
|
71
82
|
handler.wfile.flush()
|
|
72
83
|
return
|
|
73
|
-
payload = {"type": "error", "error":
|
|
84
|
+
payload = {"type": "error", "error": error}
|
|
74
85
|
if not stream:
|
|
75
86
|
services.write_json(handler, payload, status)
|
|
76
87
|
return
|
|
@@ -423,6 +423,7 @@ class ResponseStreamContext:
|
|
|
423
423
|
error_type: str = "api_error",
|
|
424
424
|
response_started: bool = False,
|
|
425
425
|
response_id: str | None = None,
|
|
426
|
+
upstream_provider: str | None = None,
|
|
426
427
|
) -> None:
|
|
427
428
|
self.algorithms.write_openai_error(
|
|
428
429
|
handler,
|
|
@@ -432,6 +433,7 @@ class ResponseStreamContext:
|
|
|
432
433
|
error_type=error_type,
|
|
433
434
|
response_started=response_started,
|
|
434
435
|
response_id=response_id,
|
|
436
|
+
**({"upstream_provider": upstream_provider} if upstream_provider is not None else {}),
|
|
435
437
|
services=self.openai_responses_stream_services(),
|
|
436
438
|
)
|
|
437
439
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
title: Responses upstream authentication isolation, 0.2.47
|
|
2
|
+
scope: Non-native upstream HTTP 401 only; native OpenAI and router access policy unchanged.
|
|
3
|
+
implementation:
|
|
4
|
+
transport: Upstream failure projected to HTTP 424 with JSON even for requested streaming when headers are not sent. Already-started streams emit response.failed without new headers.
|
|
5
|
+
metadata: error.upstream_status=401, upstream_provider, code=upstream_authentication_error, original error type and original message retained within prefixed message.
|
|
6
|
+
routes: Translated Responses and provider-native Responses, HTTPError and structured UpstreamFailure. Native Codex route deliberately excluded.
|
|
7
|
+
verification:
|
|
8
|
+
unit: python -m unittest discover -s tests -p test_openai_responses_stream.py; 9 tests passed.
|
|
9
|
+
boundary: python -m unittest discover -s tests -p test_responses_provider_auth_boundary.py; native/non-native and raw/structured matrix passed.
|
|
10
|
+
runtime_binary: codex-cli 0.154.0
|
|
11
|
+
runtime_harness: Isolated CODEX_HOME, synthetic provider and ChatGPT credentials, loopback server and refresh endpoint; actual production write_openai_responses_error used by server.
|
|
12
|
+
before: Two /v1/responses attempts followed by /oauth/token; user-visible refresh token revoked error.
|
|
13
|
+
after: One /v1/responses request with correct synthetic provider key, no /oauth/token; turn.failed displays unexpected status 424 Failed Dependency: Upstream provider diagnostic-provider rejected authentication (HTTP 401): invalid credentials.
|
|
14
|
+
assertions: Original message present, unrelated refresh message absent, no retry, no OAuth request; PASS.
|
|
15
|
+
static: Modified-source Ruff checks and documentation metadata checks passed.
|
|
16
|
+
limitations: Does not make an invalid upstream API key valid. Remote Ollama account identity check rejected currently stored key with HTTP 401 invalid credentials. No remote credentials or processes changed.
|
package/package.json
CHANGED