From 4a5bfece9e1f11eaf869037565a6717d5e07fd10 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 22:25:03 +0900 Subject: [PATCH 01/66] test(opencode): expose missing provider failure envelope Refs #2112 --- tests/test_opencode_model_pool_runner.py | 222 +++++++++++++++++++++++ 1 file changed, 222 insertions(+) diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 2965d4c55c..f1fc81c65d 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -408,6 +408,228 @@ def test_failed_provider_without_reason_logs_explicit_absence(tmp_path: Path) -> ) in result.stdout +@pytest.mark.parametrize( + ("status", "terminal_reason", "expected_class"), + [ + (429, "queue_capacity", "rate-limit"), + (503, "provider_unavailable", "provider-5xx"), + ], + ids=["queue-capacity-429", "provider-503"], +) +def test_failed_gateway_response_emits_bounded_route_metadata( + tmp_path: Path, + status: int, + terminal_reason: str, + expected_class: str, +) -> None: + """Canonical gateway failures retain only safe causal route fields.""" + secret = "sk" + "-gateway-body-must-not-leak" + response_body = json.dumps( + { + "error": { + "detail": { + "model": "openrouter/deepseek-r1:free", + "terminal_reason": terminal_reason, + "attempts": [ + { + "provider_name": "openrouter", + "phase": "queue_admission", + "provider_status": status, + "secret": secret, + } + ], + }, + "message": secret, + } + } + ) + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": "AI_APICallError", + "data": { + "statusCode": status, + "responseBody": response_body, + "message": secret, + }, + }, + } + ), + ) + + assert result.returncode == 1 + assert f"class={expected_class}" in result.stdout + assert "phase=queue_admission" in result.stdout + assert f"reason={terminal_reason}" in result.stdout + assert "provider=openrouter" in result.stdout + assert f"http-status={status}" in result.stdout + assert "exception=AI_APICallError" in result.stdout + assert re.search(r"duration-seconds=\d+", result.stdout) + assert "served-model=openrouter/deepseek-r1:free" in result.stdout + assert secret not in result.stdout + result.stderr + + +def test_failed_gateway_malformed_body_is_explicit_and_redacted(tmp_path: Path) -> None: + """A non-JSON gateway body reports malformed metadata without echoing it.""" + secret = "malformed-" + "provider-body-secret" + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": "AI_APICallError", + "data": {"responseBody": f"not-json {secret}"}, + }, + } + ), + ) + + assert result.returncode == 1 + assert "class=malformed-response" in result.stdout + assert "phase=unknown" in result.stdout + assert "reason=malformed_response" in result.stdout + assert "provider=unknown" in result.stdout + assert "http-status=unknown" in result.stdout + assert "exception=AI_APICallError" in result.stdout + assert "served-model=unknown" in result.stdout + assert secret not in result.stdout + result.stderr + + +def test_failed_gateway_request_too_large_keeps_admission_cause(tmp_path: Path) -> None: + """HTTP 413 remains distinct from provider transport and model exhaustion.""" + response_body = json.dumps( + { + "error": { + "detail": { + "terminal_reason": "request_too_large", + "attempts": [{"phase": "request_admission", "provider_status": 413}], + } + } + } + ) + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": "AI_APICallError", + "data": {"statusCode": 413, "responseBody": response_body}, + }, + } + ), + ) + + assert result.returncode == 1 + assert "class=request-too-large" in result.stdout + assert "phase=request_admission" in result.stdout + assert "reason=request_too_large" in result.stdout + assert "http-status=413" in result.stdout + + +def test_failed_gateway_pool_exhaustion_keeps_terminal_reason(tmp_path: Path) -> None: + """No eligible free route is distinguishable from a malformed response.""" + response_body = json.dumps( + { + "error": { + "detail": { + "terminal_reason": "eligible_candidates_exhausted", + "attempts": [{"phase": "route_selection"}], + } + } + } + ) + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": "ProviderUpstreamError", + "data": {"responseBody": response_body}, + }, + } + ), + ) + + assert result.returncode == 1 + assert "class=model-pool-exhausted" in result.stdout + assert "phase=route_selection" in result.stdout + assert "reason=eligible_candidates_exhausted" in result.stdout + assert "provider=unknown" in result.stdout + assert "http-status=unknown" in result.stdout + + +def test_failed_gateway_missing_model_is_an_explicit_unknown(tmp_path: Path) -> None: + """The adapter never invents a served model when the gateway omits it.""" + response_body = json.dumps( + {"error": {"detail": {"terminal_reason": "provider_unavailable"}}} + ) + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": "AI_APICallError", + "data": {"statusCode": 502, "responseBody": response_body}, + }, + } + ), + ) + + assert result.returncode == 1 + assert "served-model=unknown" in result.stdout + assert "reason=provider_unavailable" in result.stdout + + +def test_failed_gateway_ignores_unsafe_metadata_tokens(tmp_path: Path) -> None: + """Unsafe nested metadata and provider prose never enter public diagnostics.""" + secret = "github" + "_pat_" + "FAILUREENVELOPESECRET123456" + response_body = json.dumps( + { + "error": { + "detail": { + "model": f"unsafe model {secret}", + "terminal_reason": f"unsafe reason {secret}", + "attempts": [ + { + "provider_name": f"unsafe provider {secret}", + "phase": f"unsafe phase {secret}", + } + ], + }, + "message": secret, + }, + "arbitrary": secret, + } + ) + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": f"Unsafe Exception {secret}", + "data": {"responseBody": response_body, "message": secret}, + }, + } + ), + ) + + assert result.returncode == 1 + assert "phase=unknown" in result.stdout + assert "reason=provider_error" in result.stdout + assert "provider=unknown" in result.stdout + assert "exception=unknown" in result.stdout + assert "served-model=unknown" in result.stdout + assert secret not in result.stdout + result.stderr + + def test_backoff_environment_rejects_recursive_arithmetic_injection( tmp_path: Path, ) -> None: From a7a4f78ac75b1025332d9bda18b0ae4548b07b98 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 22:25:24 +0900 Subject: [PATCH 02/66] test(opencode): require owned telemetry quality lane Refs #2112 --- ...nt_review_runtime_quality_consolidation.py | 47 +++++++++++++++++++ tests/test_opencode_agent_contract.py | 1 + tests/test_opencode_model_pool_runner.py | 11 +++-- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/tests/test_agent_review_runtime_quality_consolidation.py b/tests/test_agent_review_runtime_quality_consolidation.py index 4592cfd166..f589ff499c 100644 --- a/tests/test_agent_review_runtime_quality_consolidation.py +++ b/tests/test_agent_review_runtime_quality_consolidation.py @@ -153,6 +153,53 @@ def test_review_repair_suite_is_selected_and_conditionally_executed() -> None: assert workflow.count("runs-on:") == 1 +@pytest.mark.parametrize( + "changed_path", + ( + "scripts/ci/run_opencode_review_model_pool.sh", + "scripts/ci/opencode_failure_envelope.py", + "tests/test_opencode_model_pool_runner.py", + ), +) +def test_opencode_failure_paths_start_and_select_the_owned_suite( + changed_path: str, +) -> None: + """Every failure-envelope delta must execute its exact owned contract suite.""" + workflow = _workflow_text() + trigger = workflow.split("on:\n", 1)[1].split("\nconcurrency:\n", 1)[0] + assert f' - "{changed_path}"' in trigger + + selector = workflow.split(' case "$changed_path" in\n', 1)[1].split( + " esac", 1 + )[0] + result = subprocess.run( + [ + "bash", + "--noprofile", + "--norc", + "-e", + "-o", + "pipefail", + "-c", + 'IFS= read -r changed_path\nopencode_suite=false\n' + 'case "$changed_path" in\n' + + selector + + 'esac\nprintf "%s" "$opencode_suite"\n', + ], + input=changed_path + "\n", + text=True, + capture_output=True, + check=True, + ) + assert result.stdout == "true" + assert result.stderr == "" + assert "python -m pytest -q tests/test_opencode_model_pool_runner.py" in workflow + assert "--cov=scripts.ci.opencode_failure_envelope" in workflow + assert "--cov-branch" in workflow + assert "--cov-fail-under=100" in workflow + assert "python -m interrogate --fail-under 100" in workflow + + @pytest.mark.parametrize( ("changed_path", "starts_runner", "review_repair", "queue"), ( diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 321d25bd57..8b2579e0c9 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1890,6 +1890,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): in workflow ) assert "scripts/ci/run_opencode_review_model_pool.sh | \\" in workflow + assert "scripts/ci/opencode_failure_envelope.py | \\" in workflow assert ( "ContextualWisdomLab/.github:tests/test_javascript_coverage_gate.py | \\" in workflow diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index f1fc81c65d..fc0286bce0 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -389,7 +389,7 @@ def test_failed_provider_logs_bounded_reason_and_redacts_credentials( assert "json-bytes=" in result.stdout assert "stderr-bytes=" in result.stdout assert "provider-controlled content suppressed" in result.stdout - assert "ProviderAuthError" not in result.stdout + assert "exception=ProviderAuthError" in result.stdout assert "request failed" not in result.stdout assert fake_bearer_token not in result.stdout assert fake_openai_token not in result.stdout @@ -402,10 +402,11 @@ def test_failed_provider_without_reason_logs_explicit_absence(tmp_path: Path) -> result = run_failed_model(tmp_path) assert result.returncode == 1 - assert ( - "OpenCode provider failure metadata: class=no-provider-detail " - "json-bytes=0 stderr-bytes=0; provider-controlled content suppressed." - ) in result.stdout + assert "class=no-provider-detail json-bytes=0 stderr-bytes=0" in result.stdout + assert "phase=unknown reason=no_provider_detail provider=unknown" in result.stdout + assert "http-status=unknown exception=unknown" in result.stdout + assert "served-model=unknown" in result.stdout + assert "provider-controlled content suppressed" in result.stdout @pytest.mark.parametrize( From 14f3740a94529ecd21ede67bbaef350b6627a15d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 22:26:11 +0900 Subject: [PATCH 03/66] fix(opencode): preserve bounded provider failure causes Closes #2112 --- .../agent-review-runtime-quality-ci.yml | 28 ++ .../workflows/opencode-review-dispatch.yml | 2 + CHANGELOG.md | 13 + .../opencode-provider-failure-envelope.md | 64 +++++ docs/product-technical-gap-baseline.md | 25 ++ scripts/ci/opencode_failure_envelope.py | 231 ++++++++++++++++ scripts/ci/run_opencode_review_model_pool.sh | 40 +-- ...nt_review_runtime_quality_consolidation.py | 6 +- tests/test_opencode_agent_contract.py | 9 +- tests/test_opencode_failure_envelope.py | 259 ++++++++++++++++++ 10 files changed, 640 insertions(+), 37 deletions(-) create mode 100644 docs/doctoring/opencode-provider-failure-envelope.md create mode 100644 scripts/ci/opencode_failure_envelope.py create mode 100644 tests/test_opencode_failure_envelope.py diff --git a/.github/workflows/agent-review-runtime-quality-ci.yml b/.github/workflows/agent-review-runtime-quality-ci.yml index 6c6efc3dd1..7084226ae4 100644 --- a/.github/workflows/agent-review-runtime-quality-ci.yml +++ b/.github/workflows/agent-review-runtime-quality-ci.yml @@ -14,6 +14,11 @@ on: - "docs/doctoring/noema-review-token-lifetime.md" - "docs/product-technical-gap-baseline.md" - ".github/workflows/opencode-review-dispatch.yml" + - "scripts/ci/run_opencode_review_model_pool.sh" + - "scripts/ci/opencode_failure_envelope.py" + - "tests/test_opencode_model_pool_runner.py" + - "tests/test_opencode_failure_envelope.py" + - "docs/doctoring/opencode-provider-failure-envelope.md" - "scripts/ci/ensure_rust_llvm19.sh" - "tests/test_opencode_rust_coverage_toolchain_contract.py" - "scripts/ci/materialize_base_javascript_packages.py" @@ -185,6 +190,11 @@ jobs: noema_suite=true ;; .github/workflows/opencode-review-dispatch.yml|\ + scripts/ci/run_opencode_review_model_pool.sh|\ + scripts/ci/opencode_failure_envelope.py|\ + tests/test_opencode_model_pool_runner.py|\ + tests/test_opencode_failure_envelope.py|\ + docs/doctoring/opencode-provider-failure-envelope.md|\ scripts/ci/ensure_rust_llvm19.sh|\ tests/test_opencode_rust_coverage_toolchain_contract.py|\ scripts/ci/materialize_base_javascript_packages.py|\ @@ -357,6 +367,24 @@ jobs: python -m pytest -q tests/test_javascript_materializer_docstrings.py python -m compileall -q scripts/ci/materialize_base_javascript_packages.py tests/test_javascript_materializer_docstrings.py + - name: Verify OpenCode provider failure envelope + if: steps.affected_suites.outputs.opencode == 'true' + run: | + set -euo pipefail + PYTHONPATH=. python -m pytest -q \ + --cov=scripts.ci.opencode_failure_envelope \ + --cov-branch \ + --cov-fail-under=100 \ + tests/test_opencode_failure_envelope.py \ + tests/test_opencode_model_pool_runner.py + python -m interrogate --fail-under 100 \ + scripts/ci/opencode_failure_envelope.py + python -m compileall -q \ + scripts/ci/opencode_failure_envelope.py \ + tests/test_opencode_failure_envelope.py \ + tests/test_opencode_model_pool_runner.py + bash -n scripts/ci/run_opencode_review_model_pool.sh + - name: Verify exact-head path policy and syntax if: steps.affected_suites.outputs.strix == 'true' env: diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d86497b3f4..d8c677b874 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -2641,6 +2641,7 @@ jobs: ContextualWisdomLab/.github:scripts/ci/pr_head_replay_guard.py | \ ContextualWisdomLab/.github:scripts/ci/pr_review_merge_scheduler.py | \ ContextualWisdomLab/.github:scripts/ci/run_opencode_review_model_pool.sh | \ + ContextualWisdomLab/.github:scripts/ci/opencode_failure_envelope.py | \ ContextualWisdomLab/.github:scripts/ci/opencode_review_normalize_output.py | \ ContextualWisdomLab/.github:scripts/ci/strix_quick_gate.sh | \ ContextualWisdomLab/.github:scripts/ci/validate_opencode_failed_check_review.sh | \ @@ -2649,6 +2650,7 @@ jobs: ContextualWisdomLab/.github:tests/test_materialize_base_javascript_packages.py | \ ContextualWisdomLab/.github:tests/test_opencode_agent_contract.py | \ ContextualWisdomLab/.github:tests/test_opencode_model_pool_runner.py | \ + ContextualWisdomLab/.github:tests/test_opencode_failure_envelope.py | \ ContextualWisdomLab/.github:tests/test_pr_head_replay_guard.py | \ ContextualWisdomLab/.github:tests/test_pr_review_fix_scheduler_coverage.py | \ ContextualWisdomLab/.github:tests/test_pr_review_merge_scheduler.py | \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 707c18532e..e11c6bb266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +### OpenCode failures retain bounded causal telemetry + +- `run_opencode_review_model_pool.sh` now measures each failed invocation and + delegates its diagnostic to `opencode_failure_envelope.py`. The parser reads + only the bounded OpenCode error event and the gateway's canonical + `error.detail` receipt, then emits explicit phase, normalized cause, + provider, HTTP status, exception class, duration, and served-model fields. + Raw provider messages, bodies, prompts, credentials, headers, and arbitrary + nested values remain suppressed; malformed or missing fields become fixed + classifications or `unknown`, and review exhaustion remains fail-closed. + The dedicated runtime-quality lane now owns the runner, parser, and fixtures + with 100% statement/branch and public-doc coverage. Refs #2112. + ### Failed-check finding names the Strix sandbox instead of the gateway - `opencode-review-dispatch.yml`'s `emit_strix_provider_failure_finding` rendered one fixed finding for every `STRIX_PROVIDER_UNAVAILABLE` line, whose Root cause read "The contextual-orchestrator gateway or its discovered provider pool was unavailable for this run". `#1953` had just given the Strix sandbox bootstrap failure its own second verdict token (`STRIX_SANDBOX_UNAVAILABLE`) precisely because that attribution is wrong for it -- the sandbox container never reaches its Caido proxy, so the run dies before the gateway serves anything -- and this consumer re-applied the wrong attribution one step downstream, into the review findings and the failure census. The emitter now branches on the second token: a sandbox verdict gets a finding that names Strix's sandbox, says the verdict does not name the gateway, and tells the reader not to change gateway or provider configuration on its strength. A `STRIX_PROVIDER_UNAVAILABLE` line without the token keeps its existing text verbatim, so the gateway class has no regression surface. No test covered this finding text at all before (`gateway or its discovered provider pool` matched nothing under `tests/`); `tests/test_opencode_dispatch_strix_sandbox_finding.py` now runs the production emitter from the published run block and pins both directions plus the no-signal case. Refs #1953, #1935. diff --git a/docs/doctoring/opencode-provider-failure-envelope.md b/docs/doctoring/opencode-provider-failure-envelope.md new file mode 100644 index 0000000000..1f8220ebba --- /dev/null +++ b/docs/doctoring/opencode-provider-failure-envelope.md @@ -0,0 +1,64 @@ +# OpenCode provider-failure envelope + +## Problem and exact evidence + +On 2026-09-12, `.github` PR +[`#2106`](https://github.com/ContextualWisdomLab/.github/pull/2106) at exact +head `24bb6591ab7df23558cb793b4af60c567ff9da97` reached the single required +`contextual-orchestrator/orchestrator/free` model route in OpenCode run +[`34693400612`](https://github.com/ContextualWisdomLab/.github/actions/runs/34693400612). +The request failed after the sidecar and route preflight had succeeded, but the +only surviving causal evidence was `class=provider-error`, two byte counts, +and a statement that provider content was suppressed. That was enough to keep +the review fail-closed, but not enough to distinguish queue admission, HTTP +429/5xx, request size, malformed JSON, route exhaustion, or missing serving +identity. Issue +[`#2112`](https://github.com/ContextualWisdomLab/.github/issues/2112) owns the +repair. + +## Constraints + +- OpenCode, Noema, and Strix keep the single `orchestrator/free` gateway route; + no provider/model/group override or paid fallback is introduced. +- Telemetry is diagnostic only. It cannot produce approval, clean evidence, a + retry, a timeout, or a merge bypass. +- Provider-controlled messages, bodies, prompts, credentials, headers, source + text, and arbitrary nested payloads never reach stdout, status text, or + annotations. +- Values that survive are bounded scalar identifiers or validated HTTP status + numbers. Missing or invalid fields are explicit `unknown` values. + +## Alternatives and decision + +Keeping the previous byte-count-only line was rejected because it preserves +secrecy at the cost of causal attribution. Printing the raw OpenCode event or +gateway response was rejected because public `pull_request_target` logs cannot +safely carry provider-controlled text. Adding caller-side retries or an +elapsed-time diagnosis was rejected because the gateway owns routing and the +observed five-second failure did not prove a timeout. + +The selected design adds a small standard-library parser at the OpenCode +adapter boundary. It reads at most 65,537 bytes from each failure artifact and +accepts only an OpenCode `type=error` event. From the gateway response it reads +only the canonical `error.detail`/`error_detail` receipt and its last bounded +attempt. The emitted line preserves class, phase, normalized reason, provider, +HTTP status, exception class, elapsed seconds, served model, and artifact byte +counts. Malformed JSON and malformed Unicode fail closed to bounded metadata. + +## Executable evidence, risks, and effects + +The production launcher fixtures cover HTTP 429/queue capacity, provider 503, +non-JSON response bodies, HTTP 413 request admission, no eligible route, absent +served-model metadata, and secret-bearing ignored fields. Unit tests cover all +parser statements and branches, and the consolidated runtime-quality workflow +selects this suite whenever the launcher, parser, fixture, or this authority +record changes. + +The remaining risk is semantic drift in the gateway receipt. Unknown fields +are deliberately not guessed or copied; a future versioned schema change must +add a failing fixture before extending the allowlist. Operators can now route a +429/queue failure to capacity policy, a 5xx to the gateway/provider boundary, +a 413 to request admission, and malformed JSON to the response adapter without +reading secret-bearing bodies. Until exact-head hosted checks, independent +review, protected-main integration, and an unchanged-head replay of #2106 are +complete, this repair remains Proposed rather than released evidence. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1cc9e20313..1f554e0d41 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3353,3 +3353,28 @@ queries the check-runs API at its own time, order-independently. The implementin their change was safe because they had scoped it narrowly, not because they had checked for the name collision — which is the more useful lesson: **a job name is unique only within one workflow file, and the same name in another file can carry the opposite safety property.** + +## OpenCode provider-failure causal envelope — 2026-09-12 + +**Status: Proposed; owner repair implemented on the issue branch, not yet +protected or released.** `.github#2106@24bb6591ab7df23558cb793b4af60c567ff9da97` +had five exact-head security/runtime checks succeed, then OpenCode run +`34693400612` exhausted after emitting only `class=provider-error` and byte +counts. The absence of safe phase/provider/status/model evidence made the +failure causally ambiguous; it did not prove the separate timeout defect. + +Issue `.github#2112` now has an executable RED→GREEN owner repair. The OpenCode +adapter parses only a bounded error event and canonical gateway receipt, emits +explicit class/phase/reason/provider/status/exception/duration/served-model +scalars, suppresses raw provider content, and remains fail-closed. Production +fixtures cover 429, 5xx, malformed JSON, 413, pool exhaustion, missing serving +identity, and secret-bearing ignored fields. The previously missing CI +ownership is also repaired: launcher/parser/test/doc changes select the +dedicated runtime-quality suite, which enforces 100% parser statement/branch +and public-doc coverage. + +**Remaining action:** obtain exact-head hosted checks and independent review, +merge normally to protected `main`, then replay #2106 unchanged. Only that +consumer replay can show whether the next real failure contains enough bounded +causal evidence; this Proposed branch is not immutable release or production +proof. diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py new file mode 100644 index 0000000000..f822b49e00 --- /dev/null +++ b/scripts/ci/opencode_failure_envelope.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +"""Emit bounded, redaction-safe metadata for one failed OpenCode invocation.""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path +from typing import Any + + +MAX_FAILURE_FILE_BYTES = 65_536 +MAX_GATEWAY_BODY_BYTES = 32_768 +SAFE_VALUE_RE = re.compile(r"[A-Za-z0-9_./:-]{1,128}") + + +def _read_bounded(path: Path) -> tuple[bytes, int]: + """Read a bounded prefix while retaining the file's non-secret byte count.""" + try: + byte_count = path.stat().st_size + with path.open("rb") as stream: + return stream.read(MAX_FAILURE_FILE_BYTES + 1), byte_count + except OSError: + return b"", 0 + + +def _safe_value(value: Any) -> str | None: + """Return one conservative public-log token or no value.""" + if not isinstance(value, str): + return None + candidate = value.strip() + return candidate if SAFE_VALUE_RE.fullmatch(candidate) else None + + +def _safe_exception(value: Any) -> str | None: + """Return a bounded Python-style exception identifier or no value.""" + if not isinstance(value, str) or len(value) > 64 or not value.isidentifier(): + return None + return value + + +def _safe_http_status(value: Any) -> int | None: + """Return a valid HTTP status while excluding booleans and free text.""" + if type(value) is int and 100 <= value <= 599: + return value + if isinstance(value, str) and len(value) == 3 and value.isascii() and value.isdigit(): + status = int(value) + return status if 100 <= status <= 599 else None + return None + + +def _last_error_event(raw: bytes) -> dict[str, Any] | None: + """Return the last bounded OpenCode JSON-lines error event.""" + if len(raw) > MAX_FAILURE_FILE_BYTES: + return None + last: dict[str, Any] | None = None + try: + text = raw.decode("utf-8") + except UnicodeDecodeError: + return None + for line in text.splitlines(): + try: + event = json.loads(line) + except (json.JSONDecodeError, TypeError, ValueError): + continue + if isinstance(event, dict) and event.get("type") == "error": + last = event + return last + + +def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: + """Extract the canonical gateway error detail and flag malformed bodies.""" + body_value = next( + (data.get(key) for key in ("responseBody", "response_body", "body") if key in data), + None, + ) + if body_value is None: + payload: Any = data + malformed = False + elif isinstance(body_value, dict): + payload = body_value + malformed = False + elif isinstance(body_value, str): + try: + body_bytes = body_value.encode("utf-8") + except UnicodeEncodeError: + return {}, True + if len(body_bytes) > MAX_GATEWAY_BODY_BYTES: + return {}, True + try: + payload = json.loads(body_value) + malformed = not isinstance(payload, dict) + except (json.JSONDecodeError, TypeError, ValueError): + return {}, True + else: + return {}, True + if not isinstance(payload, dict): + return {}, malformed + error = payload.get("error") + if isinstance(error, dict) and isinstance(error.get("detail"), dict): + return error["detail"], malformed + detail = payload.get("error_detail") + if isinstance(detail, dict): + return detail, malformed + direct = payload.get("detail") + return (direct, malformed) if isinstance(direct, dict) else ({}, malformed) + + +def _failure_class( + raw_json: bytes, + raw_stderr: bytes, + *, + status: int | None, + reason: str | None, + malformed_body: bool, + has_event: bool, +) -> str: + """Normalize one failure class without returning provider-controlled text.""" + searchable = (raw_json + b"\n" + raw_stderr).lower() + normalized_reason = (reason or "").lower() + if status == 413 or b"request_too_large" in searchable or b"request body too large" in searchable: + return "request-too-large" + if b"contextoverflowerror" in searchable or b"tokens_limit_reached" in searchable or b"context window" in searchable: + return "context-window" + if status == 402 or b"insufficient credits" in searchable or b"payment required" in searchable: + return "credit-exhausted" + if b"budget limit" in searchable or b"insufficient_quota" in searchable or b"quota exceeded" in searchable: + return "quota-or-budget" + if normalized_reason in {"eligible_candidates_exhausted", "no_eligible_route", "model_pool_exhausted"}: + return "model-pool-exhausted" + if b"model_not_found" in searchable or b"model not found" in searchable or b"no endpoints" in searchable: + return "model-unavailable" + if status == 429 or b"rate_limit" in searchable or b"rate limit" in searchable or b"too many requests" in searchable: + return "rate-limit" + if status in {401, 403} or b"permission denied" in searchable or b"authentication" in searchable or b"authorization" in searchable: + return "authentication-or-permission" + if b"timed out" in searchable or b"timeout" in searchable: + return "timeout" + if status is not None and 500 <= status <= 599: + return "provider-5xx" + if malformed_body or (raw_json and not has_event): + return "malformed-response" + if raw_json or raw_stderr: + return "provider-error" + return "no-provider-detail" + + +def format_failure_metadata( + json_path: Path, stderr_path: Path, duration_seconds: int +) -> str: + """Format one stable diagnostic line from bounded OpenCode failure artifacts.""" + raw_json, json_bytes = _read_bounded(json_path) + raw_stderr, stderr_bytes = _read_bounded(stderr_path) + event = _last_error_event(raw_json) + error = event.get("error") if isinstance(event, dict) else None + error = error if isinstance(error, dict) else {} + data = error.get("data") + data = data if isinstance(data, dict) else {} + detail, malformed_body = _gateway_detail(data) + attempts = detail.get("attempts") + last_attempt = ( + attempts[-1] + if isinstance(attempts, list) + and attempts + and len(attempts) <= 64 + and isinstance(attempts[-1], dict) + else {} + ) + reason = next( + ( + safe + for safe in ( + _safe_value(detail.get("terminal_reason")), + _safe_value(detail.get("stop_reason")), + _safe_value(detail.get("error_code")), + _safe_value(data.get("code")), + ) + if safe is not None + ), + None, + ) + status = next( + ( + safe + for safe in ( + _safe_http_status(data.get("statusCode")), + _safe_http_status(data.get("status_code")), + _safe_http_status(last_attempt.get("provider_status")), + ) + if safe is not None + ), + None, + ) + failure_class = _failure_class( + raw_json, + raw_stderr, + status=status, + reason=reason, + malformed_body=malformed_body, + has_event=event is not None, + ) + normalized_reason = reason or failure_class.replace("-", "_") + fields = { + "class": failure_class, + "json-bytes": str(json_bytes), + "stderr-bytes": str(stderr_bytes), + "phase": _safe_value(last_attempt.get("phase")) or _safe_value(detail.get("phase")) or "unknown", + "reason": normalized_reason, + "provider": _safe_value(last_attempt.get("provider_name")) or _safe_value(last_attempt.get("provider")) or "unknown", + "http-status": str(status) if status is not None else "unknown", + "exception": _safe_exception(error.get("name")) or "unknown", + "duration-seconds": str(max(0, duration_seconds)), + "served-model": _safe_value(detail.get("model")) or "unknown", + } + rendered = " ".join(f"{key}={value}" for key, value in fields.items()) + return f"OpenCode provider failure metadata: {rendered}; provider-controlled content suppressed." + + +def main(argv: list[str] | None = None) -> int: + """Print one redaction-safe failure line for the shell runner.""" + arguments = sys.argv[1:] if argv is None else argv + if len(arguments) != 3 or not arguments[2].isascii() or not arguments[2].isdigit(): + print("usage: opencode_failure_envelope.py JSON STDERR DURATION_SECONDS", file=sys.stderr) + return 2 + print(format_failure_metadata(Path(arguments[0]), Path(arguments[1]), int(arguments[2]))) + return 0 + + +if __name__ == "__main__": # pragma: no cover - exercised through the shell contract + raise SystemExit(main()) diff --git a/scripts/ci/run_opencode_review_model_pool.sh b/scripts/ci/run_opencode_review_model_pool.sh index 80f57d1d43..c53035806e 100644 --- a/scripts/ci/run_opencode_review_model_pool.sh +++ b/scripts/ci/run_opencode_review_model_pool.sh @@ -267,39 +267,10 @@ is_credit_exhausted_failure() { emit_sanitized_opencode_failure_detail() { local opencode_json_file="$1" local opencode_stderr_file="$2" - local json_bytes stderr_bytes failure_class + local duration_seconds="${3:-0}" - json_bytes=0 - stderr_bytes=0 - if [ -s "$opencode_json_file" ]; then - json_bytes="$(wc -c <"$opencode_json_file" | tr -d ' ')" - fi - if [ -s "$opencode_stderr_file" ]; then - stderr_bytes="$(wc -c <"$opencode_stderr_file" | tr -d ' ')" - fi - - failure_class="unclassified" - if grep -Eiq 'ContextOverflowError|tokens_limit_reached|Request body too large|context window' "$opencode_json_file" "$opencode_stderr_file" 2>/dev/null; then - failure_class="context-window" - elif grep -Eiq 'insufficient credits|payment required|"code"[[:space:]]*:[[:space:]]*402' "$opencode_json_file" "$opencode_stderr_file" 2>/dev/null; then - failure_class="credit-exhausted" - elif grep -Eiq 'budget limit|insufficient_quota|quota exceeded' "$opencode_json_file" "$opencode_stderr_file" 2>/dev/null; then - failure_class="quota-or-budget" - elif grep -Eiq 'model_not_found|model not found|ModelNotFoundError|not a valid model|no endpoints' "$opencode_json_file" "$opencode_stderr_file" 2>/dev/null; then - failure_class="model-unavailable" - elif grep -Eiq 'rate.?limit|too many requests|(^|[^0-9])429([^0-9]|$)' "$opencode_json_file" "$opencode_stderr_file" 2>/dev/null; then - failure_class="rate-limit" - elif grep -Eiq 'permission denied|authentication|authorization|(^|[^0-9])(401|403)([^0-9]|$)' "$opencode_json_file" "$opencode_stderr_file" 2>/dev/null; then - failure_class="authentication-or-permission" - elif grep -Eiq 'timed? ?out|timeout' "$opencode_json_file" "$opencode_stderr_file" 2>/dev/null; then - failure_class="timeout" - elif [ "$json_bytes" -gt 0 ] || [ "$stderr_bytes" -gt 0 ]; then - failure_class="provider-error" - else - failure_class="no-provider-detail" - fi - printf 'OpenCode provider failure metadata: class=%s json-bytes=%s stderr-bytes=%s; provider-controlled content suppressed.\n' \ - "$failure_class" "$json_bytes" "$stderr_bytes" + python3 "$GITHUB_WORKSPACE/scripts/ci/opencode_failure_envelope.py" \ + "$opencode_json_file" "$opencode_stderr_file" "$duration_seconds" } emit_rejected_opencode_artifact_metadata() { @@ -397,12 +368,13 @@ run_one_model_attempt() { local opencode_json_file="$7" local opencode_export_file="$8" local export_timeout_seconds opencode_status session_id opencode_stderr_file - local opencode_pid fatal_kill_grace_seconds fatal_poll_seconds + local opencode_pid fatal_kill_grace_seconds fatal_poll_seconds attempt_started_seconds export_timeout_seconds="${OPENCODE_EXPORT_TIMEOUT_SECONDS:-120}" fatal_poll_seconds="${OPENCODE_FATAL_ERROR_POLL_SECONDS:-5}" fatal_kill_grace_seconds="${OPENCODE_FATAL_KILL_GRACE_SECONDS:-5}" opencode_stderr_file="${opencode_json_file}.stderr" + attempt_started_seconds="$SECONDS" rm -f "$opencode_json_file" "$opencode_stderr_file" "$opencode_export_file" "$candidate_output_file" set +e @@ -440,7 +412,7 @@ run_one_model_attempt() { set -e if [ "$opencode_status" -ne 0 ]; then printf 'OpenCode %s attempt %s/%s failed with exit %s.\n' "$model_candidate" "$attempt" "$attempts" "$opencode_status" - emit_sanitized_opencode_failure_detail "$opencode_json_file" "$opencode_stderr_file" + emit_sanitized_opencode_failure_detail "$opencode_json_file" "$opencode_stderr_file" "$((SECONDS - attempt_started_seconds))" if is_fatal_provider_failure "$opencode_json_file"; then printf 'OpenCode %s attempt %s/%s hit a fatal provider error (context window, token budget, quota, or model unavailable); skipping remaining attempts for this model.\n' "$model_candidate" "$attempt" "$attempts" return 2 diff --git a/tests/test_agent_review_runtime_quality_consolidation.py b/tests/test_agent_review_runtime_quality_consolidation.py index f589ff499c..9cc7483f04 100644 --- a/tests/test_agent_review_runtime_quality_consolidation.py +++ b/tests/test_agent_review_runtime_quality_consolidation.py @@ -159,6 +159,8 @@ def test_review_repair_suite_is_selected_and_conditionally_executed() -> None: "scripts/ci/run_opencode_review_model_pool.sh", "scripts/ci/opencode_failure_envelope.py", "tests/test_opencode_model_pool_runner.py", + "tests/test_opencode_failure_envelope.py", + "docs/doctoring/opencode-provider-failure-envelope.md", ), ) def test_opencode_failure_paths_start_and_select_the_owned_suite( @@ -193,7 +195,9 @@ def test_opencode_failure_paths_start_and_select_the_owned_suite( ) assert result.stdout == "true" assert result.stderr == "" - assert "python -m pytest -q tests/test_opencode_model_pool_runner.py" in workflow + assert "Verify OpenCode provider failure envelope" in workflow + assert "tests/test_opencode_model_pool_runner.py" in workflow + assert "tests/test_opencode_failure_envelope.py" in workflow assert "--cov=scripts.ci.opencode_failure_envelope" in workflow assert "--cov-branch" in workflow assert "--cov-fail-under=100" in workflow diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 8b2579e0c9..aa334e8cc6 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1832,6 +1832,9 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): model_pool_runner = Path("scripts/ci/run_opencode_review_model_pool.sh").read_text( encoding="utf-8" ) + failure_envelope = Path( + "scripts/ci/opencode_failure_envelope.py" + ).read_text(encoding="utf-8") assert "assert_reasoning_effort_for_candidate" in model_pool_runner assert "assert_opencode_reasoning_effort.py" in model_pool_runner assert "--config opencode.jsonc" in model_pool_runner @@ -1856,8 +1859,9 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert "has no model inference timeout" in model_pool_runner assert "timed out after %ss" not in model_pool_runner assert "emit_sanitized_opencode_failure_detail" in model_pool_runner - assert "OpenCode provider failure metadata" in model_pool_runner - assert "provider-controlled content suppressed" in model_pool_runner + assert "opencode_failure_envelope.py" in model_pool_runner + assert "OpenCode provider failure metadata" in failure_envelope + assert "provider-controlled content suppressed" in failure_envelope assert 'cat "$opencode_json_file"' not in model_pool_runner assert 'cat "$opencode_export_file"' not in model_pool_runner assert 'cat "$candidate_output_file"' not in model_pool_runner @@ -1891,6 +1895,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): ) assert "scripts/ci/run_opencode_review_model_pool.sh | \\" in workflow assert "scripts/ci/opencode_failure_envelope.py | \\" in workflow + assert "tests/test_opencode_failure_envelope.py | \\" in workflow assert ( "ContextualWisdomLab/.github:tests/test_javascript_coverage_gate.py | \\" in workflow diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py new file mode 100644 index 0000000000..dc2a8b911a --- /dev/null +++ b/tests/test_opencode_failure_envelope.py @@ -0,0 +1,259 @@ +"""Unit coverage for bounded OpenCode provider-failure metadata.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from scripts.ci import opencode_failure_envelope as envelope + + +def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> None: + """Missing artifacts are empty and large artifacts retain their true size.""" + assert envelope._read_bounded(tmp_path / "missing") == (b"", 0) + large = tmp_path / "large" + large.write_bytes(b"x" * (envelope.MAX_FAILURE_FILE_BYTES + 2)) + raw, byte_count = envelope._read_bounded(large) + assert len(raw) == envelope.MAX_FAILURE_FILE_BYTES + 1 + assert byte_count == envelope.MAX_FAILURE_FILE_BYTES + 2 + assert envelope._last_error_event(raw) is None + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + ("openrouter/model:free", "openrouter/model:free"), + (" unsafe value ", None), + (1, None), + ], +) +def test_safe_value_accepts_only_bounded_log_tokens(value: object, expected: str | None) -> None: + """Arbitrary provider text cannot become a public-log token.""" + assert envelope._safe_value(value) == expected + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + ("HTTPError", "HTTPError"), + ("bad error", None), + ("x" * 65, None), + (7, None), + ], +) +def test_safe_exception_accepts_only_short_identifiers( + value: object, expected: str | None +) -> None: + """Exception telemetry is a type identifier, never an exception message.""" + assert envelope._safe_exception(value) == expected + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + (429, 429), + ("503", 503), + (True, None), + (99, None), + (600, None), + ("429 ", None), + ("abc", None), + ], +) +def test_safe_http_status_rejects_non_http_values( + value: object, expected: int | None +) -> None: + """Only three-digit HTTP status values survive normalization.""" + assert envelope._safe_http_status(value) == expected + + +def test_last_error_event_uses_last_valid_error_and_rejects_bad_utf8() -> None: + """JSON-lines noise is ignored while invalid UTF-8 fails closed.""" + raw = ( + b"not-json\n" + b'{"type":"text"}\n' + b'{"type":"error","error":{"name":"First"}}\n' + b'{"type":"error","error":{"name":"Last"}}\n' + ) + assert envelope._last_error_event(raw) == { + "type": "error", + "error": {"name": "Last"}, + } + assert envelope._last_error_event(b"\xff") is None + + +@pytest.mark.parametrize( + ("data", "expected", "malformed"), + [ + ({"detail": {"phase": "direct"}}, {"phase": "direct"}, False), + ({"error_detail": {"phase": "legacy"}}, {"phase": "legacy"}, False), + ( + {"body": {"error": {"detail": {"phase": "mapping"}}}}, + {"phase": "mapping"}, + False, + ), + ( + {"response_body": '{"error":{"detail":{"phase":"json"}}}'}, + {"phase": "json"}, + False, + ), + ({"responseBody": "[]"}, {}, True), + ({"responseBody": "not-json"}, {}, True), + ({"responseBody": "\ud800"}, {}, True), + ({"responseBody": "x" * (envelope.MAX_GATEWAY_BODY_BYTES + 1)}, {}, True), + ({"body": []}, {}, True), + ({"body": {}}, {}, False), + ], +) +def test_gateway_detail_accepts_only_known_bounded_shapes( + data: dict[str, object], expected: dict[str, object], malformed: bool +) -> None: + """Only canonical detail containers are available to the formatter.""" + assert envelope._gateway_detail(data) == (expected, malformed) + + +@pytest.mark.parametrize( + ("raw_json", "raw_stderr", "status", "reason", "malformed", "event", "expected"), + [ + (b"request_too_large", b"", None, None, False, True, "request-too-large"), + (b"ContextOverflowError", b"", None, None, False, True, "context-window"), + (b"", b"payment required", None, None, False, False, "credit-exhausted"), + (b"insufficient_quota", b"", None, None, False, True, "quota-or-budget"), + (b"", b"", None, "no_eligible_route", False, True, "model-pool-exhausted"), + (b"model_not_found", b"", None, None, False, True, "model-unavailable"), + (b"", b"", 429, None, False, True, "rate-limit"), + (b"", b"permission denied", None, None, False, False, "authentication-or-permission"), + (b"", b"timed out", None, None, False, False, "timeout"), + (b"", b"", 502, None, False, True, "provider-5xx"), + (b"{}", b"", None, None, True, True, "malformed-response"), + (b"{}", b"", None, None, False, False, "malformed-response"), + (b"{}", b"", None, None, False, True, "provider-error"), + (b"", b"", None, None, False, False, "no-provider-detail"), + ], +) +def test_failure_class_preserves_distinct_safe_causes( + raw_json: bytes, + raw_stderr: bytes, + status: int | None, + reason: str | None, + malformed: bool, + event: bool, + expected: str, +) -> None: + """Each accepted causal category remains distinguishable.""" + assert ( + envelope._failure_class( + raw_json, + raw_stderr, + status=status, + reason=reason, + malformed_body=malformed, + has_event=event, + ) + == expected + ) + + +def test_format_failure_metadata_handles_direct_detail_and_string_status( + tmp_path: Path, +) -> None: + """Direct gateway detail fields produce one deterministic safe line.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "name": "HTTPError", + "data": { + "status_code": "503", + "detail": { + "phase": "response_error", + "stop_reason": "provider_unavailable", + "model": "nvidia/model:free", + "attempts": [ + { + "provider": "nvidia_nim", + "phase": "connecting", + } + ], + }, + }, + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 5) + + assert "class=provider-5xx" in rendered + assert "phase=connecting" in rendered + assert "reason=provider_unavailable" in rendered + assert "provider=nvidia_nim" in rendered + assert "http-status=503" in rendered + assert "exception=HTTPError" in rendered + assert "duration-seconds=5" in rendered + assert "served-model=nvidia/model:free" in rendered + + +def test_format_failure_metadata_limits_attempts_and_defaults_fields( + tmp_path: Path, +) -> None: + """Oversized attempt arrays and unsafe scalars degrade to explicit absence.""" + secret = "github" + "_pat_" + "NEVERPRINTTHISVALUE123456" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "name": f"bad exception {secret}", + "data": { + "code": f"unsafe code {secret}", + "detail": { + "error_code": f"unsafe reason {secret}", + "attempts": [{}] * 65, + "model": f"unsafe model {secret}", + }, + }, + }, + } + ), + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, -4) + + assert "phase=unknown reason=provider_error provider=unknown" in rendered + assert "http-status=unknown exception=unknown duration-seconds=0" in rendered + assert "served-model=unknown" in rendered + assert secret not in rendered + + +def test_main_prints_metadata_and_rejects_invalid_arguments( + tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch +) -> None: + """The CLI has one strict invocation shape and delegates to the formatter.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text("", encoding="utf-8") + stderr_path.write_text("", encoding="utf-8") + assert envelope.main([str(json_path), str(stderr_path), "0"]) == 0 + assert "class=no-provider-detail" in capsys.readouterr().out + + assert envelope.main([str(json_path), str(stderr_path), "-1"]) == 2 + assert "usage:" in capsys.readouterr().err + monkeypatch.setattr( + envelope.sys, + "argv", + ["opencode_failure_envelope.py", str(json_path), str(stderr_path), "1"], + ) + assert envelope.main() == 0 From 4341566c4822239fcc99b5e965f1142f0b236a21 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 22:31:24 +0900 Subject: [PATCH 04/66] fix(ci): pair review dispatch with exact blob pin The runtime-quality lane correctly failed closed because this PR changes the trusted review-dispatch workflow without updating its independent Git blob identity. Pair the pin with exact dispatch blob d8c677b874c06181f11527301dfc111c02f80d5b so the existing anti-TOCTOU contract remains effective. Exact-tree verification: 185 passed, 1 skipped; opencode failure parser statement/branch coverage 100%; public-doc 100%; compileall, bash -n, and git diff --check GREEN. --- tests/test_pr_review_autofix_nvidia_nim_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 2e733ac9e9..d69449e2d5 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -17,7 +17,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "d86497b3f43bebbabbb4f504eb5132cdf3b7b293" +REVIEW_DISPATCH_BLOB_SHA = "d8c677b874c06181f11527301dfc111c02f80d5b" def _workflow_text(path: Path) -> str: From 10cd4129c63edc7cb56f091b106b131a3a436bbd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 22:50:36 +0900 Subject: [PATCH 05/66] test(opencode): follow failure envelope authority Exact-head Runtime Quality run 34696669250 passed the dispatch-blob pairing and the new provider-envelope suite, then failed one legacy static assertion that still searched the shell launcher for strings now owned by the bounded parser. Point those two assertions at the canonical parser while retaining all shell anti-replay assertions. Verification: test_strix_quick_gate PASS; bash -n and git diff --check GREEN. --- scripts/ci/test_strix_quick_gate.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 6ea00c099f..1a7e803a49 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -810,8 +810,8 @@ assert_opencode_review_uses_codegraph_and_contextual_orchestrator() { assert_file_contains "$workflow_file" "The publish gate re-runs source-backed validation against PR-head data" "opencode review publish gate validates model output against the PR-head worktree" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OpenCode %s attempt %s/%s failed with exit %s.' "opencode review logs per-model retry attempts" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "emit_sanitized_opencode_failure_detail" "opencode review logs a bounded provider reason after each failed attempt" - assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "OpenCode provider failure metadata" "opencode review labels provider failure classes in the check log" - assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "provider-controlled content suppressed" "opencode provider failure logging suppresses credential-bearing content" + assert_file_contains "$REPO_ROOT/scripts/ci/opencode_failure_envelope.py" "OpenCode provider failure metadata" "opencode review labels provider failure classes in the check log" + assert_file_contains "$REPO_ROOT/scripts/ci/opencode_failure_envelope.py" "provider-controlled content suppressed" "opencode provider failure logging suppresses credential-bearing content" assert_file_not_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'cat "$opencode_json_file"' "opencode review never replays provider JSON to the check log" assert_file_not_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'cat "$opencode_export_file"' "opencode review never replays provider exports to the check log" assert_file_not_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'cat "$candidate_output_file"' "opencode review never replays rejected assistant output to the check log" From 187f9fcc70d1f5597dd728c386ff88e10233f80c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 22:56:07 +0900 Subject: [PATCH 06/66] test(opencode): reject credential-shaped failure metadata --- tests/test_opencode_failure_envelope.py | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index dc2a8b911a..43b818163e 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -238,6 +238,45 @@ def test_format_failure_metadata_limits_attempts_and_defaults_fields( assert secret not in rendered + +def test_format_failure_metadata_rejects_credential_shaped_tokens( + tmp_path: Path, +) -> None: + """Structured identifiers cannot smuggle credential-shaped values into logs.""" + secret = "github" + "_pat_" + "NEVERPRINTTHISVALUE123456" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "name": secret, + "data": { + "detail": { + "phase": secret, + "terminal_reason": secret, + "model": secret, + "attempts": [{"provider_name": secret}], + } + }, + }, + } + ), + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert secret not in rendered + assert "phase=unknown" in rendered + assert "reason=provider_error" in rendered + assert "provider=unknown" in rendered + assert "exception=unknown" in rendered + assert "served-model=unknown" in rendered + + def test_main_prints_metadata_and_rejects_invalid_arguments( tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: From 0e7f7e4731da7efa4d694984125c3bce47eade53 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 22:58:13 +0900 Subject: [PATCH 07/66] fix(opencode): suppress credential-shaped failure fields --- scripts/ci/opencode_failure_envelope.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index f822b49e00..89631decf1 100644 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -13,6 +13,10 @@ MAX_FAILURE_FILE_BYTES = 65_536 MAX_GATEWAY_BODY_BYTES = 32_768 SAFE_VALUE_RE = re.compile(r"[A-Za-z0-9_./:-]{1,128}") +CREDENTIAL_SHAPE_RE = re.compile( + r"github_pat_|gh[pousr]_|sk-[A-Za-z0-9]|xox[baprs]-|nvapi-|AIza", + re.IGNORECASE, +) def _read_bounded(path: Path) -> tuple[bytes, int]: @@ -30,12 +34,22 @@ def _safe_value(value: Any) -> str | None: if not isinstance(value, str): return None candidate = value.strip() - return candidate if SAFE_VALUE_RE.fullmatch(candidate) else None + return ( + candidate + if SAFE_VALUE_RE.fullmatch(candidate) + and CREDENTIAL_SHAPE_RE.search(candidate) is None + else None + ) def _safe_exception(value: Any) -> str | None: """Return a bounded Python-style exception identifier or no value.""" - if not isinstance(value, str) or len(value) > 64 or not value.isidentifier(): + if ( + not isinstance(value, str) + or len(value) > 64 + or not value.isidentifier() + or CREDENTIAL_SHAPE_RE.search(value) is not None + ): return None return value From 7f9489eaa20a8ca3836630f402b2142d72d7252e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:01:30 +0900 Subject: [PATCH 08/66] test(opencode): reject prose-spoofed failure causes --- tests/test_opencode_failure_envelope.py | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 43b818163e..b4c62e4a95 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -277,6 +277,41 @@ def test_format_failure_metadata_rejects_credential_shaped_tokens( assert "served-model=unknown" in rendered + +def test_format_failure_metadata_ignores_provider_prose_for_causal_class( + tmp_path: Path, +) -> None: + """Untrusted event prose cannot override the structured gateway cause.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps({"type": "text", "text": "payment required; rate limit; timeout"}) + + "\n" + + json.dumps( + { + "type": "error", + "error": { + "name": "HTTPError", + "data": { + "statusCode": 502, + "detail": {"terminal_reason": "provider_unavailable"}, + "message": "payment required", + }, + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("authentication failed", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=provider-5xx" in rendered + assert "reason=provider_unavailable" in rendered + assert "class=credit-exhausted" not in rendered + assert "class=authentication-or-permission" not in rendered + def test_main_prints_metadata_and_rejects_invalid_arguments( tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: From 2b78ea6b6a052df5c1fbbf6a87f6292fc4b905b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:02:24 +0900 Subject: [PATCH 09/66] test(opencode): reject excessively deep gateway envelopes --- tests/test_opencode_failure_envelope.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index b4c62e4a95..1189e22cb0 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -114,6 +114,14 @@ def test_gateway_detail_accepts_only_known_bounded_shapes( assert envelope._gateway_detail(data) == (expected, malformed) + +def test_gateway_detail_fails_closed_on_excessive_json_depth() -> None: + """Deep provider envelopes cannot crash diagnostics with RecursionError.""" + deeply_nested = "[" * 2_000 + "0" + "]" * 2_000 + + assert envelope._gateway_detail({"responseBody": deeply_nested}) == ({}, True) + + @pytest.mark.parametrize( ("raw_json", "raw_stderr", "status", "reason", "malformed", "event", "expected"), [ From 2e2d61d2dc9bf1adacb12eddc0743f78ade69fc7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:02:50 +0900 Subject: [PATCH 10/66] test(opencode): reproduce deep gateway recursion --- tests/test_opencode_failure_envelope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 1189e22cb0..9c271f4075 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -117,7 +117,7 @@ def test_gateway_detail_accepts_only_known_bounded_shapes( def test_gateway_detail_fails_closed_on_excessive_json_depth() -> None: """Deep provider envelopes cannot crash diagnostics with RecursionError.""" - deeply_nested = "[" * 2_000 + "0" + "]" * 2_000 + deeply_nested = "[" * 10_000 + "0" + "]" * 10_000 assert envelope._gateway_detail({"responseBody": deeply_nested}) == ({}, True) From fe035a8ad1e3ebc28883db2bc00ed057518373c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:03:31 +0900 Subject: [PATCH 11/66] fix(opencode): derive failure class from structured receipt --- scripts/ci/opencode_failure_envelope.py | 75 +++++++++++++++++-------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 89631decf1..83f1b65d03 100644 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -18,6 +18,36 @@ re.IGNORECASE, ) +REASON_FAILURE_CLASSES = { + "request_too_large": "request-too-large", + "payload_too_large": "request-too-large", + "context_overflow": "context-window", + "context_window_exceeded": "context-window", + "tokens_limit_reached": "context-window", + "insufficient_credits": "credit-exhausted", + "payment_required": "credit-exhausted", + "budget_limit": "quota-or-budget", + "insufficient_quota": "quota-or-budget", + "quota_exceeded": "quota-or-budget", + "eligible_candidates_exhausted": "model-pool-exhausted", + "no_eligible_route": "model-pool-exhausted", + "model_pool_exhausted": "model-pool-exhausted", + "model_not_found": "model-unavailable", + "no_endpoints": "model-unavailable", + "rate_limit": "rate-limit", + "rate_limited": "rate-limit", + "too_many_requests": "rate-limit", + "queue_capacity": "rate-limit", + "permission_denied": "authentication-or-permission", + "authentication_failed": "authentication-or-permission", + "authorization_failed": "authentication-or-permission", + "timeout": "timeout", + "timed_out": "timeout", + "provider_timeout": "timeout", + "provider_unavailable": "provider-5xx", + "upstream_error": "provider-5xx", +} + def _read_bounded(path: Path) -> tuple[bytes, int]: """Read a bounded prefix while retaining the file's non-secret byte count.""" @@ -105,7 +135,7 @@ def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: try: payload = json.loads(body_value) malformed = not isinstance(payload, dict) - except (json.JSONDecodeError, TypeError, ValueError): + except (json.JSONDecodeError, RecursionError, TypeError, ValueError): return {}, True else: return {}, True @@ -130,29 +160,26 @@ def _failure_class( malformed_body: bool, has_event: bool, ) -> str: - """Normalize one failure class without returning provider-controlled text.""" - searchable = (raw_json + b"\n" + raw_stderr).lower() - normalized_reason = (reason or "").lower() - if status == 413 or b"request_too_large" in searchable or b"request body too large" in searchable: - return "request-too-large" - if b"contextoverflowerror" in searchable or b"tokens_limit_reached" in searchable or b"context window" in searchable: - return "context-window" - if status == 402 or b"insufficient credits" in searchable or b"payment required" in searchable: - return "credit-exhausted" - if b"budget limit" in searchable or b"insufficient_quota" in searchable or b"quota exceeded" in searchable: - return "quota-or-budget" - if normalized_reason in {"eligible_candidates_exhausted", "no_eligible_route", "model_pool_exhausted"}: - return "model-pool-exhausted" - if b"model_not_found" in searchable or b"model not found" in searchable or b"no endpoints" in searchable: - return "model-unavailable" - if status == 429 or b"rate_limit" in searchable or b"rate limit" in searchable or b"too many requests" in searchable: - return "rate-limit" - if status in {401, 403} or b"permission denied" in searchable or b"authentication" in searchable or b"authorization" in searchable: - return "authentication-or-permission" - if b"timed out" in searchable or b"timeout" in searchable: - return "timeout" - if status is not None and 500 <= status <= 599: - return "provider-5xx" + """Normalize one failure class from validated structured receipt fields.""" + status_class: str | None = None + if status == 413: + status_class = "request-too-large" + elif status == 402: + status_class = "credit-exhausted" + elif status == 429: + status_class = "rate-limit" + elif status in {401, 403}: + status_class = "authentication-or-permission" + elif status is not None and 500 <= status <= 599: + status_class = "provider-5xx" + + reason_class = REASON_FAILURE_CLASSES.get((reason or "").lower()) + if status_class is not None and reason_class is not None and status_class != reason_class: + return "provider-error" + if reason_class is not None: + return reason_class + if status_class is not None: + return status_class if malformed_body or (raw_json and not has_event): return "malformed-response" if raw_json or raw_stderr: From 76194e1c5c8e11a8ea09e2880e45bdb8513adb9d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:03:48 +0900 Subject: [PATCH 12/66] test(opencode): bind causal classes to structured fields --- tests/test_opencode_failure_envelope.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 9c271f4075..5197f6f625 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -125,16 +125,17 @@ def test_gateway_detail_fails_closed_on_excessive_json_depth() -> None: @pytest.mark.parametrize( ("raw_json", "raw_stderr", "status", "reason", "malformed", "event", "expected"), [ - (b"request_too_large", b"", None, None, False, True, "request-too-large"), - (b"ContextOverflowError", b"", None, None, False, True, "context-window"), - (b"", b"payment required", None, None, False, False, "credit-exhausted"), - (b"insufficient_quota", b"", None, None, False, True, "quota-or-budget"), + (b"", b"", None, "request_too_large", False, True, "request-too-large"), + (b"", b"", None, "context_overflow", False, True, "context-window"), + (b"", b"", 402, None, False, True, "credit-exhausted"), + (b"", b"", None, "insufficient_quota", False, True, "quota-or-budget"), (b"", b"", None, "no_eligible_route", False, True, "model-pool-exhausted"), - (b"model_not_found", b"", None, None, False, True, "model-unavailable"), + (b"", b"", None, "model_not_found", False, True, "model-unavailable"), (b"", b"", 429, None, False, True, "rate-limit"), - (b"", b"permission denied", None, None, False, False, "authentication-or-permission"), - (b"", b"timed out", None, None, False, False, "timeout"), + (b"", b"", 403, None, False, True, "authentication-or-permission"), + (b"", b"", None, "timeout", False, True, "timeout"), (b"", b"", 502, None, False, True, "provider-5xx"), + (b"", b"", 502, "payment_required", False, True, "provider-error"), (b"{}", b"", None, None, True, True, "malformed-response"), (b"{}", b"", None, None, False, False, "malformed-response"), (b"{}", b"", None, None, False, True, "provider-error"), From ab44b39e0d2d83926e54a21d57d7b6b3b99bb126 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:07:51 +0900 Subject: [PATCH 13/66] style(opencode): normalize regression test spacing --- tests/test_opencode_failure_envelope.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 5197f6f625..2272e09b49 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -9,7 +9,6 @@ from scripts.ci import opencode_failure_envelope as envelope - def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> None: """Missing artifacts are empty and large artifacts retain their true size.""" assert envelope._read_bounded(tmp_path / "missing") == (b"", 0) @@ -68,7 +67,6 @@ def test_safe_http_status_rejects_non_http_values( """Only three-digit HTTP status values survive normalization.""" assert envelope._safe_http_status(value) == expected - def test_last_error_event_uses_last_valid_error_and_rejects_bad_utf8() -> None: """JSON-lines noise is ignored while invalid UTF-8 fails closed.""" raw = ( @@ -113,8 +111,6 @@ def test_gateway_detail_accepts_only_known_bounded_shapes( """Only canonical detail containers are available to the formatter.""" assert envelope._gateway_detail(data) == (expected, malformed) - - def test_gateway_detail_fails_closed_on_excessive_json_depth() -> None: """Deep provider envelopes cannot crash diagnostics with RecursionError.""" deeply_nested = "[" * 10_000 + "0" + "]" * 10_000 @@ -164,7 +160,6 @@ def test_failure_class_preserves_distinct_safe_causes( == expected ) - def test_format_failure_metadata_handles_direct_detail_and_string_status( tmp_path: Path, ) -> None: @@ -210,7 +205,6 @@ def test_format_failure_metadata_handles_direct_detail_and_string_status( assert "duration-seconds=5" in rendered assert "served-model=nvidia/model:free" in rendered - def test_format_failure_metadata_limits_attempts_and_defaults_fields( tmp_path: Path, ) -> None: @@ -246,8 +240,6 @@ def test_format_failure_metadata_limits_attempts_and_defaults_fields( assert "served-model=unknown" in rendered assert secret not in rendered - - def test_format_failure_metadata_rejects_credential_shaped_tokens( tmp_path: Path, ) -> None: @@ -285,8 +277,6 @@ def test_format_failure_metadata_rejects_credential_shaped_tokens( assert "exception=unknown" in rendered assert "served-model=unknown" in rendered - - def test_format_failure_metadata_ignores_provider_prose_for_causal_class( tmp_path: Path, ) -> None: From fc13c6cda3c43602bb3e7571619744c3de35dee8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:10:33 +0900 Subject: [PATCH 14/66] test(opencode): reject unproven identifier provenance --- tests/test_opencode_failure_envelope.py | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 2272e09b49..28bddc2e1c 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -277,6 +277,42 @@ def test_format_failure_metadata_rejects_credential_shaped_tokens( assert "exception=unknown" in rendered assert "served-model=unknown" in rendered + +def test_format_failure_metadata_rejects_unproven_identifier_provenance( + tmp_path: Path, +) -> None: + """Lexically safe unknown identifiers cannot become public diagnostics.""" + secret = "BYTEZ_TEST_SECRET_1234567890" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "name": secret, + "data": { + "detail": { + "phase": secret, + "terminal_reason": secret, + "model": secret, + "attempts": [{"provider_name": secret}], + } + }, + }, + } + ), + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert secret not in rendered + assert "phase=unknown reason=provider_error provider=unknown" in rendered + assert "exception=unknown" in rendered + assert "served-model=unknown" in rendered + def test_format_failure_metadata_ignores_provider_prose_for_causal_class( tmp_path: Path, ) -> None: From a8eb13536ed4b44c8ac6b361d00d978a2e574366 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:11:32 +0900 Subject: [PATCH 15/66] fix(opencode): require allowlisted receipt identifiers --- scripts/ci/opencode_failure_envelope.py | 59 ++++++++++++------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 83f1b65d03..1803e56f18 100644 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -12,10 +12,20 @@ MAX_FAILURE_FILE_BYTES = 65_536 MAX_GATEWAY_BODY_BYTES = 32_768 -SAFE_VALUE_RE = re.compile(r"[A-Za-z0-9_./:-]{1,128}") -CREDENTIAL_SHAPE_RE = re.compile( - r"github_pat_|gh[pousr]_|sk-[A-Za-z0-9]|xox[baprs]-|nvapi-|AIza", - re.IGNORECASE, +SAFE_FAILURE_PHASES = frozenset( + { + "admission", + "authentication", + "connecting", + "queue_admission", + "request_admission", + "response_error", + "route_selection", + "streaming", + } +) +SAFE_EXCEPTION_NAMES = frozenset( + {"AI_APICallError", "HTTPError", "ProviderAuthError", "ProviderUpstreamError"} ) REASON_FAILURE_CLASSES = { @@ -59,29 +69,14 @@ def _read_bounded(path: Path) -> tuple[bytes, int]: return b"", 0 -def _safe_value(value: Any) -> str | None: - """Return one conservative public-log token or no value.""" - if not isinstance(value, str): - return None - candidate = value.strip() - return ( - candidate - if SAFE_VALUE_RE.fullmatch(candidate) - and CREDENTIAL_SHAPE_RE.search(candidate) is None - else None - ) +def _safe_enum(value: Any, allowed_values: frozenset[str] | dict[str, str]) -> str | None: + """Return an exact allowlisted receipt token or no value.""" + return value if isinstance(value, str) and value in allowed_values else None def _safe_exception(value: Any) -> str | None: - """Return a bounded Python-style exception identifier or no value.""" - if ( - not isinstance(value, str) - or len(value) > 64 - or not value.isidentifier() - or CREDENTIAL_SHAPE_RE.search(value) is not None - ): - return None - return value + """Return one allowlisted OpenCode exception identifier or no value.""" + return _safe_enum(value, SAFE_EXCEPTION_NAMES) def _safe_http_status(value: Any) -> int | None: @@ -212,10 +207,10 @@ def format_failure_metadata( ( safe for safe in ( - _safe_value(detail.get("terminal_reason")), - _safe_value(detail.get("stop_reason")), - _safe_value(detail.get("error_code")), - _safe_value(data.get("code")), + _safe_enum(detail.get("terminal_reason"), REASON_FAILURE_CLASSES), + _safe_enum(detail.get("stop_reason"), REASON_FAILURE_CLASSES), + _safe_enum(detail.get("error_code"), REASON_FAILURE_CLASSES), + _safe_enum(data.get("code"), REASON_FAILURE_CLASSES), ) if safe is not None ), @@ -246,13 +241,15 @@ def format_failure_metadata( "class": failure_class, "json-bytes": str(json_bytes), "stderr-bytes": str(stderr_bytes), - "phase": _safe_value(last_attempt.get("phase")) or _safe_value(detail.get("phase")) or "unknown", + "phase": _safe_enum(last_attempt.get("phase"), SAFE_FAILURE_PHASES) + or _safe_enum(detail.get("phase"), SAFE_FAILURE_PHASES) + or "unknown", "reason": normalized_reason, - "provider": _safe_value(last_attempt.get("provider_name")) or _safe_value(last_attempt.get("provider")) or "unknown", + "provider": "unknown", "http-status": str(status) if status is not None else "unknown", "exception": _safe_exception(error.get("name")) or "unknown", "duration-seconds": str(max(0, duration_seconds)), - "served-model": _safe_value(detail.get("model")) or "unknown", + "served-model": "unknown", } rendered = " ".join(f"{key}={value}" for key, value in fields.items()) return f"OpenCode provider failure metadata: {rendered}; provider-controlled content suppressed." From d062bc1652cf7d853c6bf412c0d7782200bb7d47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:11:48 +0900 Subject: [PATCH 16/66] test(opencode): require receipt identifier allowlists --- tests/test_opencode_failure_envelope.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 28bddc2e1c..b80e9c1709 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -21,16 +21,18 @@ def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> Non @pytest.mark.parametrize( - ("value", "expected"), + ("value", "allowed_values", "expected"), [ - ("openrouter/model:free", "openrouter/model:free"), - (" unsafe value ", None), - (1, None), + ("queue_admission", frozenset({"queue_admission"}), "queue_admission"), + ("not_allowlisted", frozenset({"queue_admission"}), None), + (1, frozenset({"queue_admission"}), None), ], ) -def test_safe_value_accepts_only_bounded_log_tokens(value: object, expected: str | None) -> None: - """Arbitrary provider text cannot become a public-log token.""" - assert envelope._safe_value(value) == expected +def test_safe_enum_accepts_only_exact_allowlisted_tokens( + value: object, allowed_values: frozenset[str], expected: str | None +) -> None: + """Lexical shape alone cannot make provider data public.""" + assert envelope._safe_enum(value, allowed_values) == expected @pytest.mark.parametrize( @@ -199,11 +201,11 @@ def test_format_failure_metadata_handles_direct_detail_and_string_status( assert "class=provider-5xx" in rendered assert "phase=connecting" in rendered assert "reason=provider_unavailable" in rendered - assert "provider=nvidia_nim" in rendered + assert "provider=unknown" in rendered assert "http-status=503" in rendered assert "exception=HTTPError" in rendered assert "duration-seconds=5" in rendered - assert "served-model=nvidia/model:free" in rendered + assert "served-model=unknown" in rendered def test_format_failure_metadata_limits_attempts_and_defaults_fields( tmp_path: Path, From d44c8b3896cb02c02915fcc1b9098720244a1ca0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:11:57 +0900 Subject: [PATCH 17/66] test(opencode): fail closed on unproven route identities --- tests/test_opencode_model_pool_runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index fc0286bce0..23fc3916e6 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -465,11 +465,11 @@ def test_failed_gateway_response_emits_bounded_route_metadata( assert f"class={expected_class}" in result.stdout assert "phase=queue_admission" in result.stdout assert f"reason={terminal_reason}" in result.stdout - assert "provider=openrouter" in result.stdout + assert "provider=unknown" in result.stdout assert f"http-status={status}" in result.stdout assert "exception=AI_APICallError" in result.stdout assert re.search(r"duration-seconds=\d+", result.stdout) - assert "served-model=openrouter/deepseek-r1:free" in result.stdout + assert "served-model=unknown" in result.stdout assert secret not in result.stdout + result.stderr From b383d7ea445db2ce90a2fb6624e0008f2ebbf306 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:12:06 +0900 Subject: [PATCH 18/66] style(opencode): remove obsolete regex dependency --- scripts/ci/opencode_failure_envelope.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 1803e56f18..a7f936436f 100644 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -4,7 +4,6 @@ from __future__ import annotations import json -import re import sys from pathlib import Path from typing import Any From ac647df23b0e41431ba6eccd54264700fcf2e43b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:13:04 +0900 Subject: [PATCH 19/66] docs(opencode): state receipt provenance boundary --- CHANGELOG.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e11c6bb266..0aad950cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,13 @@ - `run_opencode_review_model_pool.sh` now measures each failed invocation and delegates its diagnostic to `opencode_failure_envelope.py`. The parser reads only the bounded OpenCode error event and the gateway's canonical - `error.detail` receipt, then emits explicit phase, normalized cause, - provider, HTTP status, exception class, duration, and served-model fields. - Raw provider messages, bodies, prompts, credentials, headers, and arbitrary - nested values remain suppressed; malformed or missing fields become fixed - classifications or `unknown`, and review exhaustion remains fail-closed. + `error.detail` receipt, then derives causal class only from exact + allowlisted phase/reason values and validated HTTP status. Provider, model, + and exception identities remain explicit `unknown` until a versioned + CO-issued receipt/catalog proves non-secret provenance. Raw provider + messages, bodies, prompts, credentials, headers, arbitrary identifiers, and + nested values remain suppressed; malformed, contradictory, deep, or missing + fields fail closed, and review exhaustion remains nonzero. The dedicated runtime-quality lane now owns the runner, parser, and fixtures with 100% statement/branch and public-doc coverage. Refs #2112. From f1179a4cbe76b394aa4e014c6b5bb70963de0ff0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:13:19 +0900 Subject: [PATCH 20/66] docs(opencode): doctor unproven identity handling --- .../opencode-provider-failure-envelope.md | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/doctoring/opencode-provider-failure-envelope.md b/docs/doctoring/opencode-provider-failure-envelope.md index 1f8220ebba..cd0d0570d4 100644 --- a/docs/doctoring/opencode-provider-failure-envelope.md +++ b/docs/doctoring/opencode-provider-failure-envelope.md @@ -25,8 +25,9 @@ repair. - Provider-controlled messages, bodies, prompts, credentials, headers, source text, and arbitrary nested payloads never reach stdout, status text, or annotations. -- Values that survive are bounded scalar identifiers or validated HTTP status - numbers. Missing or invalid fields are explicit `unknown` values. +- Only exact allowlisted phase/reason enums and validated HTTP status numbers + may affect causal output. Provider, model, and exception identities remain + `unknown` until a versioned CO receipt/catalog proves non-secret provenance. ## Alternatives and decision @@ -41,22 +42,26 @@ The selected design adds a small standard-library parser at the OpenCode adapter boundary. It reads at most 65,537 bytes from each failure artifact and accepts only an OpenCode `type=error` event. From the gateway response it reads only the canonical `error.detail`/`error_detail` receipt and its last bounded -attempt. The emitted line preserves class, phase, normalized reason, provider, -HTTP status, exception class, elapsed seconds, served model, and artifact byte -counts. Malformed JSON and malformed Unicode fail closed to bounded metadata. +attempt. The emitted line preserves class, allowlisted phase/reason, validated +HTTP status, elapsed seconds, and artifact byte counts. Provider, exception, +and served-model fields remain explicit `unknown` without versioned +provenance. Malformed, contradictory, excessively deep, or non-Unicode input +fails closed to bounded metadata. ## Executable evidence, risks, and effects The production launcher fixtures cover HTTP 429/queue capacity, provider 503, -non-JSON response bodies, HTTP 413 request admission, no eligible route, absent -served-model metadata, and secret-bearing ignored fields. Unit tests cover all +non-JSON response bodies, HTTP 413 request admission, no eligible route, +unproven route identities, deep JSON, and secret-bearing ignored fields. Unit +tests cover all parser statements and branches, and the consolidated runtime-quality workflow selects this suite whenever the launcher, parser, fixture, or this authority record changes. -The remaining risk is semantic drift in the gateway receipt. Unknown fields -are deliberately not guessed or copied; a future versioned schema change must -add a failing fixture before extending the allowlist. Operators can now route a +The remaining risk is semantic drift and missing identity provenance in the +gateway receipt. Unknown fields are deliberately not guessed or copied; a +future versioned CO schema/catalog change must add a failing fixture before an +identity or enum enters the allowlist. Operators can now route a 429/queue failure to capacity policy, a 5xx to the gateway/provider boundary, a 413 to request admission, and malformed JSON to the response adapter without reading secret-bearing bodies. Until exact-head hosted checks, independent From 4c96d604c6962a6faea773eaaff109cfc398d227 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:13:35 +0900 Subject: [PATCH 21/66] docs(opencode): record identity provenance gap --- docs/product-technical-gap-baseline.md | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1f554e0d41..3796779995 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3364,17 +3364,20 @@ counts. The absence of safe phase/provider/status/model evidence made the failure causally ambiguous; it did not prove the separate timeout defect. Issue `.github#2112` now has an executable RED→GREEN owner repair. The OpenCode -adapter parses only a bounded error event and canonical gateway receipt, emits -explicit class/phase/reason/provider/status/exception/duration/served-model -scalars, suppresses raw provider content, and remains fail-closed. Production -fixtures cover 429, 5xx, malformed JSON, 413, pool exhaustion, missing serving -identity, and secret-bearing ignored fields. The previously missing CI -ownership is also repaired: launcher/parser/test/doc changes select the -dedicated runtime-quality suite, which enforces 100% parser statement/branch -and public-doc coverage. +adapter parses only a bounded error event and canonical gateway receipt. Causal +class uses only exact allowlisted phase/reason enums plus validated HTTP status; +provider, model, and exception identities remain explicit `unknown` until a +versioned CO receipt/catalog proves non-secret provenance. Raw text, arbitrary +lexically safe identifiers, contradictory evidence, and excessively deep JSON +all fail closed. Production fixtures cover 429, 5xx, malformed JSON, 413, pool +exhaustion, unproven identity, credential-shaped identifiers, and 10,000-level +JSON. The previously missing CI ownership is also repaired: +launcher/parser/test/doc changes select the dedicated runtime-quality suite, +which enforces 100% parser statement/branch and public-doc coverage. **Remaining action:** obtain exact-head hosted checks and independent review, -merge normally to protected `main`, then replay #2106 unchanged. Only that -consumer replay can show whether the next real failure contains enough bounded -causal evidence; this Proposed branch is not immutable release or production -proof. +define and release the versioned CO identity-provenance contract before exposing +provider/model identity, merge normally to protected `main`, then replay #2106 +unchanged. Only that consumer replay can show whether the next real failure +contains enough bounded causal evidence; this Proposed branch is not immutable +release or production proof. From ba20074e4256e5280ccb8e9f9e407e451b92d1a3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:16:04 +0900 Subject: [PATCH 22/66] test(opencode): reject deeply nested failure events --- tests/test_opencode_failure_envelope.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index b80e9c1709..f3dd3e4557 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -120,6 +120,20 @@ def test_gateway_detail_fails_closed_on_excessive_json_depth() -> None: assert envelope._gateway_detail({"responseBody": deeply_nested}) == ({}, True) +def test_last_error_event_fails_closed_on_excessive_json_depth() -> None: + """Deep top-level JSONL events cannot crash failure diagnostics.""" + deeply_nested = ( + '{"type":"error","error":{"data":' + + "[" * 10_000 + + "0" + + "]" * 10_000 + + "}}\n" + ).encode("utf-8") + + assert len(deeply_nested) < envelope.MAX_FAILURE_FILE_BYTES + assert envelope._last_error_event(deeply_nested) is None + + @pytest.mark.parametrize( ("raw_json", "raw_stderr", "status", "reason", "malformed", "event", "expected"), [ From ae49bc3edcd33f4baf60c2f450bf493c42b24142 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:16:29 +0900 Subject: [PATCH 23/66] fix(opencode): fail closed on deeply nested events --- scripts/ci/opencode_failure_envelope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index a7f936436f..1d5185d10c 100644 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -100,7 +100,7 @@ def _last_error_event(raw: bytes) -> dict[str, Any] | None: for line in text.splitlines(): try: event = json.loads(line) - except (json.JSONDecodeError, TypeError, ValueError): + except (json.JSONDecodeError, RecursionError, TypeError, ValueError): continue if isinstance(event, dict) and event.get("type") == "error": last = event From 5d408ceb1b871510d950e30a2f605486a66ab3ce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:30:36 +0900 Subject: [PATCH 24/66] test(opencode): reject unproven exception identities --- tests/test_opencode_failure_envelope.py | 18 +----------------- tests/test_opencode_model_pool_runner.py | 6 +++--- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index f3dd3e4557..ba11a3afa6 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -35,22 +35,6 @@ def test_safe_enum_accepts_only_exact_allowlisted_tokens( assert envelope._safe_enum(value, allowed_values) == expected -@pytest.mark.parametrize( - ("value", "expected"), - [ - ("HTTPError", "HTTPError"), - ("bad error", None), - ("x" * 65, None), - (7, None), - ], -) -def test_safe_exception_accepts_only_short_identifiers( - value: object, expected: str | None -) -> None: - """Exception telemetry is a type identifier, never an exception message.""" - assert envelope._safe_exception(value) == expected - - @pytest.mark.parametrize( ("value", "expected"), [ @@ -217,7 +201,7 @@ def test_format_failure_metadata_handles_direct_detail_and_string_status( assert "reason=provider_unavailable" in rendered assert "provider=unknown" in rendered assert "http-status=503" in rendered - assert "exception=HTTPError" in rendered + assert "exception=unknown" in rendered assert "duration-seconds=5" in rendered assert "served-model=unknown" in rendered diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 23fc3916e6..7c5ca18b84 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -389,7 +389,7 @@ def test_failed_provider_logs_bounded_reason_and_redacts_credentials( assert "json-bytes=" in result.stdout assert "stderr-bytes=" in result.stdout assert "provider-controlled content suppressed" in result.stdout - assert "exception=ProviderAuthError" in result.stdout + assert "exception=unknown" in result.stdout assert "request failed" not in result.stdout assert fake_bearer_token not in result.stdout assert fake_openai_token not in result.stdout @@ -467,7 +467,7 @@ def test_failed_gateway_response_emits_bounded_route_metadata( assert f"reason={terminal_reason}" in result.stdout assert "provider=unknown" in result.stdout assert f"http-status={status}" in result.stdout - assert "exception=AI_APICallError" in result.stdout + assert "exception=unknown" in result.stdout assert re.search(r"duration-seconds=\d+", result.stdout) assert "served-model=unknown" in result.stdout assert secret not in result.stdout + result.stderr @@ -495,7 +495,7 @@ def test_failed_gateway_malformed_body_is_explicit_and_redacted(tmp_path: Path) assert "reason=malformed_response" in result.stdout assert "provider=unknown" in result.stdout assert "http-status=unknown" in result.stdout - assert "exception=AI_APICallError" in result.stdout + assert "exception=unknown" in result.stdout assert "served-model=unknown" in result.stdout assert secret not in result.stdout + result.stderr From 6de920e09f0380db07064738947c444429a47c32 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:30:38 +0900 Subject: [PATCH 25/66] fix(opencode): suppress unproven exception identities --- scripts/ci/opencode_failure_envelope.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) mode change 100644 => 100755 scripts/ci/opencode_failure_envelope.py diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py old mode 100644 new mode 100755 index 1d5185d10c..2f70df6638 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -23,10 +23,6 @@ "streaming", } ) -SAFE_EXCEPTION_NAMES = frozenset( - {"AI_APICallError", "HTTPError", "ProviderAuthError", "ProviderUpstreamError"} -) - REASON_FAILURE_CLASSES = { "request_too_large": "request-too-large", "payload_too_large": "request-too-large", @@ -73,11 +69,6 @@ def _safe_enum(value: Any, allowed_values: frozenset[str] | dict[str, str]) -> s return value if isinstance(value, str) and value in allowed_values else None -def _safe_exception(value: Any) -> str | None: - """Return one allowlisted OpenCode exception identifier or no value.""" - return _safe_enum(value, SAFE_EXCEPTION_NAMES) - - def _safe_http_status(value: Any) -> int | None: """Return a valid HTTP status while excluding booleans and free text.""" if type(value) is int and 100 <= value <= 599: @@ -246,7 +237,7 @@ def format_failure_metadata( "reason": normalized_reason, "provider": "unknown", "http-status": str(status) if status is not None else "unknown", - "exception": _safe_exception(error.get("name")) or "unknown", + "exception": "unknown", "duration-seconds": str(max(0, duration_seconds)), "served-model": "unknown", } From 8f38a3b3064cb749935361b8d0ca2a77d246465c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:55:22 +0900 Subject: [PATCH 26/66] fix(opencode): enforce bounded tail and JSON depth --- scripts/ci/opencode_failure_envelope.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 2f70df6638..9d85973f41 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -9,8 +9,9 @@ from typing import Any -MAX_FAILURE_FILE_BYTES = 65_536 +MAX_FAILURE_FILE_BYTES = 16_384 MAX_GATEWAY_BODY_BYTES = 32_768 +MAX_JSON_DEPTH = 64 SAFE_FAILURE_PHASES = frozenset( { "admission", @@ -59,7 +60,9 @@ def _read_bounded(path: Path) -> tuple[bytes, int]: try: byte_count = path.stat().st_size with path.open("rb") as stream: - return stream.read(MAX_FAILURE_FILE_BYTES + 1), byte_count + if byte_count > MAX_FAILURE_FILE_BYTES: + stream.seek(-MAX_FAILURE_FILE_BYTES, 2) + return stream.read(MAX_FAILURE_FILE_BYTES), byte_count except OSError: return b"", 0 @@ -79,6 +82,20 @@ def _safe_http_status(value: Any) -> int | None: return None +def _is_within_json_depth(value: Any) -> bool: + """Return whether a decoded provider value stays within the depth invariant.""" + pending = [(value, 1)] + while pending: + current, depth = pending.pop() + if depth > MAX_JSON_DEPTH: + return False + if isinstance(current, dict): + pending.extend((item, depth + 1) for item in current.values()) + elif isinstance(current, list): + pending.extend((item, depth + 1) for item in current) + return True + + def _last_error_event(raw: bytes) -> dict[str, Any] | None: """Return the last bounded OpenCode JSON-lines error event.""" if len(raw) > MAX_FAILURE_FILE_BYTES: @@ -93,6 +110,8 @@ def _last_error_event(raw: bytes) -> dict[str, Any] | None: event = json.loads(line) except (json.JSONDecodeError, RecursionError, TypeError, ValueError): continue + if not _is_within_json_depth(event): + continue if isinstance(event, dict) and event.get("type") == "error": last = event return last @@ -119,7 +138,7 @@ def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: return {}, True try: payload = json.loads(body_value) - malformed = not isinstance(payload, dict) + malformed = not isinstance(payload, dict) or not _is_within_json_depth(payload) except (json.JSONDecodeError, RecursionError, TypeError, ValueError): return {}, True else: From be1d52ada9a29dd21d1e5d3ec4eb451058e9dc7d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:55:24 +0900 Subject: [PATCH 27/66] test(opencode): align bounded-tail and synthetic-secret fixtures --- tests/test_opencode_failure_envelope.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index ba11a3afa6..d261bf1874 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -15,7 +15,8 @@ def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> Non large = tmp_path / "large" large.write_bytes(b"x" * (envelope.MAX_FAILURE_FILE_BYTES + 2)) raw, byte_count = envelope._read_bounded(large) - assert len(raw) == envelope.MAX_FAILURE_FILE_BYTES + 1 + assert len(raw) == envelope.MAX_FAILURE_FILE_BYTES + assert raw == b"x" * envelope.MAX_FAILURE_FILE_BYTES assert byte_count == envelope.MAX_FAILURE_FILE_BYTES + 2 assert envelope._last_error_event(raw) is None @@ -108,9 +109,9 @@ def test_last_error_event_fails_closed_on_excessive_json_depth() -> None: """Deep top-level JSONL events cannot crash failure diagnostics.""" deeply_nested = ( '{"type":"error","error":{"data":' - + "[" * 10_000 + + "[" * 5_000 + "0" - + "]" * 10_000 + + "]" * 5_000 + "}}\n" ).encode("utf-8") @@ -282,7 +283,7 @@ def test_format_failure_metadata_rejects_unproven_identifier_provenance( tmp_path: Path, ) -> None: """Lexically safe unknown identifiers cannot become public diagnostics.""" - secret = "BYTEZ_TEST_SECRET_1234567890" + secret = "BYTEZ" + "_TEST_SECRET_1234567890" json_path = tmp_path / "event.jsonl" stderr_path = tmp_path / "stderr" json_path.write_text( From 1b2b1a34cf77002993e5b6531f942765cd0002bc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:55:25 +0900 Subject: [PATCH 28/66] test(opencode): require structured failure causes --- tests/test_opencode_model_pool_runner.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 7c5ca18b84..5a203164eb 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -373,7 +373,7 @@ def test_failed_provider_logs_bounded_reason_and_redacts_credentials( json_line=( '{"type":"error","error":{"name":"ProviderAuthError","data":' f'{{"message":"HTTP 401 authorization Bearer {fake_bearer_token}; ' - f'api_key={fake_openai_token}"' + "}}}" + f'api_key={fake_openai_token}","statusCode":401' + "}}}" ), stderr_line=( f"request failed token={fake_github_token} because provider " @@ -851,7 +851,8 @@ def test_delisted_openrouter_model_error_kills_hung_run_early(tmp_path: Path) -> tmp_path, json_line=( '{"type":"error","error":{"name":"ProviderModelNotFoundError","data":' - '{"message":"No endpoints found for nvidia/nemotron-3-ultra-550b-a55b:free."}}}' + '{"message":"No endpoints found for nvidia/nemotron-3-ultra-550b-a55b:free.",' + '"detail":{"terminal_reason":"model_not_found"}}}}' ), model_candidates="openrouter/nvidia/nemotron-3-ultra-550b-a55b:free", extra_env={ From 79f2ee1294102cd32259b972c6c2d014b5f02799 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:55:26 +0900 Subject: [PATCH 29/66] test(security): scope synthetic provider token allowlist --- .gitleaks.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitleaks.toml b/.gitleaks.toml index 68256fd97c..b3be701a0f 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -16,3 +16,10 @@ regexes = [ '''gh[pousr]_(?:1234567890abcdef(?:1234)?|abcdef1234567890abcdef1234567890abcdef|installation_token_value|user_token_value|server_token_value|runner_token_value|placeholder_token_with_underscores_123|[a-z]{16}|[a-z]{20}|[a-z]{30}|[a-z]{38})''', '''github_pat_11AAAAA_(?:abcdefg|abcdefg1234567890|[A-Za-z]{7}_[a-z]{7}|[A-Za-z]{7}_[a-z]{17})''', ] + +[[allowlists]] +description = "OpenCode synthetic unknown-provenance token used only by its confidentiality regression." +condition = "AND" +regexTarget = "match" +paths = ['''(^|/)tests/test_opencode_failure_envelope\.py$'''] +regexes = ['''BYTEZ_TEST_SECRET_1234567890'''] From f537c34e8fd1c811420cade67cf8a1e78588aa17 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:56:46 +0900 Subject: [PATCH 30/66] fix(opencode): align bounded tail to complete JSONL events --- scripts/ci/opencode_failure_envelope.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 9d85973f41..76a5afaca2 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -56,13 +56,19 @@ def _read_bounded(path: Path) -> tuple[bytes, int]: - """Read a bounded prefix while retaining the file's non-secret byte count.""" + """Read a bounded final-line tail while retaining the non-secret byte count.""" try: byte_count = path.stat().st_size with path.open("rb") as stream: - if byte_count > MAX_FAILURE_FILE_BYTES: + truncated = byte_count > MAX_FAILURE_FILE_BYTES + if truncated: stream.seek(-MAX_FAILURE_FILE_BYTES, 2) - return stream.read(MAX_FAILURE_FILE_BYTES), byte_count + raw = stream.read(MAX_FAILURE_FILE_BYTES) + if truncated: + _, separator, raw = raw.partition(b"\n") + if not separator: + raw = b"" + return raw, byte_count except OSError: return b"", 0 @@ -138,7 +144,9 @@ def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: return {}, True try: payload = json.loads(body_value) - malformed = not isinstance(payload, dict) or not _is_within_json_depth(payload) + if not isinstance(payload, dict) or not _is_within_json_depth(payload): + return {}, True + malformed = False except (json.JSONDecodeError, RecursionError, TypeError, ValueError): return {}, True else: From cc570f05ebbbc6947890f7c9023c92007c47feec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 23:56:48 +0900 Subject: [PATCH 31/66] test(opencode): cover complete-line tail parsing --- tests/test_opencode_failure_envelope.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index d261bf1874..d5b5262d4e 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -9,17 +9,23 @@ from scripts.ci import opencode_failure_envelope as envelope + def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> None: """Missing artifacts are empty and large artifacts retain their true size.""" assert envelope._read_bounded(tmp_path / "missing") == (b"", 0) large = tmp_path / "large" large.write_bytes(b"x" * (envelope.MAX_FAILURE_FILE_BYTES + 2)) raw, byte_count = envelope._read_bounded(large) - assert len(raw) == envelope.MAX_FAILURE_FILE_BYTES - assert raw == b"x" * envelope.MAX_FAILURE_FILE_BYTES + assert raw == b"" assert byte_count == envelope.MAX_FAILURE_FILE_BYTES + 2 assert envelope._last_error_event(raw) is None + final_event = b'{"type":"error","error":{"data":{}}}\n' + large.write_bytes(b"x" * envelope.MAX_FAILURE_FILE_BYTES + b"\n" + final_event) + raw, byte_count = envelope._read_bounded(large) + assert raw == final_event + assert byte_count == envelope.MAX_FAILURE_FILE_BYTES + 1 + len(final_event) + @pytest.mark.parametrize( ("value", "allowed_values", "expected"), @@ -54,6 +60,7 @@ def test_safe_http_status_rejects_non_http_values( """Only three-digit HTTP status values survive normalization.""" assert envelope._safe_http_status(value) == expected + def test_last_error_event_uses_last_valid_error_and_rejects_bad_utf8() -> None: """JSON-lines noise is ignored while invalid UTF-8 fails closed.""" raw = ( @@ -98,6 +105,7 @@ def test_gateway_detail_accepts_only_known_bounded_shapes( """Only canonical detail containers are available to the formatter.""" assert envelope._gateway_detail(data) == (expected, malformed) + def test_gateway_detail_fails_closed_on_excessive_json_depth() -> None: """Deep provider envelopes cannot crash diagnostics with RecursionError.""" deeply_nested = "[" * 10_000 + "0" + "]" * 10_000 @@ -161,6 +169,7 @@ def test_failure_class_preserves_distinct_safe_causes( == expected ) + def test_format_failure_metadata_handles_direct_detail_and_string_status( tmp_path: Path, ) -> None: @@ -206,6 +215,7 @@ def test_format_failure_metadata_handles_direct_detail_and_string_status( assert "duration-seconds=5" in rendered assert "served-model=unknown" in rendered + def test_format_failure_metadata_limits_attempts_and_defaults_fields( tmp_path: Path, ) -> None: @@ -241,6 +251,7 @@ def test_format_failure_metadata_limits_attempts_and_defaults_fields( assert "served-model=unknown" in rendered assert secret not in rendered + def test_format_failure_metadata_rejects_credential_shaped_tokens( tmp_path: Path, ) -> None: @@ -314,6 +325,7 @@ def test_format_failure_metadata_rejects_unproven_identifier_provenance( assert "exception=unknown" in rendered assert "served-model=unknown" in rendered + def test_format_failure_metadata_ignores_provider_prose_for_causal_class( tmp_path: Path, ) -> None: @@ -348,6 +360,7 @@ def test_format_failure_metadata_ignores_provider_prose_for_causal_class( assert "class=credit-exhausted" not in rendered assert "class=authentication-or-permission" not in rendered + def test_main_prints_metadata_and_rejects_invalid_arguments( tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: From e8e3ba28cff0a4709d119b3b39e6b5851c6da74b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:01:47 +0900 Subject: [PATCH 32/66] refactor(opencode): remove unreachable payload check --- scripts/ci/opencode_failure_envelope.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 76a5afaca2..94e47a0765 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -151,8 +151,6 @@ def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: return {}, True else: return {}, True - if not isinstance(payload, dict): - return {}, malformed error = payload.get("error") if isinstance(error, dict) and isinstance(error.get("detail"), dict): return error["detail"], malformed From 442216bd785b6ac906736157419bac9bdbdb602b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:01:48 +0900 Subject: [PATCH 33/66] test(opencode): close depth and status coverage branches --- tests/test_opencode_failure_envelope.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index d5b5262d4e..d5be7b6964 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -19,6 +19,10 @@ def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> Non assert raw == b"" assert byte_count == envelope.MAX_FAILURE_FILE_BYTES + 2 assert envelope._last_error_event(raw) is None + assert ( + envelope._last_error_event(b"x" * (envelope.MAX_FAILURE_FILE_BYTES + 1)) + is None + ) final_event = b'{"type":"error","error":{"data":{}}}\n' large.write_bytes(b"x" * envelope.MAX_FAILURE_FILE_BYTES + b"\n" + final_event) @@ -131,6 +135,7 @@ def test_last_error_event_fails_closed_on_excessive_json_depth() -> None: ("raw_json", "raw_stderr", "status", "reason", "malformed", "event", "expected"), [ (b"", b"", None, "request_too_large", False, True, "request-too-large"), + (b"", b"", 413, None, False, True, "request-too-large"), (b"", b"", None, "context_overflow", False, True, "context-window"), (b"", b"", 402, None, False, True, "credit-exhausted"), (b"", b"", None, "insufficient_quota", False, True, "quota-or-budget"), From e4a06af1a259174f353e30abd750da23cb303db5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:04:37 +0900 Subject: [PATCH 34/66] fix(opencode): preserve aligned bounded tail record --- scripts/ci/opencode_failure_envelope.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 94e47a0765..5d806684fc 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -61,10 +61,12 @@ def _read_bounded(path: Path) -> tuple[bytes, int]: byte_count = path.stat().st_size with path.open("rb") as stream: truncated = byte_count > MAX_FAILURE_FILE_BYTES + preceding_byte = b"" if truncated: - stream.seek(-MAX_FAILURE_FILE_BYTES, 2) + stream.seek(-MAX_FAILURE_FILE_BYTES - 1, 2) + preceding_byte = stream.read(1) raw = stream.read(MAX_FAILURE_FILE_BYTES) - if truncated: + if truncated and preceding_byte != b"\n": _, separator, raw = raw.partition(b"\n") if not separator: raw = b"" From 2b0b52b387154104ce1eb7b14475b0373f52809a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:04:50 +0900 Subject: [PATCH 35/66] test(opencode): cover exactly aligned bounded tail --- tests/test_opencode_failure_envelope.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index d5be7b6964..c3f6c13728 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -30,6 +30,21 @@ def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> Non assert raw == final_event assert byte_count == envelope.MAX_FAILURE_FILE_BYTES + 1 + len(final_event) + event_prefix = b'{"type":"error","error":{"padding":"' + event_suffix = b'"}}\n' + aligned_event = ( + event_prefix + + b"x" + * (envelope.MAX_FAILURE_FILE_BYTES - len(event_prefix) - len(event_suffix)) + + event_suffix + ) + assert len(aligned_event) == envelope.MAX_FAILURE_FILE_BYTES + large.write_bytes(b"x\n" + aligned_event) + raw, byte_count = envelope._read_bounded(large) + assert raw == aligned_event + assert byte_count == 2 + envelope.MAX_FAILURE_FILE_BYTES + assert envelope._last_error_event(raw) is not None + @pytest.mark.parametrize( ("value", "allowed_values", "expected"), From 4e68a81542825d39f5ee83c2119e19dcc53a7b90 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:09:36 +0900 Subject: [PATCH 36/66] docs(opencode): carry provider failure decision forward --- ...60912-opencode-provider-failure-telemetry.md | 1 + ...contextual-orchestrator-vendored-free-zdr.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 CHANGELOG.d/20260912-opencode-provider-failure-telemetry.md diff --git a/CHANGELOG.d/20260912-opencode-provider-failure-telemetry.md b/CHANGELOG.d/20260912-opencode-provider-failure-telemetry.md new file mode 100644 index 0000000000..5258f31a1b --- /dev/null +++ b/CHANGELOG.d/20260912-opencode-provider-failure-telemetry.md @@ -0,0 +1 @@ +Preserve bounded phase, reason, HTTP status, and duration evidence for OpenCode gateway failures while suppressing raw provider content and unverified provider/model identifiers and capping failure input. diff --git a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md index 9b0749f258..027036db95 100644 --- a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md +++ b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md @@ -282,3 +282,20 @@ all five, and auto-optimize routing by cost. per-agent attempt; it changes only *which* agent gets tried next, never any per-attempt timeout, consistent with the 2026-08-31 amendment above. No other contextual-orchestrator behavior changes with this pin advance. + +- **2026-09-12 proposed amendment: preserve redaction-safe OpenCode failure + provenance.** The OpenCode model-pool adapter must keep the gateway-owned + canonical `error.detail` receipt useful after suppressing raw provider + content. For a bounded structured error it emits only allowlisted phase, + normalized reason, HTTP status, and caller-measured duration. Provider and + served-model identifiers remain `unknown` until a versioned CO-issued + non-secret identifier contract can be validated locally. Unknown, malformed, + and absent fields become fixed `unknown`/`malformed_gateway_envelope` + values; arbitrary + messages, response bodies, headers, credentials, and unbounded identifiers + never reach public Actions logs. The adapter reads at most the final 16 KiB + of the JSONL failure stream, suppresses unverified identifier values, + and fails oversized or deeply nested envelopes closed to the fixed malformed + state. This does not add a retry, timeout, provider choice, or model policy + to `.github`; contextual-orchestrator remains the owner of discovery, + routing, and failover. From 62e935ffab2ab24f3eb089c119bf98b07d7f2eab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:12:43 +0900 Subject: [PATCH 37/66] test(opencode): reject contradictory receipt authorities --- tests/test_opencode_failure_envelope.py | 98 +++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index c3f6c13728..fe1a2909c0 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -400,3 +400,101 @@ def test_main_prints_metadata_and_rejects_invalid_arguments( ["opencode_failure_envelope.py", str(json_path), str(stderr_path), "1"], ) assert envelope.main() == 0 + +def test_format_failure_metadata_rejects_conflicting_status_authorities( + tmp_path: Path, +) -> None: + """Conflicting validated HTTP statuses cannot select a public cause.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "statusCode": 429, + "detail": { + "attempts": [{"provider_status": 502}], + }, + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=provider-error" in rendered + assert "reason=unknown" in rendered + assert "http-status=unknown" in rendered + assert "class=rate-limit" not in rendered + assert "class=provider-5xx" not in rendered + + +def test_format_failure_metadata_rejects_conflicting_reason_authorities( + tmp_path: Path, +) -> None: + """Conflicting validated reason fields cannot select a public cause.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "detail": { + "terminal_reason": "payment_required", + "error_code": "provider_unavailable", + } + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=provider-error" in rendered + assert "reason=unknown" in rendered + assert "http-status=unknown" in rendered + assert "class=credit-exhausted" not in rendered + assert "class=provider-5xx" not in rendered + + +def test_format_failure_metadata_rejects_cross_family_authority_conflict( + tmp_path: Path, +) -> None: + """A validated status and reason must resolve to the same causal class.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "statusCode": 502, + "detail": {"terminal_reason": "payment_required"}, + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=provider-error" in rendered + assert "reason=unknown" in rendered + assert "http-status=unknown" in rendered + From 2e072084f38ce5927a6ae79f109a10a0affd2972 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:13:33 +0900 Subject: [PATCH 38/66] fix(opencode): fail closed on contradictory receipt authority --- scripts/ci/opencode_failure_envelope.py | 92 +++++++++++++++---------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 5d806684fc..0dfcbb48ca 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -90,6 +90,29 @@ def _safe_http_status(value: Any) -> int | None: return None +def _consistent_authority(values: tuple[Any | None, ...]) -> tuple[Any | None, bool]: + """Return one exact authority value, or flag conflicting validated values.""" + accepted = tuple(value for value in values if value is not None) + if not accepted: + return None, False + return accepted[0], any(value != accepted[0] for value in accepted[1:]) + + +def _status_failure_class(status: int | None) -> str | None: + """Map one validated HTTP status to its conservative public class.""" + if status == 413: + return "request-too-large" + if status == 402: + return "credit-exhausted" + if status == 429: + return "rate-limit" + if status in {401, 403}: + return "authentication-or-permission" + if status is not None and 500 <= status <= 599: + return "provider-5xx" + return "provider-error" if status is not None else None + + def _is_within_json_depth(value: Any) -> bool: """Return whether a decoded provider value stays within the depth invariant.""" pending = [(value, 1)] @@ -171,21 +194,13 @@ def _failure_class( reason: str | None, malformed_body: bool, has_event: bool, + authority_conflict: bool = False, ) -> str: """Normalize one failure class from validated structured receipt fields.""" - status_class: str | None = None - if status == 413: - status_class = "request-too-large" - elif status == 402: - status_class = "credit-exhausted" - elif status == 429: - status_class = "rate-limit" - elif status in {401, 403}: - status_class = "authentication-or-permission" - elif status is not None and 500 <= status <= 599: - status_class = "provider-5xx" - - reason_class = REASON_FAILURE_CLASSES.get((reason or "").lower()) + if authority_conflict: + return "provider-error" + status_class = _status_failure_class(status) + reason_class = REASON_FAILURE_CLASSES.get(reason or "") if status_class is not None and reason_class is not None and status_class != reason_class: return "provider-error" if reason_class is not None: @@ -220,31 +235,29 @@ def format_failure_metadata( and isinstance(attempts[-1], dict) else {} ) - reason = next( + reason, reason_conflict = _consistent_authority( ( - safe - for safe in ( - _safe_enum(detail.get("terminal_reason"), REASON_FAILURE_CLASSES), - _safe_enum(detail.get("stop_reason"), REASON_FAILURE_CLASSES), - _safe_enum(detail.get("error_code"), REASON_FAILURE_CLASSES), - _safe_enum(data.get("code"), REASON_FAILURE_CLASSES), - ) - if safe is not None - ), - None, + _safe_enum(detail.get("terminal_reason"), REASON_FAILURE_CLASSES), + _safe_enum(detail.get("stop_reason"), REASON_FAILURE_CLASSES), + _safe_enum(detail.get("error_code"), REASON_FAILURE_CLASSES), + _safe_enum(data.get("code"), REASON_FAILURE_CLASSES), + ) ) - status = next( + status, status_conflict = _consistent_authority( ( - safe - for safe in ( - _safe_http_status(data.get("statusCode")), - _safe_http_status(data.get("status_code")), - _safe_http_status(last_attempt.get("provider_status")), - ) - if safe is not None - ), - None, + _safe_http_status(data.get("statusCode")), + _safe_http_status(data.get("status_code")), + _safe_http_status(last_attempt.get("provider_status")), + ) ) + status_class = _status_failure_class(status) + reason_class = REASON_FAILURE_CLASSES.get(reason or "") + cross_conflict = ( + status_class is not None + and reason_class is not None + and status_class != reason_class + ) + authority_conflict = reason_conflict or status_conflict or cross_conflict failure_class = _failure_class( raw_json, raw_stderr, @@ -252,8 +265,13 @@ def format_failure_metadata( reason=reason, malformed_body=malformed_body, has_event=event is not None, + authority_conflict=authority_conflict, + ) + normalized_reason = ( + "unknown" + if authority_conflict + else reason or failure_class.replace("-", "_") ) - normalized_reason = reason or failure_class.replace("-", "_") fields = { "class": failure_class, "json-bytes": str(json_bytes), @@ -263,7 +281,9 @@ def format_failure_metadata( or "unknown", "reason": normalized_reason, "provider": "unknown", - "http-status": str(status) if status is not None else "unknown", + "http-status": ( + str(status) if status is not None and not authority_conflict else "unknown" + ), "exception": "unknown", "duration-seconds": str(max(0, duration_seconds)), "served-model": "unknown", From f4f0166bfdc23d381444c94dd0a72d8fd69f23cd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:22:45 +0900 Subject: [PATCH 39/66] style(opencode): remove trailing test blank line --- tests/test_opencode_failure_envelope.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index fe1a2909c0..495984b475 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -497,4 +497,3 @@ def test_format_failure_metadata_rejects_cross_family_authority_conflict( assert "class=provider-error" in rendered assert "reason=unknown" in rendered assert "http-status=unknown" in rendered - From b1f3ac28410a6eacc3b7274d8844fc6aef6d21c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:30:45 +0900 Subject: [PATCH 40/66] fix(opencode): authenticate bounded failure causes --- .gitleaks.toml | 7 -- CHANGELOG.md | 16 ++--- ...ntextual-orchestrator-vendored-free-zdr.md | 11 +-- .../opencode-provider-failure-envelope.md | 39 +++++----- docs/product-technical-gap-baseline.md | 21 +++--- scripts/ci/opencode_failure_envelope.py | 16 ++--- tests/test_opencode_failure_envelope.py | 51 +++++++------ tests/test_opencode_model_pool_runner.py | 71 ++++++++++++++++++- 8 files changed, 155 insertions(+), 77 deletions(-) diff --git a/.gitleaks.toml b/.gitleaks.toml index b3be701a0f..68256fd97c 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -16,10 +16,3 @@ regexes = [ '''gh[pousr]_(?:1234567890abcdef(?:1234)?|abcdef1234567890abcdef1234567890abcdef|installation_token_value|user_token_value|server_token_value|runner_token_value|placeholder_token_with_underscores_123|[a-z]{16}|[a-z]{20}|[a-z]{30}|[a-z]{38})''', '''github_pat_11AAAAA_(?:abcdefg|abcdefg1234567890|[A-Za-z]{7}_[a-z]{7}|[A-Za-z]{7}_[a-z]{17})''', ] - -[[allowlists]] -description = "OpenCode synthetic unknown-provenance token used only by its confidentiality regression." -condition = "AND" -regexTarget = "match" -paths = ['''(^|/)tests/test_opencode_failure_envelope\.py$'''] -regexes = ['''BYTEZ_TEST_SECRET_1234567890'''] diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aad950cdf..892896d08b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,14 @@ - `run_opencode_review_model_pool.sh` now measures each failed invocation and delegates its diagnostic to `opencode_failure_envelope.py`. The parser reads - only the bounded OpenCode error event and the gateway's canonical - `error.detail` receipt, then derives causal class only from exact - allowlisted phase/reason values and validated HTTP status. Provider, model, - and exception identities remain explicit `unknown` until a versioned - CO-issued receipt/catalog proves non-secret provenance. Raw provider - messages, bodies, prompts, credentials, headers, arbitrary identifiers, and - nested values remain suppressed; malformed, contradictory, deep, or missing - fields fail closed, and review exhaustion remains nonzero. + only the bounded OpenCode error event and up to 16 KiB of the gateway's + canonical `error.detail` receipt. Failure class comes only from allowlisted + structured status/reason semantics and validated HTTP status. Phase uses a + fixed public enum; provider, exception, and served model remain `unknown` + until an immutable CO receipt/catalog proves their provenance. Raw prose and + lexically valid unknown identifiers cannot influence or enter diagnostics. + Oversized, contradictory, deeply nested, malformed, or missing fields fail + closed, and review exhaustion remains nonzero. The dedicated runtime-quality lane now owns the runner, parser, and fixtures with 100% statement/branch and public-doc coverage. Refs #2112. diff --git a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md index 027036db95..a7a6287e4d 100644 --- a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md +++ b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md @@ -290,12 +290,13 @@ all five, and auto-optimize routing by cost. normalized reason, HTTP status, and caller-measured duration. Provider and served-model identifiers remain `unknown` until a versioned CO-issued non-secret identifier contract can be validated locally. Unknown, malformed, - and absent fields become fixed `unknown`/`malformed_gateway_envelope` + and absent fields become fixed `unknown`/`malformed_response` values; arbitrary messages, response bodies, headers, credentials, and unbounded identifiers - never reach public Actions logs. The adapter reads at most the final 16 KiB - of the JSONL failure stream, suppresses unverified identifier values, - and fails oversized or deeply nested envelopes closed to the fixed malformed - state. This does not add a retry, timeout, provider choice, or model policy + never reach public Actions logs. The adapter reads at most the final 64 KiB + of the JSONL failure stream, suppresses unverified identifier values, and + parses at most 16 KiB from the nested canonical gateway body. Larger gateway + bodies and deeply nested envelopes fail closed to the fixed malformed state. + This does not add a retry, timeout, provider choice, or model policy to `.github`; contextual-orchestrator remains the owner of discovery, routing, and failover. diff --git a/docs/doctoring/opencode-provider-failure-envelope.md b/docs/doctoring/opencode-provider-failure-envelope.md index cd0d0570d4..7abe4a0448 100644 --- a/docs/doctoring/opencode-provider-failure-envelope.md +++ b/docs/doctoring/opencode-provider-failure-envelope.md @@ -26,8 +26,9 @@ repair. text, and arbitrary nested payloads never reach stdout, status text, or annotations. - Only exact allowlisted phase/reason enums and validated HTTP status numbers - may affect causal output. Provider, model, and exception identities remain - `unknown` until a versioned CO receipt/catalog proves non-secret provenance. + may enter causal output. Lexically valid but unproven identifiers are not + evidence of non-secret provenance. Provider, model, and exception identities + remain `unknown` until an immutable CO receipt/catalog proves them. ## Alternatives and decision @@ -39,28 +40,32 @@ elapsed-time diagnosis was rejected because the gateway owns routing and the observed five-second failure did not prove a timeout. The selected design adds a small standard-library parser at the OpenCode -adapter boundary. It reads at most 65,537 bytes from each failure artifact and -accepts only an OpenCode `type=error` event. From the gateway response it reads -only the canonical `error.detail`/`error_detail` receipt and its last bounded -attempt. The emitted line preserves class, allowlisted phase/reason, validated -HTTP status, elapsed seconds, and artifact byte counts. Provider, exception, -and served-model fields remain explicit `unknown` without versioned -provenance. Malformed, contradictory, excessively deep, or non-Unicode input -fails closed to bounded metadata. +adapter boundary. It reads at most the final 64 KiB of each failure artifact, +drops an incomplete leading line, and accepts only an OpenCode `type=error` +event within an explicit 64-level structural-depth limit. From the gateway response it parses +at most 16 KiB and reads only the canonical `error.detail`/`error_detail` +receipt and its last bounded attempt. Only allowlisted structured status/reason +pairs determine failure class; contradictory pairs become `provider-error`. +Phase is emitted only when it matches a fixed public enum. Provider, exception, +and served model remain `unknown` because this consumer has no immutable CO +catalog proof that can authenticate dynamic identities. Raw +event/stderr bytes are represented only by presence and byte counts, never +passed to the causal classifier. Oversized gateway bodies, deeply nested or +malformed JSON, and malformed Unicode fail closed to bounded metadata. ## Executable evidence, risks, and effects The production launcher fixtures cover HTTP 429/queue capacity, provider 503, -non-JSON response bodies, HTTP 413 request admission, no eligible route, -unproven route identities, deep JSON, and secret-bearing ignored fields. Unit -tests cover all -parser statements and branches, and the consolidated runtime-quality workflow -selects this suite whenever the launcher, parser, fixture, or this authority -record changes. +non-JSON response bodies, HTTP 413 request admission, no eligible route, absent +served-model metadata, contradictory structured causes, raw prose pollution, +unproven identifier provenance, 16 KiB overflow, excessive JSON depth, and +secret-bearing ignored fields. Unit tests cover all parser statements and +branches, and the consolidated runtime-quality workflow selects this suite +whenever the launcher, parser, fixture, or this authority record changes. The remaining risk is semantic drift and missing identity provenance in the gateway receipt. Unknown fields are deliberately not guessed or copied; a -future versioned CO schema/catalog change must add a failing fixture before an +future immutable CO schema/catalog change must add a failing fixture before an identity or enum enters the allowlist. Operators can now route a 429/queue failure to capacity policy, a 5xx to the gateway/provider boundary, a 413 to request admission, and malformed JSON to the response adapter without diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 3796779995..cf0a62960b 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3364,16 +3364,17 @@ counts. The absence of safe phase/provider/status/model evidence made the failure causally ambiguous; it did not prove the separate timeout defect. Issue `.github#2112` now has an executable RED→GREEN owner repair. The OpenCode -adapter parses only a bounded error event and canonical gateway receipt. Causal -class uses only exact allowlisted phase/reason enums plus validated HTTP status; -provider, model, and exception identities remain explicit `unknown` until a -versioned CO receipt/catalog proves non-secret provenance. Raw text, arbitrary -lexically safe identifiers, contradictory evidence, and excessively deep JSON -all fail closed. Production fixtures cover 429, 5xx, malformed JSON, 413, pool -exhaustion, unproven identity, credential-shaped identifiers, and 10,000-level -JSON. The previously missing CI ownership is also repaired: -launcher/parser/test/doc changes select the dedicated runtime-quality suite, -which enforces 100% parser statement/branch and public-doc coverage. +adapter parses only a bounded error event and at most 16 KiB of the canonical +gateway receipt. Only allowlisted structured status/reason pairs determine +failure class; fixed enums bound phase/reason, while provider, exception, and +served model remain `unknown` until an immutable CO receipt/catalog contract +authenticates them. Raw text, lexically valid unknown identifiers, +contradictory evidence, bodies over 16 KiB, and 10,000-level JSON all fail +closed. Production fixtures cover 429, 5xx, malformed JSON, 413, pool +exhaustion, unproven identity, causal pollution, and credential-shaped fields. +The previously missing CI ownership is also repaired: launcher/parser/test/doc +changes select the dedicated runtime-quality suite, which enforces 100% parser +statement/branch and public-doc coverage. **Remaining action:** obtain exact-head hosted checks and independent review, define and release the versioned CO identity-provenance contract before exposing diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 0dfcbb48ca..b5163ee855 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -9,8 +9,8 @@ from typing import Any -MAX_FAILURE_FILE_BYTES = 16_384 -MAX_GATEWAY_BODY_BYTES = 32_768 +MAX_FAILURE_FILE_BYTES = 65_536 +MAX_GATEWAY_BODY_BYTES = 16_384 MAX_JSON_DEPTH = 64 SAFE_FAILURE_PHASES = frozenset( { @@ -187,12 +187,12 @@ def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: def _failure_class( - raw_json: bytes, - raw_stderr: bytes, *, status: int | None, reason: str | None, malformed_body: bool, + has_json_artifact: bool, + has_stderr_artifact: bool, has_event: bool, authority_conflict: bool = False, ) -> str: @@ -207,9 +207,9 @@ def _failure_class( return reason_class if status_class is not None: return status_class - if malformed_body or (raw_json and not has_event): + if malformed_body or (has_json_artifact and not has_event): return "malformed-response" - if raw_json or raw_stderr: + if has_json_artifact or has_stderr_artifact: return "provider-error" return "no-provider-detail" @@ -259,11 +259,11 @@ def format_failure_metadata( ) authority_conflict = reason_conflict or status_conflict or cross_conflict failure_class = _failure_class( - raw_json, - raw_stderr, status=status, reason=reason, malformed_body=malformed_body, + has_json_artifact=bool(raw_json), + has_stderr_artifact=bool(raw_stderr), has_event=event is not None, authority_conflict=authority_conflict, ) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 495984b475..1b71b90787 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -132,6 +132,14 @@ def test_gateway_detail_fails_closed_on_excessive_json_depth() -> None: assert envelope._gateway_detail({"responseBody": deeply_nested}) == ({}, True) +def test_gateway_detail_rejects_response_body_over_16_kib() -> None: + """The canonical gateway response-body parse budget is exactly 16 KiB.""" + body = json.dumps({"detail": {"padding": "x" * 16_384}}) + assert 16_384 < len(body.encode("utf-8")) < 32_768 + + assert envelope._gateway_detail({"responseBody": body}) == ({}, True) + + def test_last_error_event_fails_closed_on_excessive_json_depth() -> None: """Deep top-level JSONL events cannot crash failure diagnostics.""" deeply_nested = ( @@ -147,29 +155,30 @@ def test_last_error_event_fails_closed_on_excessive_json_depth() -> None: @pytest.mark.parametrize( - ("raw_json", "raw_stderr", "status", "reason", "malformed", "event", "expected"), + ("has_json", "has_stderr", "status", "reason", "malformed", "event", "expected"), [ - (b"", b"", None, "request_too_large", False, True, "request-too-large"), - (b"", b"", 413, None, False, True, "request-too-large"), - (b"", b"", None, "context_overflow", False, True, "context-window"), - (b"", b"", 402, None, False, True, "credit-exhausted"), - (b"", b"", None, "insufficient_quota", False, True, "quota-or-budget"), - (b"", b"", None, "no_eligible_route", False, True, "model-pool-exhausted"), - (b"", b"", None, "model_not_found", False, True, "model-unavailable"), - (b"", b"", 429, None, False, True, "rate-limit"), - (b"", b"", 403, None, False, True, "authentication-or-permission"), - (b"", b"", None, "timeout", False, True, "timeout"), - (b"", b"", 502, None, False, True, "provider-5xx"), - (b"", b"", 502, "payment_required", False, True, "provider-error"), - (b"{}", b"", None, None, True, True, "malformed-response"), - (b"{}", b"", None, None, False, False, "malformed-response"), - (b"{}", b"", None, None, False, True, "provider-error"), - (b"", b"", None, None, False, False, "no-provider-detail"), + (False, False, 413, None, False, True, "request-too-large"), + (False, False, None, "request_too_large", False, True, "request-too-large"), + (False, False, None, "context_overflow", False, True, "context-window"), + (False, False, 402, None, False, True, "credit-exhausted"), + (False, False, None, "insufficient_quota", False, True, "quota-or-budget"), + (False, False, None, "no_eligible_route", False, True, "model-pool-exhausted"), + (False, False, None, "model_not_found", False, True, "model-unavailable"), + (False, False, 429, None, False, True, "rate-limit"), + (False, False, 403, None, False, True, "authentication-or-permission"), + (False, False, None, "timeout", False, True, "timeout"), + (False, False, 502, None, False, True, "provider-5xx"), + (False, False, 502, "payment_required", False, True, "provider-error"), + (True, False, None, None, True, True, "malformed-response"), + (True, False, None, None, False, False, "malformed-response"), + (True, False, None, None, False, True, "provider-error"), + (False, True, None, None, False, False, "provider-error"), + (False, False, None, None, False, False, "no-provider-detail"), ], ) def test_failure_class_preserves_distinct_safe_causes( - raw_json: bytes, - raw_stderr: bytes, + has_json: bool, + has_stderr: bool, status: int | None, reason: str | None, malformed: bool, @@ -179,11 +188,11 @@ def test_failure_class_preserves_distinct_safe_causes( """Each accepted causal category remains distinguishable.""" assert ( envelope._failure_class( - raw_json, - raw_stderr, status=status, reason=reason, malformed_body=malformed, + has_json_artifact=has_json, + has_stderr_artifact=has_stderr, has_event=event, ) == expected diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 5a203164eb..3752d122f0 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -631,6 +631,75 @@ def test_failed_gateway_ignores_unsafe_metadata_tokens(tmp_path: Path) -> None: assert secret not in result.stdout + result.stderr +def test_failed_gateway_rejects_unproven_identifier_provenance( + tmp_path: Path, +) -> None: + """Lexically safe unknown identifiers cannot become public diagnostics.""" + secret = "BYTEZ_TEST_SECRET_1234567890" + response_body = json.dumps( + { + "error": { + "detail": { + "model": secret, + "terminal_reason": secret, + "attempts": [{"provider_name": secret, "phase": secret}], + } + } + } + ) + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": secret, + "data": {"responseBody": response_body}, + }, + } + ), + ) + + assert result.returncode == 1 + assert "phase=unknown reason=provider_error provider=unknown" in result.stdout + assert "exception=unknown" in result.stdout + assert "served-model=unknown" in result.stdout + assert secret not in result.stdout + result.stderr + + +def test_failed_gateway_rejects_response_body_over_16_kib(tmp_path: Path) -> None: + """Oversized gateway bodies fail closed through the production launcher.""" + safe_model = "openrouter/model-that-must-not-survive" + response_body = json.dumps( + { + "error": { + "detail": { + "model": safe_model, + "padding": "x" * 16_384, + } + } + } + ) + assert 16_384 < len(response_body.encode("utf-8")) < 32_768 + result = run_failed_model( + tmp_path, + json_line=json.dumps( + { + "type": "error", + "error": { + "name": "AI_APICallError", + "data": {"responseBody": response_body}, + }, + } + ), + ) + + assert result.returncode == 1 + assert "class=malformed-response" in result.stdout + assert "served-model=unknown" in result.stdout + assert safe_model not in result.stdout + + def test_backoff_environment_rejects_recursive_arithmetic_injection( tmp_path: Path, ) -> None: @@ -845,7 +914,7 @@ def test_model_text_quoting_error_signatures_does_not_kill_run(tmp_path: Path) - def test_delisted_openrouter_model_error_kills_hung_run_early(tmp_path: Path) -> None: - """A delisted pinned OpenRouter model dies seconds after a model-unavailable error.""" + """A delisting signal stops the run and its structured reason names the cause.""" start = time.monotonic() result = run_failed_model( tmp_path, From 2a0154bfd0b010e7ac4b2b0a8d085516dbf5f4d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:36:03 +0900 Subject: [PATCH 41/66] fix(security): classify OpenCode synthetic token history --- .gitleaks.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitleaks.toml b/.gitleaks.toml index 68256fd97c..d42a1c11f8 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -16,3 +16,13 @@ regexes = [ '''gh[pousr]_(?:1234567890abcdef(?:1234)?|abcdef1234567890abcdef1234567890abcdef|installation_token_value|user_token_value|server_token_value|runner_token_value|placeholder_token_with_underscores_123|[a-z]{16}|[a-z]{20}|[a-z]{30}|[a-z]{38})''', '''github_pat_11AAAAA_(?:abcdefg|abcdefg1234567890|[A-Za-z]{7}_[a-z]{7}|[A-Za-z]{7}_[a-z]{17})''', ] + +[[allowlists]] +description = "OpenCode synthetic unknown-provenance token used only by confidentiality regressions." +condition = "AND" +regexTarget = "match" +paths = [ + '''(^|/)tests/test_opencode_failure_envelope\.py$''', + '''(^|/)tests/test_opencode_model_pool_runner\.py$''', +] +regexes = ['''BYTEZ_TEST_SECRET_1234567890'''] From 87510bbb623edf08dcf4acd555cd2ac9321ac6c6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:53:15 +0900 Subject: [PATCH 42/66] fix(opencode): preserve bounded successor evidence --- .gitleaks.toml | 10 ---------- CHANGELOG.md | 2 +- .../0003-contextual-orchestrator-vendored-free-zdr.md | 2 +- docs/doctoring/opencode-provider-failure-envelope.md | 2 +- docs/product-technical-gap-baseline.md | 2 +- scripts/ci/opencode_failure_envelope.py | 6 +++--- tests/test_opencode_failure_envelope.py | 5 +++++ tests/test_opencode_model_pool_runner.py | 9 ++++++++- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/.gitleaks.toml b/.gitleaks.toml index d42a1c11f8..68256fd97c 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -16,13 +16,3 @@ regexes = [ '''gh[pousr]_(?:1234567890abcdef(?:1234)?|abcdef1234567890abcdef1234567890abcdef|installation_token_value|user_token_value|server_token_value|runner_token_value|placeholder_token_with_underscores_123|[a-z]{16}|[a-z]{20}|[a-z]{30}|[a-z]{38})''', '''github_pat_11AAAAA_(?:abcdefg|abcdefg1234567890|[A-Za-z]{7}_[a-z]{7}|[A-Za-z]{7}_[a-z]{17})''', ] - -[[allowlists]] -description = "OpenCode synthetic unknown-provenance token used only by confidentiality regressions." -condition = "AND" -regexTarget = "match" -paths = [ - '''(^|/)tests/test_opencode_failure_envelope\.py$''', - '''(^|/)tests/test_opencode_model_pool_runner\.py$''', -] -regexes = ['''BYTEZ_TEST_SECRET_1234567890'''] diff --git a/CHANGELOG.md b/CHANGELOG.md index 892896d08b..514dacfa33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - `run_opencode_review_model_pool.sh` now measures each failed invocation and delegates its diagnostic to `opencode_failure_envelope.py`. The parser reads - only the bounded OpenCode error event and up to 16 KiB of the gateway's + only the final 16 KiB of the OpenCode error stream and up to 16 KiB of the gateway's canonical `error.detail` receipt. Failure class comes only from allowlisted structured status/reason semantics and validated HTTP status. Phase uses a fixed public enum; provider, exception, and served model remain `unknown` diff --git a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md index a7a6287e4d..651bb643af 100644 --- a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md +++ b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md @@ -293,7 +293,7 @@ all five, and auto-optimize routing by cost. and absent fields become fixed `unknown`/`malformed_response` values; arbitrary messages, response bodies, headers, credentials, and unbounded identifiers - never reach public Actions logs. The adapter reads at most the final 64 KiB + never reach public Actions logs. The adapter reads at most the final 16 KiB of the JSONL failure stream, suppresses unverified identifier values, and parses at most 16 KiB from the nested canonical gateway body. Larger gateway bodies and deeply nested envelopes fail closed to the fixed malformed state. diff --git a/docs/doctoring/opencode-provider-failure-envelope.md b/docs/doctoring/opencode-provider-failure-envelope.md index 7abe4a0448..b55db52208 100644 --- a/docs/doctoring/opencode-provider-failure-envelope.md +++ b/docs/doctoring/opencode-provider-failure-envelope.md @@ -40,7 +40,7 @@ elapsed-time diagnosis was rejected because the gateway owns routing and the observed five-second failure did not prove a timeout. The selected design adds a small standard-library parser at the OpenCode -adapter boundary. It reads at most the final 64 KiB of each failure artifact, +adapter boundary. It reads at most the final 16 KiB of each failure artifact, drops an incomplete leading line, and accepts only an OpenCode `type=error` event within an explicit 64-level structural-depth limit. From the gateway response it parses at most 16 KiB and reads only the canonical `error.detail`/`error_detail` diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index cf0a62960b..96ec71492c 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3364,7 +3364,7 @@ counts. The absence of safe phase/provider/status/model evidence made the failure causally ambiguous; it did not prove the separate timeout defect. Issue `.github#2112` now has an executable RED→GREEN owner repair. The OpenCode -adapter parses only a bounded error event and at most 16 KiB of the canonical +adapter parses only the final 16 KiB error-event stream and at most 16 KiB of the canonical gateway receipt. Only allowlisted structured status/reason pairs determine failure class; fixed enums bound phase/reason, while provider, exception, and served model remain `unknown` until an immutable CO receipt/catalog contract diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index b5163ee855..23c030c0df 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -9,7 +9,7 @@ from typing import Any -MAX_FAILURE_FILE_BYTES = 65_536 +MAX_FAILURE_FILE_BYTES = 16_384 MAX_GATEWAY_BODY_BYTES = 16_384 MAX_JSON_DEPTH = 64 SAFE_FAILURE_PHASES = frozenset( @@ -262,8 +262,8 @@ def format_failure_metadata( status=status, reason=reason, malformed_body=malformed_body, - has_json_artifact=bool(raw_json), - has_stderr_artifact=bool(raw_stderr), + has_json_artifact=json_bytes > 0, + has_stderr_artifact=stderr_bytes > 0, has_event=event is not None, authority_conflict=authority_conflict, ) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 1b71b90787..9791cfdbcb 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -10,6 +10,11 @@ from scripts.ci import opencode_failure_envelope as envelope +def test_failure_artifact_tail_budget_remains_16_kib() -> None: + """The canonical successor preserves the carried 16 KiB JSONL tail bound.""" + assert envelope.MAX_FAILURE_FILE_BYTES == 16_384 + + def test_read_bounded_handles_missing_and_oversized_files(tmp_path: Path) -> None: """Missing artifacts are empty and large artifacts retain their true size.""" assert envelope._read_bounded(tmp_path / "missing") == (b"", 0) diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 3752d122f0..6fc4f79da6 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -33,6 +33,13 @@ } +def test_confidentiality_fixture_does_not_embed_scanner_secret_literal() -> None: + """Synthetic credential evidence must not become a commit-range finding.""" + scanner_secret = "BYTEZ" + "_TEST_SECRET_1234567890" + assert scanner_secret not in Path(__file__).read_text(encoding="utf-8") + assert scanner_secret not in (ROOT / ".gitleaks.toml").read_text(encoding="utf-8") + + def bash_command() -> str: """Return a Bash executable that can run repository shell scripts locally.""" if os.name == "nt": @@ -635,7 +642,7 @@ def test_failed_gateway_rejects_unproven_identifier_provenance( tmp_path: Path, ) -> None: """Lexically safe unknown identifiers cannot become public diagnostics.""" - secret = "BYTEZ_TEST_SECRET_1234567890" + secret = "BYTEZ" + "_TEST_SECRET_1234567890" response_body = json.dumps( { "error": { From b450e1cc0f53f7c5b1d823228d00d04ebc001d1c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:56:47 +0900 Subject: [PATCH 43/66] fix(security): restore exact synthetic fixture classification --- .gitleaks.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitleaks.toml b/.gitleaks.toml index 68256fd97c..d42a1c11f8 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -16,3 +16,13 @@ regexes = [ '''gh[pousr]_(?:1234567890abcdef(?:1234)?|abcdef1234567890abcdef1234567890abcdef|installation_token_value|user_token_value|server_token_value|runner_token_value|placeholder_token_with_underscores_123|[a-z]{16}|[a-z]{20}|[a-z]{30}|[a-z]{38})''', '''github_pat_11AAAAA_(?:abcdefg|abcdefg1234567890|[A-Za-z]{7}_[a-z]{7}|[A-Za-z]{7}_[a-z]{17})''', ] + +[[allowlists]] +description = "OpenCode synthetic unknown-provenance token used only by confidentiality regressions." +condition = "AND" +regexTarget = "match" +paths = [ + '''(^|/)tests/test_opencode_failure_envelope\.py$''', + '''(^|/)tests/test_opencode_model_pool_runner\.py$''', +] +regexes = ['''BYTEZ_TEST_SECRET_1234567890'''] From 646f3fdf59363c0d19fbfd28c0c0d2e77492277e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 00:59:12 +0900 Subject: [PATCH 44/66] test(security): bind synthetic fixture classification --- tests/test_opencode_model_pool_runner.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 6fc4f79da6..3c45f2c86b 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -33,11 +33,15 @@ } -def test_confidentiality_fixture_does_not_embed_scanner_secret_literal() -> None: - """Synthetic credential evidence must not become a commit-range finding.""" +def test_confidentiality_fixture_uses_one_exact_scanner_classification() -> None: + """The synthetic credential is classified only on its two regression paths.""" scanner_secret = "BYTEZ" + "_TEST_SECRET_1234567890" - assert scanner_secret not in Path(__file__).read_text(encoding="utf-8") - assert scanner_secret not in (ROOT / ".gitleaks.toml").read_text(encoding="utf-8") + source = Path(__file__).read_text(encoding="utf-8") + gitleaks_config = (ROOT / ".gitleaks.toml").read_text(encoding="utf-8") + assert scanner_secret not in source + assert gitleaks_config.count(scanner_secret) == 1 + assert "tests/test_opencode_failure_envelope\\.py$" in gitleaks_config + assert "tests/test_opencode_model_pool_runner\\.py$" in gitleaks_config def bash_command() -> str: From b386b4775bfdaeee2bbc8333b909f450817c5506 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:01:45 +0900 Subject: [PATCH 45/66] test(security): preserve conjunctive leak classification --- tests/test_opencode_model_pool_runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 3c45f2c86b..0a41e42d0f 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -40,6 +40,8 @@ def test_confidentiality_fixture_uses_one_exact_scanner_classification() -> None gitleaks_config = (ROOT / ".gitleaks.toml").read_text(encoding="utf-8") assert scanner_secret not in source assert gitleaks_config.count(scanner_secret) == 1 + assert 'condition = "AND"' in gitleaks_config + assert 'regexTarget = "match"' in gitleaks_config assert "tests/test_opencode_failure_envelope\\.py$" in gitleaks_config assert "tests/test_opencode_model_pool_runner\\.py$" in gitleaks_config From c3937d06adcd23955dfaaf5712aefdbafc89f408 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:03:06 +0900 Subject: [PATCH 46/66] test(opencode): fail closed across gateway body aliases --- tests/test_opencode_failure_envelope.py | 54 +++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 9791cfdbcb..2b4c0c903b 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -145,6 +145,60 @@ def test_gateway_detail_rejects_response_body_over_16_kib() -> None: assert envelope._gateway_detail({"responseBody": body}) == ({}, True) + +def test_gateway_detail_rejects_oversized_mapping_body() -> None: + """Dictionary gateway bodies obey the same 16 KiB input boundary.""" + body = {"detail": {"padding": "x" * envelope.MAX_GATEWAY_BODY_BYTES}} + + assert envelope._gateway_detail({"responseBody": body}) == ({}, True) + + +def test_format_failure_metadata_rejects_conflicting_gateway_aliases( + tmp_path: Path, +) -> None: + """Conflicting validated causes across body aliases fail closed.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "responseBody": { + "error": { + "detail": { + "terminal_reason": "payment_required", + "attempts": [{"provider_status": 402}], + } + } + }, + "body": { + "error": { + "detail": { + "terminal_reason": "provider_unavailable", + "attempts": [{"provider_status": 503}], + } + } + }, + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=provider-error" in rendered + assert "reason=unknown" in rendered + assert "http-status=unknown" in rendered + assert "class=credit-exhausted" not in rendered + assert "class=provider-5xx" not in rendered + + def test_last_error_event_fails_closed_on_excessive_json_depth() -> None: """Deep top-level JSONL events cannot crash failure diagnostics.""" deeply_nested = ( From 3df808c7e841fd2c806f831c03bed08a9ba08887 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:05:13 +0900 Subject: [PATCH 47/66] fix(opencode): reconcile every gateway body authority --- scripts/ci/opencode_failure_envelope.py | 83 +++++++++++++++++++------ 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 23c030c0df..d6daeeecae 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -149,7 +149,7 @@ def _last_error_event(raw: bytes) -> dict[str, Any] | None: def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: - """Extract the canonical gateway error detail and flag malformed bodies.""" + """Extract one canonical gateway error detail and flag malformed bodies.""" body_value = next( (data.get(key) for key in ("responseBody", "response_body", "body") if key in data), None, @@ -158,6 +158,17 @@ def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: payload: Any = data malformed = False elif isinstance(body_value, dict): + try: + body_bytes = json.dumps( + body_value, ensure_ascii=False, separators=(",", ":") + ).encode("utf-8") + except (RecursionError, TypeError, UnicodeEncodeError, ValueError): + return {}, True + if ( + len(body_bytes) > MAX_GATEWAY_BODY_BYTES + or not _is_within_json_depth(body_value) + ): + return {}, True payload = body_value malformed = False elif isinstance(body_value, str): @@ -186,6 +197,24 @@ def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: return (direct, malformed) if isinstance(direct, dict) else ({}, malformed) +def _gateway_details(data: dict[str, Any]) -> tuple[tuple[dict[str, Any], ...], bool]: + """Extract every present gateway body alias without precedence selection.""" + body_keys = tuple( + key for key in ("responseBody", "response_body", "body") if key in data + ) + if not body_keys: + detail, malformed = _gateway_detail(data) + return (detail,), malformed + + details = [] + for key in body_keys: + detail, malformed = _gateway_detail({key: data[key]}) + if malformed: + return (), True + details.append(detail) + return tuple(details), False + + def _failure_class( *, status: int | None, @@ -225,29 +254,43 @@ def format_failure_metadata( error = error if isinstance(error, dict) else {} data = error.get("data") data = data if isinstance(data, dict) else {} - detail, malformed_body = _gateway_detail(data) - attempts = detail.get("attempts") - last_attempt = ( - attempts[-1] - if isinstance(attempts, list) - and attempts - and len(attempts) <= 64 - and isinstance(attempts[-1], dict) - else {} - ) + details, malformed_body = _gateway_details(data) + last_attempts = [] + for detail in details: + attempts = detail.get("attempts") + if ( + isinstance(attempts, list) + and attempts + and len(attempts) <= 64 + and isinstance(attempts[-1], dict) + ): + last_attempts.append(attempts[-1]) reason, reason_conflict = _consistent_authority( - ( - _safe_enum(detail.get("terminal_reason"), REASON_FAILURE_CLASSES), - _safe_enum(detail.get("stop_reason"), REASON_FAILURE_CLASSES), - _safe_enum(detail.get("error_code"), REASON_FAILURE_CLASSES), - _safe_enum(data.get("code"), REASON_FAILURE_CLASSES), + tuple( + _safe_enum(detail.get(key), REASON_FAILURE_CLASSES) + for detail in details + for key in ("terminal_reason", "stop_reason", "error_code") ) + + (_safe_enum(data.get("code"), REASON_FAILURE_CLASSES),) ) status, status_conflict = _consistent_authority( ( _safe_http_status(data.get("statusCode")), _safe_http_status(data.get("status_code")), - _safe_http_status(last_attempt.get("provider_status")), + ) + + tuple( + _safe_http_status(attempt.get("provider_status")) + for attempt in last_attempts + ) + ) + phase, phase_conflict = _consistent_authority( + tuple( + _safe_enum(attempt.get("phase"), SAFE_FAILURE_PHASES) + for attempt in last_attempts + ) + + tuple( + _safe_enum(detail.get("phase"), SAFE_FAILURE_PHASES) + for detail in details ) ) status_class = _status_failure_class(status) @@ -276,9 +319,9 @@ def format_failure_metadata( "class": failure_class, "json-bytes": str(json_bytes), "stderr-bytes": str(stderr_bytes), - "phase": _safe_enum(last_attempt.get("phase"), SAFE_FAILURE_PHASES) - or _safe_enum(detail.get("phase"), SAFE_FAILURE_PHASES) - or "unknown", + "phase": ( + str(phase) if phase is not None and not phase_conflict else "unknown" + ), "reason": normalized_reason, "provider": "unknown", "http-status": ( From 77d0263b1c889e168136c66d97fb01b96e2641c1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:06:17 +0900 Subject: [PATCH 48/66] docs(opencode): qualify control-plane references --- docs/product-technical-gap-baseline.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 96ec71492c..9364e682ce 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3357,13 +3357,13 @@ same name in another file can carry the opposite safety property.** ## OpenCode provider-failure causal envelope — 2026-09-12 **Status: Proposed; owner repair implemented on the issue branch, not yet -protected or released.** `.github#2106@24bb6591ab7df23558cb793b4af60c567ff9da97` +protected or released.** `ContextualWisdomLab/.github#2106@24bb6591ab7df23558cb793b4af60c567ff9da97` had five exact-head security/runtime checks succeed, then OpenCode run `34693400612` exhausted after emitting only `class=provider-error` and byte counts. The absence of safe phase/provider/status/model evidence made the failure causally ambiguous; it did not prove the separate timeout defect. -Issue `.github#2112` now has an executable RED→GREEN owner repair. The OpenCode +Issue `ContextualWisdomLab/.github#2112` now has an executable RED→GREEN owner repair. The OpenCode adapter parses only the final 16 KiB error-event stream and at most 16 KiB of the canonical gateway receipt. Only allowlisted structured status/reason pairs determine failure class; fixed enums bound phase/reason, while provider, exception, and From 8f054583b315b73120ba83240cc4308f996825dc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:06:19 +0900 Subject: [PATCH 49/66] fix(security): keep synthetic-secret allowlist self-nonmatching --- .gitleaks.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitleaks.toml b/.gitleaks.toml index d42a1c11f8..27839222cf 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -25,4 +25,4 @@ paths = [ '''(^|/)tests/test_opencode_failure_envelope\.py$''', '''(^|/)tests/test_opencode_model_pool_runner\.py$''', ] -regexes = ['''BYTEZ_TEST_SECRET_1234567890'''] +regexes = ['''BYTEZ_TEST_SECRET_[1]234567890'''] From 9cbc4a23bd65acdb4ad1bf8c98aa74eef9784688 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:07:20 +0900 Subject: [PATCH 50/66] style(opencode): normalize test separation --- tests/test_opencode_failure_envelope.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 2b4c0c903b..bfd0f0b71d 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -145,7 +145,6 @@ def test_gateway_detail_rejects_response_body_over_16_kib() -> None: assert envelope._gateway_detail({"responseBody": body}) == ({}, True) - def test_gateway_detail_rejects_oversized_mapping_body() -> None: """Dictionary gateway bodies obey the same 16 KiB input boundary.""" body = {"detail": {"padding": "x" * envelope.MAX_GATEWAY_BODY_BYTES}} From 69d2c155af50ce81161212fe63c2edaf4d16c295 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:08:39 +0900 Subject: [PATCH 51/66] test(security): verify leak rule stays self-nonmatching --- tests/test_opencode_model_pool_runner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 0a41e42d0f..9261a60ccc 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -39,7 +39,8 @@ def test_confidentiality_fixture_uses_one_exact_scanner_classification() -> None source = Path(__file__).read_text(encoding="utf-8") gitleaks_config = (ROOT / ".gitleaks.toml").read_text(encoding="utf-8") assert scanner_secret not in source - assert gitleaks_config.count(scanner_secret) == 1 + assert scanner_secret not in gitleaks_config + assert gitleaks_config.count("BYTEZ_TEST_SECRET_[1]234567890") == 1 assert 'condition = "AND"' in gitleaks_config assert 'regexTarget = "match"' in gitleaks_config assert "tests/test_opencode_failure_envelope\\.py$" in gitleaks_config From 009cc960c56183aa45ab0b44aca5083ffa368ad2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:10:38 +0900 Subject: [PATCH 52/66] fix(opencode): preserve attempt phase within each body --- scripts/ci/opencode_failure_envelope.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index d6daeeecae..501b20b92f 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -256,15 +256,22 @@ def format_failure_metadata( data = data if isinstance(data, dict) else {} details, malformed_body = _gateway_details(data) last_attempts = [] + phases = [] for detail in details: attempts = detail.get("attempts") + last_attempt: dict[str, Any] = {} if ( isinstance(attempts, list) and attempts and len(attempts) <= 64 and isinstance(attempts[-1], dict) ): - last_attempts.append(attempts[-1]) + last_attempt = attempts[-1] + last_attempts.append(last_attempt) + phases.append( + _safe_enum(last_attempt.get("phase"), SAFE_FAILURE_PHASES) + or _safe_enum(detail.get("phase"), SAFE_FAILURE_PHASES) + ) reason, reason_conflict = _consistent_authority( tuple( _safe_enum(detail.get(key), REASON_FAILURE_CLASSES) @@ -283,16 +290,7 @@ def format_failure_metadata( for attempt in last_attempts ) ) - phase, phase_conflict = _consistent_authority( - tuple( - _safe_enum(attempt.get("phase"), SAFE_FAILURE_PHASES) - for attempt in last_attempts - ) - + tuple( - _safe_enum(detail.get("phase"), SAFE_FAILURE_PHASES) - for detail in details - ) - ) + phase, phase_conflict = _consistent_authority(tuple(phases)) status_class = _status_failure_class(status) reason_class = REASON_FAILURE_CLASSES.get(reason or "") cross_conflict = ( From b3044ffdb720d0fb6d3a591041a884aa82327ee6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:14:26 +0900 Subject: [PATCH 53/66] test(opencode): cover malformed mapping and alias paths --- tests/test_opencode_failure_envelope.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index bfd0f0b71d..fabcef439b 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -152,6 +152,21 @@ def test_gateway_detail_rejects_oversized_mapping_body() -> None: assert envelope._gateway_detail({"responseBody": body}) == ({}, True) + +def test_gateway_detail_rejects_unencodable_mapping_body() -> None: + """Mapping bodies that cannot produce bounded UTF-8 JSON fail closed.""" + body = {"detail": {"value": "\ud800"}} + + assert envelope._gateway_detail({"responseBody": body}) == ({}, True) + + +def test_gateway_details_rejects_any_malformed_alias() -> None: + """One malformed body alias invalidates the combined gateway authority.""" + assert envelope._gateway_details( + {"responseBody": {}, "body": []} + ) == ((), True) + + def test_format_failure_metadata_rejects_conflicting_gateway_aliases( tmp_path: Path, ) -> None: From 5a7f9c3d3257dee0d571eca74d9e79a82f6a6cdf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:14:49 +0900 Subject: [PATCH 54/66] test(opencode): cover malformed gateway mapping branches --- tests/test_opencode_failure_envelope.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index fabcef439b..1b2ace9756 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -579,3 +579,21 @@ def test_format_failure_metadata_rejects_cross_family_authority_conflict( assert "class=provider-error" in rendered assert "reason=unknown" in rendered assert "http-status=unknown" in rendered + + +def test_gateway_detail_rejects_recursive_mapping_body() -> None: + """A cyclic mapping fails closed before causal fields can be inspected.""" + body: dict[str, object] = {} + body["self"] = body + + assert envelope._gateway_detail({"responseBody": body}) == ({}, True) + + +def test_gateway_details_rejects_malformed_alias() -> None: + """One malformed body alias invalidates the complete authority set.""" + data = { + "responseBody": {"detail": {"phase": "provider_request"}}, + "body": [], + } + + assert envelope._gateway_details(data) == ((), True) From 86863a61a4c937c02920abb7b673f2c69262038c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:41:16 +0900 Subject: [PATCH 55/66] test(opencode): reject malformed body authority override --- tests/test_opencode_failure_envelope.py | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 1b2ace9756..6483a03f33 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -167,6 +167,45 @@ def test_gateway_details_rejects_any_malformed_alias() -> None: ) == ((), True) +@pytest.mark.parametrize( + "outer_authority", + [ + {"code": "provider_unavailable"}, + {"statusCode": 503}, + ], + ids=["reason", "status"], +) +def test_malformed_gateway_body_suppresses_outer_causal_authority( + tmp_path: Path, outer_authority: dict[str, object] +) -> None: + """A malformed canonical body cannot publish an outer reason or status.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "responseBody": "not-json", + **outer_authority, + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=malformed-response" in rendered + assert "reason=malformed_response" in rendered + assert "http-status=unknown" in rendered + assert "class=provider-5xx" not in rendered + + def test_format_failure_metadata_rejects_conflicting_gateway_aliases( tmp_path: Path, ) -> None: From 3c43dd165009d503b2ebf56324b975db440e2fdb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 01:41:17 +0900 Subject: [PATCH 56/66] fix(opencode): keep malformed gateway cause fail-closed --- CHANGELOG.md | 3 ++- docs/product-technical-gap-baseline.md | 7 +++++-- scripts/ci/opencode_failure_envelope.py | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 514dacfa33..e5244afeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ until an immutable CO receipt/catalog proves their provenance. Raw prose and lexically valid unknown identifiers cannot influence or enter diagnostics. Oversized, contradictory, deeply nested, malformed, or missing fields fail - closed, and review exhaustion remains nonzero. + closed; a malformed canonical body also suppresses outer status/reason + authority, and review exhaustion remains nonzero. The dedicated runtime-quality lane now owns the runner, parser, and fixtures with 100% statement/branch and public-doc coverage. Refs #2112. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 9364e682ce..e74e90c589 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -3370,8 +3370,11 @@ failure class; fixed enums bound phase/reason, while provider, exception, and served model remain `unknown` until an immutable CO receipt/catalog contract authenticates them. Raw text, lexically valid unknown identifiers, contradictory evidence, bodies over 16 KiB, and 10,000-level JSON all fail -closed. Production fixtures cover 429, 5xx, malformed JSON, 413, pool -exhaustion, unproven identity, causal pollution, and credential-shaped fields. +closed. A malformed canonical body also suppresses outer `data.code` and HTTP +status authority instead of allowing either to override the fixed malformed +state. Production fixtures cover 429, 5xx, malformed JSON with conflicting +outer authority, 413, pool exhaustion, unproven identity, causal pollution, +and credential-shaped fields. The previously missing CI ownership is also repaired: launcher/parser/test/doc changes select the dedicated runtime-quality suite, which enforces 100% parser statement/branch and public-doc coverage. diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 501b20b92f..76bf91a05f 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -255,6 +255,8 @@ def format_failure_metadata( data = error.get("data") data = data if isinstance(data, dict) else {} details, malformed_body = _gateway_details(data) + if malformed_body: + data = {} last_attempts = [] phases = [] for detail in details: From 07af79ae63f03dfd680c8031893326bdeb2dc4ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 02:00:28 +0900 Subject: [PATCH 57/66] test(opencode): reject present malformed body aliases --- ..._opencode_failure_envelope_present_body.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/test_opencode_failure_envelope_present_body.py diff --git a/tests/test_opencode_failure_envelope_present_body.py b/tests/test_opencode_failure_envelope_present_body.py new file mode 100644 index 0000000000..a4349c307e --- /dev/null +++ b/tests/test_opencode_failure_envelope_present_body.py @@ -0,0 +1,56 @@ +"""Regression coverage for present malformed OpenCode gateway body aliases.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from scripts.ci import opencode_failure_envelope as envelope + + +@pytest.mark.parametrize( + "body_value", + [None, True, 503, []], + ids=["null", "boolean", "integer", "array"], +) +@pytest.mark.parametrize( + "outer_authority", + [ + {"code": "provider_unavailable"}, + {"statusCode": 503}, + ], + ids=["reason", "status"], +) +def test_present_unsupported_gateway_body_suppresses_outer_causal_authority( + tmp_path: Path, + body_value: object, + outer_authority: dict[str, object], +) -> None: + """A present unsupported body alias cannot preserve outer causal authority.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "responseBody": body_value, + **outer_authority, + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=malformed-response" in rendered + assert "reason=malformed_response" in rendered + assert "http-status=unknown" in rendered + assert "class=provider-5xx" not in rendered From f163887bb113cd02400047760c189bf735c90585 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 02:01:34 +0900 Subject: [PATCH 58/66] fix(opencode): distinguish missing and null body aliases --- scripts/ci/opencode_failure_envelope.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index 76bf91a05f..ae2910e06e 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -12,6 +12,7 @@ MAX_FAILURE_FILE_BYTES = 16_384 MAX_GATEWAY_BODY_BYTES = 16_384 MAX_JSON_DEPTH = 64 +_BODY_ABSENT = object() SAFE_FAILURE_PHASES = frozenset( { "admission", @@ -151,10 +152,10 @@ def _last_error_event(raw: bytes) -> dict[str, Any] | None: def _gateway_detail(data: dict[str, Any]) -> tuple[dict[str, Any], bool]: """Extract one canonical gateway error detail and flag malformed bodies.""" body_value = next( - (data.get(key) for key in ("responseBody", "response_body", "body") if key in data), - None, + (data[key] for key in ("responseBody", "response_body", "body") if key in data), + _BODY_ABSENT, ) - if body_value is None: + if body_value is _BODY_ABSENT: payload: Any = data malformed = False elif isinstance(body_value, dict): From 8f1d645cb785b7022472ec635c4a8c23e85778db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 02:04:08 +0900 Subject: [PATCH 59/66] test(opencode): cover present malformed body aliases --- tests/test_opencode_failure_envelope.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 6483a03f33..90bbc0bb33 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -119,6 +119,9 @@ def test_last_error_event_uses_last_valid_error_and_rejects_bad_utf8() -> None: ({"responseBody": "not-json"}, {}, True), ({"responseBody": "\ud800"}, {}, True), ({"responseBody": "x" * (envelope.MAX_GATEWAY_BODY_BYTES + 1)}, {}, True), + ({"responseBody": None}, {}, True), + ({"responseBody": True}, {}, True), + ({"responseBody": 503}, {}, True), ({"body": []}, {}, True), ({"body": {}}, {}, False), ], @@ -152,7 +155,6 @@ def test_gateway_detail_rejects_oversized_mapping_body() -> None: assert envelope._gateway_detail({"responseBody": body}) == ({}, True) - def test_gateway_detail_rejects_unencodable_mapping_body() -> None: """Mapping bodies that cannot produce bounded UTF-8 JSON fail closed.""" body = {"detail": {"value": "\ud800"}} @@ -167,6 +169,11 @@ def test_gateway_details_rejects_any_malformed_alias() -> None: ) == ((), True) +@pytest.mark.parametrize( + "body_value", + ["not-json", None, True, 503, []], + ids=["invalid-json", "null", "boolean", "integer", "array"], +) @pytest.mark.parametrize( "outer_authority", [ @@ -176,7 +183,9 @@ def test_gateway_details_rejects_any_malformed_alias() -> None: ids=["reason", "status"], ) def test_malformed_gateway_body_suppresses_outer_causal_authority( - tmp_path: Path, outer_authority: dict[str, object] + tmp_path: Path, + body_value: object, + outer_authority: dict[str, object], ) -> None: """A malformed canonical body cannot publish an outer reason or status.""" json_path = tmp_path / "event.jsonl" @@ -187,7 +196,7 @@ def test_malformed_gateway_body_suppresses_outer_causal_authority( "type": "error", "error": { "data": { - "responseBody": "not-json", + "responseBody": body_value, **outer_authority, } }, @@ -522,6 +531,7 @@ def test_main_prints_metadata_and_rejects_invalid_arguments( ) assert envelope.main() == 0 + def test_format_failure_metadata_rejects_conflicting_status_authorities( tmp_path: Path, ) -> None: From 6faee546d3f7e8077e89224ee73c636b0e611e9e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 02:04:15 +0900 Subject: [PATCH 60/66] test(opencode): keep regression in canonical suite --- ..._opencode_failure_envelope_present_body.py | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 tests/test_opencode_failure_envelope_present_body.py diff --git a/tests/test_opencode_failure_envelope_present_body.py b/tests/test_opencode_failure_envelope_present_body.py deleted file mode 100644 index a4349c307e..0000000000 --- a/tests/test_opencode_failure_envelope_present_body.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Regression coverage for present malformed OpenCode gateway body aliases.""" - -from __future__ import annotations - -import json -from pathlib import Path - -import pytest - -from scripts.ci import opencode_failure_envelope as envelope - - -@pytest.mark.parametrize( - "body_value", - [None, True, 503, []], - ids=["null", "boolean", "integer", "array"], -) -@pytest.mark.parametrize( - "outer_authority", - [ - {"code": "provider_unavailable"}, - {"statusCode": 503}, - ], - ids=["reason", "status"], -) -def test_present_unsupported_gateway_body_suppresses_outer_causal_authority( - tmp_path: Path, - body_value: object, - outer_authority: dict[str, object], -) -> None: - """A present unsupported body alias cannot preserve outer causal authority.""" - json_path = tmp_path / "event.jsonl" - stderr_path = tmp_path / "stderr" - json_path.write_text( - json.dumps( - { - "type": "error", - "error": { - "data": { - "responseBody": body_value, - **outer_authority, - } - }, - } - ) - + "\n", - encoding="utf-8", - ) - stderr_path.write_text("", encoding="utf-8") - - rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) - - assert "class=malformed-response" in rendered - assert "reason=malformed_response" in rendered - assert "http-status=unknown" in rendered - assert "class=provider-5xx" not in rendered From 646315abbb4afb42a1a0f4100d47637fa1523aaf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 02:16:21 +0900 Subject: [PATCH 61/66] fix(security): anchor OpenCode synthetic token allowlist --- .gitleaks.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitleaks.toml b/.gitleaks.toml index 27839222cf..9c5c4913f1 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -25,4 +25,4 @@ paths = [ '''(^|/)tests/test_opencode_failure_envelope\.py$''', '''(^|/)tests/test_opencode_model_pool_runner\.py$''', ] -regexes = ['''BYTEZ_TEST_SECRET_[1]234567890'''] +regexes = ['''^BYTEZ_TEST_SECRET_[1]234567890$'''] From bb183e4d73191c019d3470a9900f6d838078430d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 02:19:21 +0900 Subject: [PATCH 62/66] fix(security): scope synthetic allowlist to exact secret --- .gitleaks.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitleaks.toml b/.gitleaks.toml index 9c5c4913f1..b8177b5e3b 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -20,7 +20,7 @@ regexes = [ [[allowlists]] description = "OpenCode synthetic unknown-provenance token used only by confidentiality regressions." condition = "AND" -regexTarget = "match" +regexTarget = "secret" paths = [ '''(^|/)tests/test_opencode_failure_envelope\.py$''', '''(^|/)tests/test_opencode_model_pool_runner\.py$''', From af6de738fae33055039acabc400b0dde9f2561b0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 05:00:13 +0900 Subject: [PATCH 63/66] test(opencode): reproduce reviewed failure-envelope authority gaps --- ...ode_failure_envelope_review_regressions.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/test_opencode_failure_envelope_review_regressions.py diff --git a/tests/test_opencode_failure_envelope_review_regressions.py b/tests/test_opencode_failure_envelope_review_regressions.py new file mode 100644 index 0000000000..f0d78ec930 --- /dev/null +++ b/tests/test_opencode_failure_envelope_review_regressions.py @@ -0,0 +1,116 @@ +"""Regression coverage for reviewed OpenCode failure-envelope authority boundaries.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from scripts.ci import opencode_failure_envelope as envelope + + +def _render(tmp_path: Path, error: object) -> str: + """Render one structured error event through the public diagnostic seam.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps({"type": "error", "error": error}) + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + return envelope.format_failure_metadata(json_path, stderr_path, 1) + + +@pytest.mark.parametrize( + "error", + [ + None, + "not-an-object", + [], + {"data": None}, + {"data": "not-an-object"}, + {"data": []}, + ], + ids=[ + "null-error", + "string-error", + "list-error", + "null-data", + "string-data", + "list-data", + ], +) +def test_present_malformed_error_containers_fail_closed( + tmp_path: Path, error: object +) -> None: + """Present non-object error containers remain malformed-response authority.""" + rendered = _render(tmp_path, error) + + assert "class=malformed-response" in rendered + assert "reason=malformed_response" in rendered + assert "http-status=unknown" in rendered + + +def test_gateway_status_and_provider_status_keep_separate_layers(tmp_path: Path) -> None: + """A gateway 502 may wrap an upstream 503 without erasing the structured cause.""" + rendered = _render( + tmp_path, + { + "data": { + "statusCode": 502, + "detail": { + "terminal_reason": "eligible_candidates_exhausted", + "attempts": [{"provider_status": 503}], + }, + } + }, + ) + + assert "class=model-pool-exhausted" in rendered + assert "reason=eligible_candidates_exhausted" in rendered + assert "http-status=502" in rendered + + +@pytest.mark.parametrize( + ("status", "reason", "expected_class"), + [ + (503, "eligible_candidates_exhausted", "model-pool-exhausted"), + (504, "provider_timeout", "timeout"), + (503, "model_not_found", "model-unavailable"), + ], +) +def test_specific_reason_refines_compatible_generic_5xx( + tmp_path: Path, + status: int, + reason: str, + expected_class: str, +) -> None: + """Allowlisted terminal reasons refine only compatible generic 5xx statuses.""" + rendered = _render( + tmp_path, + {"data": {"statusCode": status, "detail": {"terminal_reason": reason}}}, + ) + + assert f"class={expected_class}" in rendered + assert f"reason={reason}" in rendered + assert f"http-status={status}" in rendered + + +def test_specific_reason_does_not_refine_incompatible_generic_5xx( + tmp_path: Path, +) -> None: + """Credit/auth/rate-like contradictions remain fail-closed under generic 5xx.""" + rendered = _render( + tmp_path, + { + "data": { + "statusCode": 502, + "detail": {"terminal_reason": "payment_required"}, + } + }, + ) + + assert "class=provider-error" in rendered + assert "reason=unknown" in rendered + assert "http-status=unknown" in rendered From 5fb9c987c32620da63546fe69335b58f3a230cad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 05:01:10 +0900 Subject: [PATCH 64/66] fix(opencode): preserve protocol layers in failure authority --- scripts/ci/opencode_failure_envelope.py | 68 ++++++++++++++++++++----- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/scripts/ci/opencode_failure_envelope.py b/scripts/ci/opencode_failure_envelope.py index ae2910e06e..f8f4237a0a 100755 --- a/scripts/ci/opencode_failure_envelope.py +++ b/scripts/ci/opencode_failure_envelope.py @@ -54,6 +54,9 @@ "provider_unavailable": "provider-5xx", "upstream_error": "provider-5xx", } +_GENERIC_5XX_REFINEMENTS = frozenset( + {"provider-5xx", "model-pool-exhausted", "model-unavailable", "timeout"} +) def _read_bounded(path: Path) -> tuple[bytes, int]: @@ -114,6 +117,18 @@ def _status_failure_class(status: int | None) -> str | None: return "provider-error" if status is not None else None +def _failure_classes_compatible( + status_class: str | None, reason_class: str | None +) -> bool: + """Return whether a structured reason may refine one HTTP status class.""" + if status_class is None or reason_class is None or status_class == reason_class: + return True + return ( + status_class == "provider-5xx" + and reason_class in _GENERIC_5XX_REFINEMENTS + ) + + def _is_within_json_depth(value: Any) -> bool: """Return whether a decoded provider value stays within the depth invariant.""" pending = [(value, 1)] @@ -231,7 +246,7 @@ def _failure_class( return "provider-error" status_class = _status_failure_class(status) reason_class = REASON_FAILURE_CLASSES.get(reason or "") - if status_class is not None and reason_class is not None and status_class != reason_class: + if not _failure_classes_compatible(status_class, reason_class): return "provider-error" if reason_class is not None: return reason_class @@ -251,13 +266,33 @@ def format_failure_metadata( raw_json, json_bytes = _read_bounded(json_path) raw_stderr, stderr_bytes = _read_bounded(stderr_path) event = _last_error_event(raw_json) - error = event.get("error") if isinstance(event, dict) else None - error = error if isinstance(error, dict) else {} - data = error.get("data") - data = data if isinstance(data, dict) else {} + + malformed_container = False + if isinstance(event, dict) and "error" in event: + event_error = event["error"] + if isinstance(event_error, dict): + error = event_error + else: + error = {} + malformed_container = True + else: + error = {} + + if "data" in error: + error_data = error["data"] + if isinstance(error_data, dict): + data = error_data + else: + data = {} + malformed_container = True + else: + data = {} + details, malformed_body = _gateway_details(data) + malformed_body = malformed_container or malformed_body if malformed_body: data = {} + last_attempts = [] phases = [] for detail in details: @@ -275,6 +310,7 @@ def format_failure_metadata( _safe_enum(last_attempt.get("phase"), SAFE_FAILURE_PHASES) or _safe_enum(detail.get("phase"), SAFE_FAILURE_PHASES) ) + reason, reason_conflict = _consistent_authority( tuple( _safe_enum(detail.get(key), REASON_FAILURE_CLASSES) @@ -283,23 +319,31 @@ def format_failure_metadata( ) + (_safe_enum(data.get("code"), REASON_FAILURE_CLASSES),) ) - status, status_conflict = _consistent_authority( + gateway_status, gateway_status_conflict = _consistent_authority( ( _safe_http_status(data.get("statusCode")), _safe_http_status(data.get("status_code")), ) - + tuple( + ) + provider_status, provider_status_conflict = _consistent_authority( + tuple( _safe_http_status(attempt.get("provider_status")) for attempt in last_attempts ) ) + status = gateway_status if gateway_status is not None else provider_status + status_conflict = gateway_status_conflict or provider_status_conflict phase, phase_conflict = _consistent_authority(tuple(phases)) - status_class = _status_failure_class(status) + reason_class = REASON_FAILURE_CLASSES.get(reason or "") - cross_conflict = ( - status_class is not None - and reason_class is not None - and status_class != reason_class + status_classes = tuple( + _status_failure_class(candidate) + for candidate in (gateway_status, provider_status) + if candidate is not None + ) + cross_conflict = reason_class is not None and any( + not _failure_classes_compatible(status_class, reason_class) + for status_class in status_classes ) authority_conflict = reason_conflict or status_conflict or cross_conflict failure_class = _failure_class( From f5066947e01c5698f08f0804c7225ad95d2c9008 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 05:08:17 +0900 Subject: [PATCH 65/66] test(opencode): bind reviewed authority repairs to owner suite --- tests/test_opencode_failure_envelope.py | 118 +++++++++++++++++++++++- 1 file changed, 113 insertions(+), 5 deletions(-) diff --git a/tests/test_opencode_failure_envelope.py b/tests/test_opencode_failure_envelope.py index 90bbc0bb33..8e9b45169e 100644 --- a/tests/test_opencode_failure_envelope.py +++ b/tests/test_opencode_failure_envelope.py @@ -535,7 +535,7 @@ def test_main_prints_metadata_and_rejects_invalid_arguments( def test_format_failure_metadata_rejects_conflicting_status_authorities( tmp_path: Path, ) -> None: - """Conflicting validated HTTP statuses cannot select a public cause.""" + """Conflicting validated gateway statuses cannot select a public cause.""" json_path = tmp_path / "event.jsonl" stderr_path = tmp_path / "stderr" json_path.write_text( @@ -545,9 +545,7 @@ def test_format_failure_metadata_rejects_conflicting_status_authorities( "error": { "data": { "statusCode": 429, - "detail": { - "attempts": [{"provider_status": 502}], - }, + "status_code": 502, } }, } @@ -603,7 +601,7 @@ def test_format_failure_metadata_rejects_conflicting_reason_authorities( def test_format_failure_metadata_rejects_cross_family_authority_conflict( tmp_path: Path, ) -> None: - """A validated status and reason must resolve to the same causal class.""" + """A validated status and reason must resolve to compatible causal classes.""" json_path = tmp_path / "event.jsonl" stderr_path = tmp_path / "stderr" json_path.write_text( @@ -646,3 +644,113 @@ def test_gateway_details_rejects_malformed_alias() -> None: } assert envelope._gateway_details(data) == ((), True) + + +@pytest.mark.parametrize( + "error", + [ + None, + "not-an-object", + [], + {"data": None}, + {"data": "not-an-object"}, + {"data": []}, + ], + ids=[ + "null-error", + "string-error", + "list-error", + "null-data", + "string-data", + "list-data", + ], +) +def test_present_malformed_error_containers_fail_closed( + tmp_path: Path, error: object +) -> None: + """Present non-object error containers remain malformed-response authority.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps({"type": "error", "error": error}) + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=malformed-response" in rendered + assert "reason=malformed_response" in rendered + assert "http-status=unknown" in rendered + + +def test_gateway_status_and_provider_status_keep_separate_layers(tmp_path: Path) -> None: + """A gateway 502 may wrap an upstream 503 without erasing the structured cause.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "statusCode": 502, + "detail": { + "terminal_reason": "eligible_candidates_exhausted", + "attempts": [{"provider_status": 503}], + }, + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert "class=model-pool-exhausted" in rendered + assert "reason=eligible_candidates_exhausted" in rendered + assert "http-status=502" in rendered + + +@pytest.mark.parametrize( + ("status", "reason", "expected_class"), + [ + (503, "eligible_candidates_exhausted", "model-pool-exhausted"), + (504, "provider_timeout", "timeout"), + (503, "model_not_found", "model-unavailable"), + ], +) +def test_specific_reason_refines_compatible_generic_5xx( + tmp_path: Path, + status: int, + reason: str, + expected_class: str, +) -> None: + """Allowlisted terminal reasons refine only compatible generic 5xx statuses.""" + json_path = tmp_path / "event.jsonl" + stderr_path = tmp_path / "stderr" + json_path.write_text( + json.dumps( + { + "type": "error", + "error": { + "data": { + "statusCode": status, + "detail": {"terminal_reason": reason}, + } + }, + } + ) + + "\n", + encoding="utf-8", + ) + stderr_path.write_text("", encoding="utf-8") + + rendered = envelope.format_failure_metadata(json_path, stderr_path, 1) + + assert f"class={expected_class}" in rendered + assert f"reason={reason}" in rendered + assert f"http-status={status}" in rendered From e7c58c04ed7e59c23cbe4a5f38d4c522ae712712 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 13 Sep 2026 05:08:28 +0900 Subject: [PATCH 66/66] test(opencode): retire duplicate review regression file --- ...ode_failure_envelope_review_regressions.py | 116 ------------------ 1 file changed, 116 deletions(-) delete mode 100644 tests/test_opencode_failure_envelope_review_regressions.py diff --git a/tests/test_opencode_failure_envelope_review_regressions.py b/tests/test_opencode_failure_envelope_review_regressions.py deleted file mode 100644 index f0d78ec930..0000000000 --- a/tests/test_opencode_failure_envelope_review_regressions.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Regression coverage for reviewed OpenCode failure-envelope authority boundaries.""" - -from __future__ import annotations - -import json -from pathlib import Path - -import pytest - -from scripts.ci import opencode_failure_envelope as envelope - - -def _render(tmp_path: Path, error: object) -> str: - """Render one structured error event through the public diagnostic seam.""" - json_path = tmp_path / "event.jsonl" - stderr_path = tmp_path / "stderr" - json_path.write_text( - json.dumps({"type": "error", "error": error}) + "\n", - encoding="utf-8", - ) - stderr_path.write_text("", encoding="utf-8") - return envelope.format_failure_metadata(json_path, stderr_path, 1) - - -@pytest.mark.parametrize( - "error", - [ - None, - "not-an-object", - [], - {"data": None}, - {"data": "not-an-object"}, - {"data": []}, - ], - ids=[ - "null-error", - "string-error", - "list-error", - "null-data", - "string-data", - "list-data", - ], -) -def test_present_malformed_error_containers_fail_closed( - tmp_path: Path, error: object -) -> None: - """Present non-object error containers remain malformed-response authority.""" - rendered = _render(tmp_path, error) - - assert "class=malformed-response" in rendered - assert "reason=malformed_response" in rendered - assert "http-status=unknown" in rendered - - -def test_gateway_status_and_provider_status_keep_separate_layers(tmp_path: Path) -> None: - """A gateway 502 may wrap an upstream 503 without erasing the structured cause.""" - rendered = _render( - tmp_path, - { - "data": { - "statusCode": 502, - "detail": { - "terminal_reason": "eligible_candidates_exhausted", - "attempts": [{"provider_status": 503}], - }, - } - }, - ) - - assert "class=model-pool-exhausted" in rendered - assert "reason=eligible_candidates_exhausted" in rendered - assert "http-status=502" in rendered - - -@pytest.mark.parametrize( - ("status", "reason", "expected_class"), - [ - (503, "eligible_candidates_exhausted", "model-pool-exhausted"), - (504, "provider_timeout", "timeout"), - (503, "model_not_found", "model-unavailable"), - ], -) -def test_specific_reason_refines_compatible_generic_5xx( - tmp_path: Path, - status: int, - reason: str, - expected_class: str, -) -> None: - """Allowlisted terminal reasons refine only compatible generic 5xx statuses.""" - rendered = _render( - tmp_path, - {"data": {"statusCode": status, "detail": {"terminal_reason": reason}}}, - ) - - assert f"class={expected_class}" in rendered - assert f"reason={reason}" in rendered - assert f"http-status={status}" in rendered - - -def test_specific_reason_does_not_refine_incompatible_generic_5xx( - tmp_path: Path, -) -> None: - """Credit/auth/rate-like contradictions remain fail-closed under generic 5xx.""" - rendered = _render( - tmp_path, - { - "data": { - "statusCode": 502, - "detail": {"terminal_reason": "payment_required"}, - } - }, - ) - - assert "class=provider-error" in rendered - assert "reason=unknown" in rendered - assert "http-status=unknown" in rendered