fix(agent99): bound DB sessions and expire stale outcomes
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 4m0s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 1s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 17s

This commit is contained in:
ogt
2026-07-15 08:47:05 +08:00
parent 47586431c0
commit 0f2ddc0ce8
10 changed files with 569 additions and 37 deletions

View File

@@ -1752,6 +1752,200 @@ class PostgresAgent99DispatchLedger:
"runtime_closure_verified": False,
}
async def record_outcome_timeout_no_write_terminal(
self,
*,
identity: Agent99DispatchIdentity,
) -> dict[str, Any]:
"""Fail an expired run without replaying transport or claiming closure."""
now = _utc_now_naive()
error_code = "E-AGENT99-OUTCOME-TIMEOUT"
try:
async with get_db_context(identity.project_id) as db:
current_result = await db.execute(
select(
AwoooPRunState.state,
AwoooPRunState.error_detail,
AwoooPRunState.timeout_at,
)
.where(
AwoooPRunState.run_id == identity.run_id,
AwoooPRunState.project_id == identity.project_id,
)
.with_for_update()
)
current = current_result.one_or_none()
envelope = _parse_envelope(
current.error_detail if current is not None else None
)
identity_matches = bool(
isinstance(envelope, dict)
and _stage_identity_matches(
identity,
envelope.get("identity")
if isinstance(envelope.get("identity"), dict)
else {},
)
)
if (
current is not None
and current.state == "failed"
and identity_matches
and envelope.get("outcome_timeout_no_write_terminal") is True
):
return {
**envelope,
"status": "outcome_timeout_no_write_terminal_idempotent",
"receipt_persisted": True,
"runtime_closure_verified": False,
}
if current is None or not identity_matches:
return {
"status": "outcome_timeout_run_identity_invalid_fail_closed",
"receipt_persisted": False,
"runtime_closure_verified": False,
}
timeout_at = current.timeout_at
if (
current.state != "waiting_tool"
or timeout_at is None
or timeout_at > now
):
return {
"status": "outcome_timeout_not_due_no_write",
"receipt_persisted": False,
"runtime_closure_verified": False,
}
prior_controlled_apply_authorized = bool(
envelope.get("controlled_apply_authorized") is True
)
prior_runtime_execution_authorized = bool(
envelope.get("runtime_execution_authorized") is True
)
prior_runtime_execution_attempted = bool(
envelope.get("runtime_execution_attempted") is True
)
terminal_receipt = {
"schema_version": "agent99_outcome_timeout_no_write_v1",
"status": "authenticated_outcome_timeout",
**_stage_identity(identity),
"timeout_at": timeout_at.isoformat(),
"terminal_at": now.isoformat(),
"terminalizer_runtime_write_performed": False,
"original_runtime_write_status": (
"unknown_without_authenticated_outcome"
),
"transport_replayed": False,
"incident_resolution_authorized": False,
"learning_writeback_authorized": False,
"telegram_authorized": False,
"stores_raw_evidence": False,
}
envelope.update({
"status": "outcome_timeout_no_write_terminal",
"run_terminal_state": "failed",
"outcome_timeout_no_write_terminal": True,
"prior_controlled_apply_authorized": (
prior_controlled_apply_authorized
),
"prior_runtime_execution_authorized": (
prior_runtime_execution_authorized
),
"runtime_execution_attempted": (
prior_runtime_execution_attempted
),
"controlled_apply_authorized": False,
"runtime_execution_authorized": False,
"terminalizer_runtime_write_performed": False,
"outcome_runtime_write_status": (
"unknown_without_authenticated_outcome"
),
"transport_replayed": False,
"automation_execution_success": False,
"post_verifier_passed": False,
"runtime_closure_verified": False,
"closure_complete": False,
"incident_resolution_authorized": False,
"safe_next_action": (
"await_new_source_recurrence_after_agent99_relay_recovery"
),
"verifier": {
**_stage_identity(identity),
"step_seq": 2,
"status": "failed_authenticated_outcome_timeout",
"post_verifier_passed": False,
"receipt": terminal_receipt,
},
"learning_writeback": {
**_stage_identity(identity),
"step_seq": 3,
"status": "not_applicable_outcome_timeout_no_write",
"required_receipts": [],
"receipt_refs": {},
"stores_raw_evidence": False,
},
})
envelope_json = _stable_json(envelope)
updated = await db.execute(
update(AwoooPRunState)
.where(
AwoooPRunState.run_id == identity.run_id,
AwoooPRunState.project_id == identity.project_id,
AwoooPRunState.state == "waiting_tool",
AwoooPRunState.timeout_at <= now,
)
.values(
state="failed",
output_sha256=_sha256(envelope_json),
error_code=error_code,
error_detail=envelope_json,
heartbeat_at=now,
completed_at=now,
lease_until=None,
next_attempt_at=None,
worker_id=None,
)
.returning(AwoooPRunState.run_id)
)
persisted = updated.scalar_one_or_none() is not None
if persisted:
await db.execute(
update(AwoooPRunStepJournal)
.where(
AwoooPRunStepJournal.run_id == identity.run_id,
AwoooPRunStepJournal.step_seq.in_([2, 3]),
AwoooPRunStepJournal.result_status == "pending",
)
.values(
output_hash=_sha256(_stable_json(terminal_receipt)),
compensation_json=terminal_receipt,
result_status="failed",
error_code=error_code,
was_blocked=True,
block_reason="authenticated_outcome_timeout_no_write",
completed_at=now,
)
)
except Exception as exc:
logger.warning(
"agent99_outcome_timeout_terminal_failed_fail_closed",
incident_id=identity.incident_id,
run_id=str(identity.run_id),
error=str(exc),
)
return {
"status": "outcome_timeout_terminal_persistence_failed",
"receipt_persisted": False,
"runtime_closure_verified": False,
}
return {
**envelope,
"receipt_persisted": persisted,
"runtime_closure_verified": False,
}
async def record_learning_writeback(
self,
*,
@@ -2236,6 +2430,7 @@ class PostgresAgent99DispatchLedger:
AwoooPRunState.run_id,
AwoooPRunState.state,
AwoooPRunState.error_detail,
AwoooPRunState.timeout_at,
)
.where(
AwoooPRunState.project_id == (project_id or "awoooi"),
@@ -2309,6 +2504,7 @@ class PostgresAgent99DispatchLedger:
"identity": identity,
"run_state": str(row.state),
"receipt": envelope,
"timeout_at": getattr(row, "timeout_at", None),
})
return items