Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ wrong_citation 且 forbid_citations=true 的源码拒绝还会检查答案正文

本次有界真实验收使用 `services/agent/e2e_guarded_boundary_evaluation.py`,CLI 身份参数与既有入口相同。它要求同一周期 active 和 clean checkout,并将共享增量 guard 注入 loop/judge 两个适配器,付费负探针复用 judge。增量日志固定在该周期 accounting 目录的 `dav58-increment-<identity>.json`;已存在即拒绝重放,不重置。入口将 guard 与自身的源码 hash 加入 artifact provenance。

增量 guard 按已核验的 DeepSeek Flash 高峰费率,在每次 HTTP 前持久化完整模型上限包络(保守取 1,048,576 输入和 393,216 输出 tokens),不依赖本地 framing 估计。只有完整、相互一致的 usage 才将独立包络结算为高峰费用;原 ModelBudget 预留从不退款。未知 usage、异常模型/思考输出、网络或落盘失败停止全部后续调用。已核验费用加完整包络须不超过本次 USD1;可能提前停止,不能保证完整矩阵必能完成。transport 无重试,周期与 purpose 门禁同时生效。
增量 guard 按已核验的 DeepSeek Flash 高峰费率,在每次 HTTP 前持久化完整模型上限包络(保守取 1,048,576 输入和 393,216 输出 tokens),不依赖本地 framing 估计。只有完整、相互一致的 usage 才将独立包络结算为高峰费用;原 ModelBudget 预留从不退款。未知 usage、异常模型/思考输出、网络或落盘失败停止全部后续调用。网络或读取失败回执记录 `network_error_class`:HTTPX 原生连接/读取错误与超时仅记录类型名,其他异常归为 `transport_failure`,不记录异常消息、URL 或请求内容。已核验费用加完整包络须不超过本次 USD1;可能提前停止,不能保证完整矩阵必能完成。transport 无重试,周期与 purpose 门禁同时生效。



Expand Down Expand Up @@ -956,6 +956,9 @@ This mode is recovery preparation, not formal model/Java acceptance.
For offline preparation, `validate_recovery_sources` checks bounded source
snapshots against an explicit attempt-to-request-body-hash crosswalk; SQL attempt
IDs do not supply that crosswalk. Missing original mappings must not be synthesized.
Optional guard receipt `network_error_class` accepts only `ConnectError`, `ReadError`,
`ConnectTimeout`, `ReadTimeout` or `transport_failure`; legacy receipts may omit it.
Other values and unknown fields are rejected; this diagnostic never settles unknown usage.
`compile_recovery_plan` derives conditional costs from explicit approval, pricing
and lane caps using `A + U + R - cmin + E` for a single in-flight request. Its result
has `paid_authorized=False` and `runtime_applied=False`: caller-declared caps are
Expand Down
4 changes: 2 additions & 2 deletions services/agent/data/repository_corpus_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
},
{
"doc_id": "repository-development",
"version": "sha256-fc51f51a59ce466ed4c08135164f0f8bb914e13bad0c20e333eb85e402b92908",
"chunk_id": "repository-development:sha256-fc51f51a59ce466ed4c08135164f0f8bb914e13bad0c20e333eb85e402b92908:1",
"version": "sha256-17b79d66eafe8b4e8b3a3d9cc7a4e675ee39f7dbe4bb375f010d31bad11e6d5e",
"chunk_id": "repository-development:sha256-17b79d66eafe8b4e8b3a3d9cc7a4e675ee39f7dbe4bb375f010d31bad11e6d5e:1",
"source_path": "docs/DEVELOPMENT.md",
"access_scope": "repository-public",
"sample_kind": "real",
Expand Down
7 changes: 6 additions & 1 deletion services/agent/src/budget_binding_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ def rehearse_audit_recovery(source, destination):
_GUARD_STATE_KEYS = ("config_sha256", "continuation_run", "halted", "limit_micro_usd",
"pending_micro_usd", "period_identity", "policy", "receipts",
"settled_peak_micro_usd")
_GUARD_RECEIPT_KEYS = ("completion_token_cap", "completion_tokens", "finished_at_utc", "lane",
_GUARD_RECEIPT_KEYS = ("completion_token_cap", "completion_tokens", "finished_at_utc", "lane", "network_error_class",
"peak_micro_usd", "prompt_token_cap", "prompt_tokens", "reason",
"request_bytes", "request_model", "request_sha256", "reserved_micro_usd",
"response_model", "started_at_utc", "status", "total_tokens")
Expand Down Expand Up @@ -625,6 +625,11 @@ def validate_recovery_sources(sources, guard, unknown_match):
if (not isinstance(receipt, dict) or not set(receipt) <= set(_GUARD_RECEIPT_KEYS)
or not isinstance(receipt.get("lane"), str)
or not 1 <= len(receipt["lane"]) <= _MAX_LANE_TEXT
or ("network_error_class" in receipt and (
type(receipt["network_error_class"]) is not str
or receipt["network_error_class"] not in (
"ConnectError", "ReadError", "ConnectTimeout", "ReadTimeout", "transport_failure"
)))
or (peak_micro_usd is not None and not _bounded_count(peak_micro_usd))
or any(not _bounded_scalar(value) for value in receipt.values())):
raise ValueError("guard receipt declares missing or unbounded fields")
Expand Down
7 changes: 6 additions & 1 deletion services/agent/src/dav58_live_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,12 @@ async def handle_async_request(self, request):
try:
response = await self.inner.handle_async_request(request)
await response.aread()
except BaseException:
except BaseException as error:
receipt["network_error_class"] = (
type(error).__name__ if type(error) in (
httpx.ConnectError, httpx.ReadError, httpx.ConnectTimeout, httpx.ReadTimeout
) else "transport_failure"
)
Comment thread
DavidHLP marked this conversation as resolved.
self.guard.halt(receipt, "network_or_read_failure")
raise ModelBudgetExceeded("incremental guard stopped after network failure") from None
self.guard.finish(receipt, response)
Expand Down
27 changes: 27 additions & 0 deletions services/agent/tests/test_budget_binding_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,33 @@ def test_recovery_source_snapshot_preserves_exact_47_rows_and_unknown_guard_prov
evidence.source_provenance_by_ledger[ids[0]]["recorded_ledger"][0] = 59


@pytest.mark.parametrize("diagnostic", [
"ConnectError", "ReadError", "ConnectTimeout", "ReadTimeout", "transport_failure",
])
def test_recovery_source_accepts_safe_network_diagnostic_without_releasing_unknown(diagnostic):
sources, guard, unknown_match = _synthetic_recovery_inputs()
guard["state"]["receipts"][-1]["network_error_class"] = diagnostic
guard["source_sha256"] = _recovery_digest(guard["state"])

evidence = migration.validate_recovery_sources(sources, guard, unknown_match)

assert evidence.unknown_attempts == 1
assert evidence.pending_micro_usd == 786432
assert evidence.guard_source_sha256 == guard["source_sha256"]


@pytest.mark.parametrize("diagnostic", [
"", "RuntimeError", "ReadError: secret", None, True, 1, [], {},
])
def test_recovery_source_rejects_invalid_network_diagnostic(diagnostic):
sources, guard, unknown_match = _synthetic_recovery_inputs()
guard["state"]["receipts"][-1]["network_error_class"] = diagnostic
guard["source_sha256"] = _recovery_digest(guard["state"])

with pytest.raises(ValueError, match="guard receipt"):
migration.validate_recovery_sources(sources, guard, unknown_match)


def test_recovery_source_evidence_holds_no_mutable_alias_of_its_inputs():
ids = ("a" * 32, "b" * 32)
sources, guard, unknown_match = _synthetic_recovery_inputs()
Expand Down
32 changes: 32 additions & 0 deletions services/agent/tests/test_dav58_live_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,38 @@ async def run():
assert guard.state["pending_micro_usd"] == 786432


@pytest.mark.parametrize("error,expected", [
(httpx.ConnectError("private-token private-request"), "ConnectError"),
(httpx.ReadTimeout("private-token private-request"), "ReadTimeout"),
(RuntimeError("private-token private-request"), "transport_failure"),
(type("private_token", (httpx.ConnectError,), {})("private-token private-request"), "transport_failure"),
])
def test_network_failure_records_safe_type_without_message_or_retry(tmp_path, error, expected):
requests = []
def handler(request):
requests.append(request)
raise error
guard, loop, judge = models(tmp_path, handler)
try:
async def run():
for model in (loop, judge):
with pytest.raises(ModelBudgetExceeded) as failure:
await model.decide([{"role": "user", "content": "case"}])
assert "private-token" not in str(failure.value)
asyncio.run(run())
saved = json.loads(guard.path.read_text())
assert len(requests) == len(saved["receipts"]) == 1
assert saved["receipts"][0]["network_error_class"] == expected
assert saved["receipts"][0]["reason"] == "network_or_read_failure"
assert saved["receipts"][0]["status"] == "unknown_or_unsafe"
assert saved["halted"] is True
assert saved["pending_micro_usd"] == ENVELOPE_MICRO_USD
assert "private-token" not in guard.path.read_text()
assert "private-request" not in guard.path.read_text()
finally:
guard.close()


def test_total_increment_ceiling_checks_before_transport(tmp_path):
requests = []
guard, loop, judge = models(tmp_path, lambda request: requests.append(request) or httpx.Response(200, json=receipt()))
Expand Down
Loading