From 0e75c04279386dfdbc59b00241c22c0cbc00f424 Mon Sep 17 00:00:00 2001 From: Y1fe1Zh0u Date: Mon, 24 Aug 2026 19:40:52 +0800 Subject: [PATCH 1/2] Stop immutable reference failures from burning model turns Unreadable ledger-bound artifact references cannot be repaired by regenerating the final answer, so fail them immediately. Suppress false sandbox block warnings for unchanged materialized Skill scripts while preserving the existing Vercel READY receipt exception from the v1.11.4 baseline. Constraint: Preserve fail-closed handling for genuinely changed banned executable files. Rejected: Treat every unreadable provider URL as successful | only an exact Vercel READY receipt is authoritative enough for the existing warning path. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Do not route immutable Tool Ledger integrity failures back through model repair. Tested: 86 scoped backend tests; scoped Ruff; Architecture Guard P0; git diff --check. Not-tested: Live Vercel deployment and production sandbox execution. --- .../services/agent_runtime/verification.py | 2 +- .../sandbox/local/subprocess_backend.py | 10 ++++---- .../tests/test_agent_runtime_node_executor.py | 23 +++++++++++++++++ .../test_agent_runtime_reference_reader.py | 4 +-- .../tests/test_sandbox_subprocess_backend.py | 25 +++++++++++++++++++ 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/backend/app/services/agent_runtime/verification.py b/backend/app/services/agent_runtime/verification.py index 65bbc5c6b..b5f7e087f 100644 --- a/backend/app/services/agent_runtime/verification.py +++ b/backend/app/services/agent_runtime/verification.py @@ -1010,7 +1010,7 @@ async def verify( ) continue return VerificationResult( - outcome="repair", + outcome="fail", reason="an artifact/evidence reference is not readable", details={ "code": "tool_reference_unreadable", diff --git a/backend/app/services/sandbox/local/subprocess_backend.py b/backend/app/services/sandbox/local/subprocess_backend.py index 16c351d3d..2d0557b58 100644 --- a/backend/app/services/sandbox/local/subprocess_backend.py +++ b/backend/app/services/sandbox/local/subprocess_backend.py @@ -636,17 +636,17 @@ def is_allowed(relative_path: Path) -> bool: continue except OSError: continue - if file_path.suffix.lower() in banned_suffixes: - logger.warning( - f"[Sandbox Gateway] Blocked banned file extension: {rel_path}" - ) - continue if target_file is not None: try: if file_path.read_bytes() == target_file.read_bytes(): continue except OSError: pass + if file_path.suffix.lower() in banned_suffixes: + logger.warning( + f"[Sandbox Gateway] Blocked banned file extension: {rel_path}" + ) + continue publication_candidates[rel_path] = file_path deletion_candidates = { diff --git a/backend/tests/test_agent_runtime_node_executor.py b/backend/tests/test_agent_runtime_node_executor.py index e95106b79..188caa740 100644 --- a/backend/tests/test_agent_runtime_node_executor.py +++ b/backend/tests/test_agent_runtime_node_executor.py @@ -1691,6 +1691,29 @@ async def test_verification_repairs_are_bounded() -> None: assert verifier.calls == ["first", "second"] +@pytest.mark.asyncio +async def test_verification_integrity_failure_does_not_reenter_model() -> None: + run_id = uuid.uuid4() + model = ModelService(ModelStepResult(intent="finish", finish_content="done")) + verifier = Verifier( + VerificationResult( + outcome="fail", + reason="an artifact/evidence reference is not readable", + details={"code": "tool_reference_unreadable"}, + ) + ) + executor = _executor(model, verifier=verifier, max_verification_repairs=10) + + result = await _invoke(run_id, executor) + + lifecycle = result["lifecycle"] + assert lifecycle["status"] == "failed" + assert lifecycle["reason"] == "an artifact/evidence reference is not readable" + assert lifecycle.get("verification_attempt_count", 0) == 0 + assert model.calls == 1 + assert verifier.calls == ["done"] + + @pytest.mark.asyncio async def test_task_completion_gate_exhaustion_delivers_latest_candidate() -> None: run_id = uuid.uuid4() diff --git a/backend/tests/test_agent_runtime_reference_reader.py b/backend/tests/test_agent_runtime_reference_reader.py index 97986b938..7baf58d20 100644 --- a/backend/tests/test_agent_runtime_reference_reader.py +++ b/backend/tests/test_agent_runtime_reference_reader.py @@ -516,7 +516,7 @@ async def test_production_verifier_and_finalizer_propagate_only_read_back_refs() @pytest.mark.asyncio -async def test_production_verifier_repairs_an_unreadable_current_run_reference() -> None: +async def test_production_verifier_fails_fast_on_an_unreadable_current_run_reference() -> None: tenant_id = uuid.uuid4() run_id = uuid.uuid4() agent_id = uuid.uuid4() @@ -553,7 +553,7 @@ async def test_production_verifier_repairs_an_unreadable_current_run_reference() context, "done", ) - assert result.outcome == "repair" + assert result.outcome == "fail" assert result.details == { "code": "tool_reference_unreadable", "reference": reference, diff --git a/backend/tests/test_sandbox_subprocess_backend.py b/backend/tests/test_sandbox_subprocess_backend.py index eece192e3..c0dbf0620 100644 --- a/backend/tests/test_sandbox_subprocess_backend.py +++ b/backend/tests/test_sandbox_subprocess_backend.py @@ -326,6 +326,31 @@ async def test_sandbox_output_sanitization(tmp_path: Path) -> None: assert not (target / "evil.sh").exists() +@pytest.mark.asyncio +async def test_sandbox_does_not_report_unchanged_skill_scripts_as_blocked( + tmp_path: Path, + monkeypatch, +) -> None: + staging = tmp_path / "staging" + target = tmp_path / "target" + script_path = Path("skills/skill-creator/scripts/package_skill.py") + for root in (staging, target): + path = root / script_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("print('package')", encoding="utf-8") + + warnings: list[str] = [] + monkeypatch.setattr(subprocess_backend.logger, "warning", warnings.append) + + await SubprocessBackend(SandboxConfig())._verify_and_merge_outputs( + staging, + target, + ) + + assert warnings == [] + assert (target / script_path).read_text(encoding="utf-8") == "print('package')" + + @pytest.mark.asyncio async def test_sandbox_quota_ignores_unchanged_materialized_files( tmp_path: Path, From 65bbddbd9f1d7c279d6e5591a07899f472943011 Mon Sep 17 00:00:00 2001 From: Y1fe1Zh0u Date: Mon, 24 Aug 2026 19:45:25 +0800 Subject: [PATCH 2/2] Identify the first v1.11.4 runtime hotfix Stamp backend and frontend runtime metadata as 1.11.4-fix.1 so the mainline release and deployed health/version surfaces identify the exact hotfix. Constraint: Release remains based on the official v1.11.4 tag at ae16d3ea. Confidence: high Scope-risk: narrow Reversibility: clean Tested: 86 backend tests; 122 frontend tests; frontend production build; scoped Ruff; Architecture Guard P0; git diff --check. Not-tested: Tag-triggered Drone deployment before publication. --- backend/VERSION | 2 +- frontend/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/VERSION b/backend/VERSION index 3d0e62313..703a816ae 100644 --- a/backend/VERSION +++ b/backend/VERSION @@ -1 +1 @@ -1.11.4 +1.11.4-fix.1 diff --git a/frontend/VERSION b/frontend/VERSION index 3d0e62313..703a816ae 100644 --- a/frontend/VERSION +++ b/frontend/VERSION @@ -1 +1 @@ -1.11.4 +1.11.4-fix.1