|
@@ -17,6 +17,7 @@ from typing import Any, Literal, TypeAlias
|
|
|
|
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
|
|
|
|
|
|
|
|
+from cyber_agent.application.quality import ValidationSubject
|
|
|
from cyber_agent.core.artifacts import MaterialIssue, ValidationMaterial
|
|
from cyber_agent.core.artifacts import MaterialIssue, ValidationMaterial
|
|
|
from cyber_agent.core.validator_web import ValidatorToolSession
|
|
from cyber_agent.core.validator_web import ValidatorToolSession
|
|
|
from cyber_agent.trace.models import Message, Trace
|
|
from cyber_agent.trace.models import Message, Trace
|
|
@@ -162,6 +163,8 @@ class ValidationPlan(_StrictModel):
|
|
|
effective_scopes: list[ValidationScope] = Field(min_length=1)
|
|
effective_scopes: list[ValidationScope] = Field(min_length=1)
|
|
|
checks: list[ValidationCheckSpec] = Field(min_length=1)
|
|
checks: list[ValidationCheckSpec] = Field(min_length=1)
|
|
|
material_manifest: list[dict[str, Any]] = Field(default_factory=list)
|
|
material_manifest: list[dict[str, Any]] = Field(default_factory=list)
|
|
|
|
|
+ subject: ValidationSubject | None = None
|
|
|
|
|
+ quality_manifest: list[dict[str, Any]] = Field(default_factory=list)
|
|
|
plan_hash: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
plan_hash: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
|
|
|
|
|
|
@model_validator(mode="after")
|
|
@model_validator(mode="after")
|
|
@@ -369,12 +372,19 @@ class ValidationPolicy(_StrictModel):
|
|
|
model_by_scope: Mapping[str, str],
|
|
model_by_scope: Mapping[str, str],
|
|
|
root: bool,
|
|
root: bool,
|
|
|
task_progress: Mapping[str, Any] | BaseModel | None = None,
|
|
task_progress: Mapping[str, Any] | BaseModel | None = None,
|
|
|
|
|
+ subject: ValidationSubject | None = None,
|
|
|
|
|
+ quality_checks: Sequence[ValidationCheckSpec] = (),
|
|
|
|
|
+ quality_manifest: Sequence[Mapping[str, Any]] = (),
|
|
|
) -> ValidationPlan:
|
|
) -> ValidationPlan:
|
|
|
"""从任务合同和权威材料确定性编译完整检查计划。"""
|
|
"""从任务合同和权威材料确定性编译完整检查计划。"""
|
|
|
brief = _jsonable(task_brief) or {}
|
|
brief = _jsonable(task_brief) or {}
|
|
|
anchor = _jsonable(root_task_anchor) or {}
|
|
anchor = _jsonable(root_task_anchor) or {}
|
|
|
requested = brief.get("validation_scopes", []) if isinstance(brief, dict) else []
|
|
requested = brief.get("validation_scopes", []) if isinstance(brief, dict) else []
|
|
|
- scopes = self.effective_scopes(requested, root=root)
|
|
|
|
|
|
|
+ scopes = (
|
|
|
|
|
+ ["output"]
|
|
|
|
|
+ if subject is not None and subject.subject_type == "candidate"
|
|
|
|
|
+ else self.effective_scopes(requested, root=root)
|
|
|
|
|
+ )
|
|
|
if isinstance(brief, dict):
|
|
if isinstance(brief, dict):
|
|
|
brief = dict(brief)
|
|
brief = dict(brief)
|
|
|
brief["validation_scopes"] = [
|
|
brief["validation_scopes"] = [
|
|
@@ -431,6 +441,9 @@ class ValidationPolicy(_StrictModel):
|
|
|
),
|
|
),
|
|
|
method="deterministic",
|
|
method="deterministic",
|
|
|
))
|
|
))
|
|
|
|
|
+ checks.extend(quality_checks)
|
|
|
|
|
+ if any(check.scope not in scopes for check in quality_checks):
|
|
|
|
|
+ raise ValueError("quality checks must belong to an effective scope")
|
|
|
|
|
|
|
|
manifest = [
|
|
manifest = [
|
|
|
{
|
|
{
|
|
@@ -475,6 +488,8 @@ class ValidationPolicy(_StrictModel):
|
|
|
"candidate_output": candidate_output,
|
|
"candidate_output": candidate_output,
|
|
|
"model_by_scope": dict(model_by_scope),
|
|
"model_by_scope": dict(model_by_scope),
|
|
|
"tool_policy_version": self.tool_policy_version,
|
|
"tool_policy_version": self.tool_policy_version,
|
|
|
|
|
+ "subject": _jsonable(subject),
|
|
|
|
|
+ "quality_manifest": _jsonable(quality_manifest),
|
|
|
}
|
|
}
|
|
|
return ValidationPlan(
|
|
return ValidationPlan(
|
|
|
policy_version=self.policy_version,
|
|
policy_version=self.policy_version,
|
|
@@ -486,6 +501,8 @@ class ValidationPolicy(_StrictModel):
|
|
|
effective_scopes=scopes,
|
|
effective_scopes=scopes,
|
|
|
checks=checks,
|
|
checks=checks,
|
|
|
material_manifest=manifest,
|
|
material_manifest=manifest,
|
|
|
|
|
+ subject=subject,
|
|
|
|
|
+ quality_manifest=[dict(item) for item in quality_manifest],
|
|
|
plan_hash=_sha(base),
|
|
plan_hash=_sha(base),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -620,6 +637,7 @@ def build_validation_packet(
|
|
|
candidate_output: str | None = None,
|
|
candidate_output: str | None = None,
|
|
|
validation_plan: ValidationPlan | None = None,
|
|
validation_plan: ValidationPlan | None = None,
|
|
|
materials: Sequence[ValidationMaterial] = (),
|
|
materials: Sequence[ValidationMaterial] = (),
|
|
|
|
|
+ fixed_checks: Mapping[str, ValidationCheck] | None = None,
|
|
|
max_chars: int = MAX_VALIDATION_INPUT_CHARS,
|
|
max_chars: int = MAX_VALIDATION_INPUT_CHARS,
|
|
|
) -> str:
|
|
) -> str:
|
|
|
"""固定保留任务合同、Plan和真实材料,再按最新优先裁剪主路径。"""
|
|
"""固定保留任务合同、Plan和真实材料,再按最新优先裁剪主路径。"""
|
|
@@ -628,10 +646,20 @@ def build_validation_packet(
|
|
|
completion_criteria = brief.get("completion_criteria")
|
|
completion_criteria = brief.get("completion_criteria")
|
|
|
if expected_outputs is None and isinstance(brief, dict):
|
|
if expected_outputs is None and isinstance(brief, dict):
|
|
|
expected_outputs = brief.get("expected_outputs")
|
|
expected_outputs = brief.get("expected_outputs")
|
|
|
|
|
+ serialized_plan = _jsonable(validation_plan)
|
|
|
|
|
+ if isinstance(serialized_plan, dict) and fixed_checks:
|
|
|
|
|
+ serialized_plan = dict(serialized_plan)
|
|
|
|
|
+ serialized_plan["checks"] = [
|
|
|
|
|
+ item for item in serialized_plan.get("checks", [])
|
|
|
|
|
+ if item.get("check_id") not in fixed_checks
|
|
|
|
|
+ ]
|
|
|
packet: dict[str, Any] = {
|
|
packet: dict[str, Any] = {
|
|
|
VALIDATION_PROTOCOL_MARKER: True,
|
|
VALIDATION_PROTOCOL_MARKER: True,
|
|
|
"validation_scope": validation_scope,
|
|
"validation_scope": validation_scope,
|
|
|
- "validation_plan": _jsonable(validation_plan),
|
|
|
|
|
|
|
+ "validation_plan": serialized_plan,
|
|
|
|
|
+ "framework_checks": [
|
|
|
|
|
+ item.model_dump(mode="json") for item in (fixed_checks or {}).values()
|
|
|
|
|
+ ],
|
|
|
"root_task_anchor": _jsonable(root_task_anchor),
|
|
"root_task_anchor": _jsonable(root_task_anchor),
|
|
|
"task_brief": brief,
|
|
"task_brief": brief,
|
|
|
"completion_criteria": _jsonable(completion_criteria or []),
|
|
"completion_criteria": _jsonable(completion_criteria or []),
|
|
@@ -696,6 +724,7 @@ def parse_scope_validation_result(
|
|
|
expected_scope: ValidationScope,
|
|
expected_scope: ValidationScope,
|
|
|
validator_trace_id: str,
|
|
validator_trace_id: str,
|
|
|
opened_source_ids: set[str] | None = None,
|
|
opened_source_ids: set[str] | None = None,
|
|
|
|
|
+ fixed_checks: Mapping[str, ValidationCheck] | None = None,
|
|
|
) -> ScopeValidationResult:
|
|
) -> ScopeValidationResult:
|
|
|
"""严格绑定Plan check_id,并由框架注入Validator Trace ID。"""
|
|
"""严格绑定Plan check_id,并由框架注入Validator Trace ID。"""
|
|
|
raw = json.loads(content)
|
|
raw = json.loads(content)
|
|
@@ -706,7 +735,19 @@ def parse_scope_validation_result(
|
|
|
raise ValueError(
|
|
raise ValueError(
|
|
|
f"validator returned scope {decision.scope!r}, expected {expected_scope!r}"
|
|
f"validator returned scope {decision.scope!r}, expected {expected_scope!r}"
|
|
|
)
|
|
)
|
|
|
- planned = {item.check_id: item for item in plan.checks_for_scope(expected_scope)}
|
|
|
|
|
|
|
+ fixed_checks = fixed_checks or {}
|
|
|
|
|
+ all_planned = {
|
|
|
|
|
+ item.check_id: item for item in plan.checks_for_scope(expected_scope)
|
|
|
|
|
+ }
|
|
|
|
|
+ unknown_fixed = set(fixed_checks) - set(all_planned)
|
|
|
|
|
+ if unknown_fixed:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ f"framework checks are not in plan: {sorted(unknown_fixed)}"
|
|
|
|
|
+ )
|
|
|
|
|
+ planned = {
|
|
|
|
|
+ check_id: spec for check_id, spec in all_planned.items()
|
|
|
|
|
+ if check_id not in fixed_checks
|
|
|
|
|
+ }
|
|
|
returned_ids = [item.check_id for item in decision.checks]
|
|
returned_ids = [item.check_id for item in decision.checks]
|
|
|
if len(returned_ids) != len(set(returned_ids)):
|
|
if len(returned_ids) != len(set(returned_ids)):
|
|
|
raise ValueError("validator returned duplicate check_id values")
|
|
raise ValueError("validator returned duplicate check_id values")
|
|
@@ -731,11 +772,18 @@ def parse_scope_validation_result(
|
|
|
raise ValueError(
|
|
raise ValueError(
|
|
|
f"{expected_scope} non-pass must use retry_from={expected_retry}"
|
|
f"{expected_scope} non-pass must use retry_from={expected_retry}"
|
|
|
)
|
|
)
|
|
|
|
|
+ combined_by_id = {**fixed_checks, **{
|
|
|
|
|
+ item.check_id: item for item in decision.checks
|
|
|
|
|
+ }}
|
|
|
|
|
+ combined = [
|
|
|
|
|
+ combined_by_id[item.check_id]
|
|
|
|
|
+ for item in plan.checks_for_scope(expected_scope)
|
|
|
|
|
+ ]
|
|
|
result = ScopeValidationResult(
|
|
result = ScopeValidationResult(
|
|
|
validator_trace_id=validator_trace_id,
|
|
validator_trace_id=validator_trace_id,
|
|
|
scope=expected_scope,
|
|
scope=expected_scope,
|
|
|
outcome=decision.outcome,
|
|
outcome=decision.outcome,
|
|
|
- checks=decision.checks,
|
|
|
|
|
|
|
+ checks=combined,
|
|
|
reason=decision.reason,
|
|
reason=decision.reason,
|
|
|
retry_from=decision.retry_from,
|
|
retry_from=decision.retry_from,
|
|
|
plan_hash=plan.plan_hash,
|
|
plan_hash=plan.plan_hash,
|
|
@@ -837,6 +885,72 @@ def _framework_scope_result(
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _scope_result_with_fixed_checks(
|
|
|
|
|
+ *,
|
|
|
|
|
+ validator_trace_id: str,
|
|
|
|
|
+ scope: ValidationScope,
|
|
|
|
|
+ plan: ValidationPlan,
|
|
|
|
|
+ fixed_checks: Mapping[str, ValidationCheck],
|
|
|
|
|
+ error_reason: str | None = None,
|
|
|
|
|
+) -> ScopeValidationResult:
|
|
|
|
|
+ """Complete a blocked deterministic scope without asking the model."""
|
|
|
|
|
+ planned = plan.checks_for_scope(scope)
|
|
|
|
|
+ fixed_ids = set(fixed_checks)
|
|
|
|
|
+ unknown = fixed_ids - {item.check_id for item in planned}
|
|
|
|
|
+ if unknown:
|
|
|
|
|
+ raise ValueError(f"framework checks are not in plan: {sorted(unknown)}")
|
|
|
|
|
+ failing = next(
|
|
|
|
|
+ (item for item in fixed_checks.values() if item.status == "failed"),
|
|
|
|
|
+ None,
|
|
|
|
|
+ )
|
|
|
|
|
+ unresolved = next(
|
|
|
|
|
+ (item for item in fixed_checks.values() if item.status == "unknown"),
|
|
|
|
|
+ None,
|
|
|
|
|
+ )
|
|
|
|
|
+ primary = failing or unresolved
|
|
|
|
|
+ if error_reason is not None:
|
|
|
|
|
+ outcome: ValidationOutcome = "error"
|
|
|
|
|
+ reason = error_reason
|
|
|
|
|
+ elif failing is not None:
|
|
|
|
|
+ outcome = "failed"
|
|
|
|
|
+ reason = failing.issue or "Deterministic quality check failed"
|
|
|
|
|
+ elif unresolved is not None:
|
|
|
|
|
+ outcome = "unknown"
|
|
|
|
|
+ reason = unresolved.issue or "Deterministic quality check is unknown"
|
|
|
|
|
+ else:
|
|
|
|
|
+ outcome = "passed"
|
|
|
|
|
+ reason = "All deterministic checks passed"
|
|
|
|
|
+ checks: list[ValidationCheck] = []
|
|
|
|
|
+ for spec in planned:
|
|
|
|
|
+ if spec.check_id in fixed_checks:
|
|
|
|
|
+ checks.append(fixed_checks[spec.check_id])
|
|
|
|
|
+ elif outcome == "passed":
|
|
|
|
|
+ raise ValueError("scope still has model checks to execute")
|
|
|
|
|
+ else:
|
|
|
|
|
+ blocker = primary.check_id if primary else "quality provider"
|
|
|
|
|
+ checks.append(ValidationCheck(
|
|
|
|
|
+ check_id=spec.check_id,
|
|
|
|
|
+ status="unknown",
|
|
|
|
|
+ issue=f"Not evaluated because {blocker} blocked this scope: {reason}",
|
|
|
|
|
+ ))
|
|
|
|
|
+ result = ScopeValidationResult(
|
|
|
|
|
+ validator_trace_id=validator_trace_id,
|
|
|
|
|
+ scope=scope,
|
|
|
|
|
+ outcome=outcome,
|
|
|
|
|
+ checks=checks,
|
|
|
|
|
+ reason=reason,
|
|
|
|
|
+ retry_from=(
|
|
|
|
|
+ None if outcome in {"passed", "error"} else _SCOPE_RETRY[scope]
|
|
|
|
|
+ ),
|
|
|
|
|
+ plan_hash=plan.plan_hash,
|
|
|
|
|
+ )
|
|
|
|
|
+ return _require_scope_result_integrity(
|
|
|
|
|
+ result,
|
|
|
|
|
+ plan=plan,
|
|
|
|
|
+ expected_scope=scope,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def scope_validation_error(
|
|
def scope_validation_error(
|
|
|
*,
|
|
*,
|
|
|
validator_trace_id: str,
|
|
validator_trace_id: str,
|
|
@@ -965,9 +1079,13 @@ class LLMValidator:
|
|
|
source_urls: Sequence[str] = (),
|
|
source_urls: Sequence[str] = (),
|
|
|
resume_scope_results: Sequence[ScopeValidationResult] = (),
|
|
resume_scope_results: Sequence[ScopeValidationResult] = (),
|
|
|
on_scope_result: ScopeResultCallback | None = None,
|
|
on_scope_result: ScopeResultCallback | None = None,
|
|
|
|
|
+ fixed_checks: Mapping[str, ValidationCheck] | None = None,
|
|
|
|
|
+ fixed_scope_errors: Mapping[ValidationScope, str] | None = None,
|
|
|
) -> ValidationRun:
|
|
) -> ValidationRun:
|
|
|
"""顺序执行所有Scope;每完成一项即可回调持久化断点。"""
|
|
"""顺序执行所有Scope;每完成一项即可回调持久化断点。"""
|
|
|
started = time.monotonic()
|
|
started = time.monotonic()
|
|
|
|
|
+ fixed_checks = fixed_checks or {}
|
|
|
|
|
+ fixed_scope_errors = fixed_scope_errors or {}
|
|
|
runs: list[_ScopeRun] = []
|
|
runs: list[_ScopeRun] = []
|
|
|
resumed: dict[ValidationScope, ScopeValidationResult] = {}
|
|
resumed: dict[ValidationScope, ScopeValidationResult] = {}
|
|
|
for item in resume_scope_results:
|
|
for item in resume_scope_results:
|
|
@@ -993,7 +1111,31 @@ class LLMValidator:
|
|
|
)
|
|
)
|
|
|
try:
|
|
try:
|
|
|
blocking_issue = _blocking_material_issue(material_issues, scope)
|
|
blocking_issue = _blocking_material_issue(material_issues, scope)
|
|
|
- if blocking_issue is not None:
|
|
|
|
|
|
|
+ scope_fixed = {
|
|
|
|
|
+ check_id: check
|
|
|
|
|
+ for check_id, check in fixed_checks.items()
|
|
|
|
|
+ if check_id in {
|
|
|
|
|
+ item.check_id for item in plan.checks_for_scope(scope)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ fixed_blocked = (
|
|
|
|
|
+ scope in fixed_scope_errors
|
|
|
|
|
+ or any(item.status != "passed" for item in scope_fixed.values())
|
|
|
|
|
+ )
|
|
|
|
|
+ remaining_checks = [
|
|
|
|
|
+ item for item in plan.checks_for_scope(scope)
|
|
|
|
|
+ if item.check_id not in scope_fixed
|
|
|
|
|
+ ]
|
|
|
|
|
+ if fixed_blocked or (scope_fixed and not remaining_checks):
|
|
|
|
|
+ run = await self.record_scope_fixed(
|
|
|
|
|
+ evaluated_trace=evaluated_trace,
|
|
|
|
|
+ plan=plan,
|
|
|
|
|
+ scope=scope,
|
|
|
|
|
+ fixed_checks=scope_fixed,
|
|
|
|
|
+ error_reason=fixed_scope_errors.get(scope),
|
|
|
|
|
+ validator_trace_id=trace_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ elif blocking_issue is not None:
|
|
|
run = await self.record_scope_non_success(
|
|
run = await self.record_scope_non_success(
|
|
|
evaluated_trace=evaluated_trace,
|
|
evaluated_trace=evaluated_trace,
|
|
|
plan=plan,
|
|
plan=plan,
|
|
@@ -1017,6 +1159,7 @@ class LLMValidator:
|
|
|
model=model_by_scope.get(scope, ""),
|
|
model=model_by_scope.get(scope, ""),
|
|
|
source_urls=source_urls,
|
|
source_urls=source_urls,
|
|
|
validator_trace_id=trace_id,
|
|
validator_trace_id=trace_id,
|
|
|
|
|
+ fixed_checks=scope_fixed,
|
|
|
)
|
|
)
|
|
|
finally:
|
|
finally:
|
|
|
if self.trace_release:
|
|
if self.trace_release:
|
|
@@ -1079,6 +1222,7 @@ class LLMValidator:
|
|
|
source_urls: Sequence[str],
|
|
source_urls: Sequence[str],
|
|
|
validator_trace_id: str,
|
|
validator_trace_id: str,
|
|
|
task_progress: Mapping[str, Any] | BaseModel | None = None,
|
|
task_progress: Mapping[str, Any] | BaseModel | None = None,
|
|
|
|
|
+ fixed_checks: Mapping[str, ValidationCheck] | None = None,
|
|
|
) -> _ScopeRun:
|
|
) -> _ScopeRun:
|
|
|
"""创建一个Scope Trace并运行严格白名单的模型/工具循环。"""
|
|
"""创建一个Scope Trace并运行严格白名单的模型/工具循环。"""
|
|
|
session = (
|
|
session = (
|
|
@@ -1108,6 +1252,7 @@ class LLMValidator:
|
|
|
candidate_output=candidate_output,
|
|
candidate_output=candidate_output,
|
|
|
validation_plan=plan,
|
|
validation_plan=plan,
|
|
|
materials=materials,
|
|
materials=materials,
|
|
|
|
|
+ fixed_checks=fixed_checks,
|
|
|
max_chars=self.max_input_chars,
|
|
max_chars=self.max_input_chars,
|
|
|
)
|
|
)
|
|
|
except Exception as exc:
|
|
except Exception as exc:
|
|
@@ -1164,6 +1309,7 @@ class LLMValidator:
|
|
|
expected_scope=scope,
|
|
expected_scope=scope,
|
|
|
validator_trace_id=validator_trace_id,
|
|
validator_trace_id=validator_trace_id,
|
|
|
opened_source_ids=(session.opened_source_ids if session else set()),
|
|
opened_source_ids=(session.opened_source_ids if session else set()),
|
|
|
|
|
+ fixed_checks=fixed_checks,
|
|
|
)
|
|
)
|
|
|
duration_ms = int((time.monotonic() - started) * 1000)
|
|
duration_ms = int((time.monotonic() - started) * 1000)
|
|
|
await self._finish_trace(
|
|
await self._finish_trace(
|
|
@@ -1241,6 +1387,35 @@ class LLMValidator:
|
|
|
duration_ms=int((time.monotonic() - started) * 1000),
|
|
duration_ms=int((time.monotonic() - started) * 1000),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ async def record_scope_fixed(
|
|
|
|
|
+ self,
|
|
|
|
|
+ *,
|
|
|
|
|
+ evaluated_trace: Trace,
|
|
|
|
|
+ plan: ValidationPlan,
|
|
|
|
|
+ scope: ValidationScope,
|
|
|
|
|
+ fixed_checks: Mapping[str, ValidationCheck],
|
|
|
|
|
+ error_reason: str | None,
|
|
|
|
|
+ validator_trace_id: str,
|
|
|
|
|
+ ) -> _ScopeRun:
|
|
|
|
|
+ """Persist an all-deterministic or deterministically blocked scope."""
|
|
|
|
|
+ trace = self._new_trace(
|
|
|
|
|
+ trace_id=validator_trace_id,
|
|
|
|
|
+ evaluated_trace=evaluated_trace,
|
|
|
|
|
+ model=None,
|
|
|
|
|
+ scope=scope,
|
|
|
|
|
+ plan_hash=plan.plan_hash,
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.trace_store.create_trace(trace)
|
|
|
|
|
+ result = _scope_result_with_fixed_checks(
|
|
|
|
|
+ validator_trace_id=validator_trace_id,
|
|
|
|
|
+ scope=scope,
|
|
|
|
|
+ plan=plan,
|
|
|
|
|
+ fixed_checks=fixed_checks,
|
|
|
|
|
+ error_reason=error_reason,
|
|
|
|
|
+ )
|
|
|
|
|
+ await self._store_terminal_message(trace, result)
|
|
|
|
|
+ return _ScopeRun(result=result, trace_id=validator_trace_id)
|
|
|
|
|
+
|
|
|
async def record_scope_non_success(
|
|
async def record_scope_non_success(
|
|
|
self,
|
|
self,
|
|
|
*,
|
|
*,
|