forgexa-cli 1.85.1__tar.gz → 1.85.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/PKG-INFO +1 -1
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/agent_core.py +140 -4
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/daemon.py +16 -8
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/pyproject.toml +1 -1
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/README.md +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/aws_evidence.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/aws_logs_evidence.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/observability_contract.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/observability_evidence.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/setup.cfg +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_publish_script.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.85.1 → forgexa_cli-1.85.2}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.85.
|
|
2
|
+
__version__ = "1.85.2"
|
|
@@ -68,6 +68,11 @@ except ImportError:
|
|
|
68
68
|
logger = logging.getLogger("agent_core")
|
|
69
69
|
|
|
70
70
|
IMPLEMENTATION_AUDIT_PROTOCOL_VERSION = 3
|
|
71
|
+
IMPLEMENTATION_ENVIRONMENT_BLOCKERS = frozenset({
|
|
72
|
+
"environment_unavailable", "service_not_running", "dependency_unavailable",
|
|
73
|
+
"permission_required", "test_data_unavailable", "target_mismatch",
|
|
74
|
+
})
|
|
75
|
+
TASK_FAILURE_DOMAINS = frozenset({"code_quality", "execution", "environment", "evidence", "delivery", "plan", "verification"})
|
|
71
76
|
LOCAL_BIND_PRIVATE_VALIDATION_METRIC_KEYS = frozenset({
|
|
72
77
|
"validation_issues", "validation_issue_details", "timeout_recovery_validation_issues",
|
|
73
78
|
"timeout_recovery_original_failure", "task_delivery_issue",
|
|
@@ -238,6 +243,8 @@ def redact_implementation_audit(contract: dict, report: object) -> dict | None:
|
|
|
238
243
|
check = {"status": row["status"], "evidence": evidence}
|
|
239
244
|
if row["status"] != "covered":
|
|
240
245
|
check.update(description="Local implementation issue remains unresolved; inspect local task evidence.", files=[])
|
|
246
|
+
if row.get("blocking_reason") in IMPLEMENTATION_ENVIRONMENT_BLOCKERS:
|
|
247
|
+
check["blocking_reason"] = row["blocking_reason"]
|
|
241
248
|
if row.get("plan_revision"):
|
|
242
249
|
check["plan_revision"] = {
|
|
243
250
|
"reason": "Local evidence conflicts with the approved plan",
|
|
@@ -281,7 +288,7 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
281
288
|
checks.append(row)
|
|
282
289
|
continue
|
|
283
290
|
check = {"task_id": row.get("task_id"), **{
|
|
284
|
-
key: implementation[key] for key in ("status", "evidence", "description", "files", "plan_revision")
|
|
291
|
+
key: implementation[key] for key in ("status", "evidence", "description", "files", "plan_revision", "blocking_reason")
|
|
285
292
|
if key in implementation
|
|
286
293
|
}}
|
|
287
294
|
if row.get("plan_revision") is not None and implementation.get("status") != "covered":
|
|
@@ -296,6 +303,7 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
296
303
|
"task_id": task_id, "status": row.get("status"),
|
|
297
304
|
"description": row.get("description") or "Runtime acceptance remains pending",
|
|
298
305
|
"files": [], "evidence": row.get("evidence") or [],
|
|
306
|
+
**({"blocking_reason": row["blocking_reason"]} if row.get("blocking_reason") else {}),
|
|
299
307
|
} for task_id in pending_verification_ids)
|
|
300
308
|
implementation_statuses = {row["task_id"]: row["status"] for row in checks}
|
|
301
309
|
deferred_contract = contract.get("deferred_acceptance_verification")
|
|
@@ -363,6 +371,7 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
363
371
|
pending.extend({
|
|
364
372
|
"task_id": task_id, "status": row["status"], "description": row["description"],
|
|
365
373
|
"files": row.get("files") or [], "evidence": row["evidence"],
|
|
374
|
+
**({"blocking_reason": row["blocking_reason"]} if row.get("blocking_reason") else {}),
|
|
366
375
|
} for task_id in pending_verification_ids)
|
|
367
376
|
acceptance_checks.append(row)
|
|
368
377
|
result = assess_implementation_audit({
|
|
@@ -443,6 +452,13 @@ def assess_implementation_audit(contract: dict, report: object) -> dict:
|
|
|
443
452
|
):
|
|
444
453
|
issues.append(f"Implementation {kind} check requires an explicit status and concrete evidence")
|
|
445
454
|
continue
|
|
455
|
+
blocking_reason = row.get("blocking_reason")
|
|
456
|
+
if blocking_reason is not None and (
|
|
457
|
+
not isinstance(blocking_reason, str) or blocking_reason not in IMPLEMENTATION_ENVIRONMENT_BLOCKERS
|
|
458
|
+
or status not in ("blocked", "unverified") or row.get("plan_revision") is not None
|
|
459
|
+
):
|
|
460
|
+
issues.append("Implementation blocking_reason must identify an allowed unmet environment prerequisite on a blocked or unverified check")
|
|
461
|
+
continue
|
|
446
462
|
if status == "covered" and row.get("plan_revision") is not None:
|
|
447
463
|
issues.append(
|
|
448
464
|
"A covered implementation check cannot request a plan revision; "
|
|
@@ -541,6 +557,7 @@ def assess_implementation_audit(contract: dict, report: object) -> dict:
|
|
|
541
557
|
"task_id": task_id, "status": status, "description": description,
|
|
542
558
|
"files": paths, "evidence": evidence,
|
|
543
559
|
**({"plan_revision": revision} if revision is not None else {}),
|
|
560
|
+
**({"blocking_reason": blocking_reason} if blocking_reason is not None else {}),
|
|
544
561
|
} for task_id in related)
|
|
545
562
|
required = expected if contract.get("final_verification") else {subject}
|
|
546
563
|
if required - checked.keys():
|
|
@@ -563,6 +580,57 @@ def assess_implementation_audit(contract: dict, report: object) -> dict:
|
|
|
563
580
|
return result
|
|
564
581
|
|
|
565
582
|
|
|
583
|
+
def implementation_audit_failure_domain(contract: dict, assessment: dict) -> str | None:
|
|
584
|
+
if not assessment.get("report_valid"):
|
|
585
|
+
return "evidence"
|
|
586
|
+
if assessment.get("node_complete"):
|
|
587
|
+
return None
|
|
588
|
+
if assessment.get("status") == "plan_revision_required":
|
|
589
|
+
return "plan"
|
|
590
|
+
subject = contract.get("subject_task_id")
|
|
591
|
+
findings = [finding for finding in assessment.get("findings") or [] if finding.get("task_id") == subject]
|
|
592
|
+
if findings and all(
|
|
593
|
+
finding.get("status") in ("blocked", "unverified")
|
|
594
|
+
and finding.get("blocking_reason") in IMPLEMENTATION_ENVIRONMENT_BLOCKERS for finding in findings
|
|
595
|
+
):
|
|
596
|
+
return "environment"
|
|
597
|
+
role = next((task.get("type") for task in contract.get("tasks") or [] if task.get("id") == subject), None)
|
|
598
|
+
if role in ("coding", "fix") and any(finding.get("status") == "gap" for finding in findings):
|
|
599
|
+
return "code_quality"
|
|
600
|
+
return "verification"
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def task_failure_domain(
|
|
604
|
+
failure_code: str | None, *, audit_contract: dict | None = None,
|
|
605
|
+
audit_assessment: dict | None = None, conformance: dict | None = None,
|
|
606
|
+
payload_failed: bool = False,
|
|
607
|
+
) -> str:
|
|
608
|
+
if failure_code in {
|
|
609
|
+
"planned_task_incomplete", "planned_task_baseline_invalid", "approved_plan_baseline_modified",
|
|
610
|
+
"remediation_scope_violation", "verification_work_item_scope_violation", "uncommitted_workspace_changes",
|
|
611
|
+
"forbidden_branch_merge",
|
|
612
|
+
}:
|
|
613
|
+
return "delivery"
|
|
614
|
+
if failure_code == "implementation_plan_revision_required":
|
|
615
|
+
return "plan"
|
|
616
|
+
if payload_failed:
|
|
617
|
+
return "execution"
|
|
618
|
+
if failure_code == "implementation_audit_incomplete":
|
|
619
|
+
return implementation_audit_failure_domain(audit_contract or {}, audit_assessment or {}) or "verification"
|
|
620
|
+
if failure_code in {"approved_plan_deviation", "approved_plan_conformance_unverified"}:
|
|
621
|
+
assessment = conformance or {}
|
|
622
|
+
if assessment.get("reason") == "approved_plan_review_unavailable":
|
|
623
|
+
return "execution"
|
|
624
|
+
if assessment.get("reason") or assessment.get("diagnostic_code"):
|
|
625
|
+
return "evidence"
|
|
626
|
+
return "code_quality" if assessment.get("status") == "deviation" else "verification"
|
|
627
|
+
if failure_code == "acceptance_criteria_uncovered":
|
|
628
|
+
return "verification"
|
|
629
|
+
if failure_code in {"validation_unavailable", "validation_retry_exhausted"}:
|
|
630
|
+
return "evidence"
|
|
631
|
+
return "execution"
|
|
632
|
+
|
|
633
|
+
|
|
566
634
|
_WORKSPACE_INSTRUCTION_PATHS = (
|
|
567
635
|
"AGENTS.md",
|
|
568
636
|
"CLAUDE.md",
|
|
@@ -1889,7 +1957,7 @@ def has_agent_token_usage(*sources: object) -> bool:
|
|
|
1889
1957
|
return False
|
|
1890
1958
|
|
|
1891
1959
|
|
|
1892
|
-
def _extract_output_signals(text: str) -> dict[str, Any]:
|
|
1960
|
+
def _extract_output_signals(text: str, usage_source: object = None) -> dict[str, Any]:
|
|
1893
1961
|
"""Parse stdout/stderr-like streams for success and failure signals.
|
|
1894
1962
|
|
|
1895
1963
|
Uses structural signal detection — NOT keyword matching on result text.
|
|
@@ -1933,6 +2001,8 @@ def _extract_output_signals(text: str) -> dict[str, Any]:
|
|
|
1933
2001
|
|
|
1934
2002
|
if ev_type == "turn.completed":
|
|
1935
2003
|
has_turn_completed = True
|
|
2004
|
+
has_turn_failed = False
|
|
2005
|
+
error_messages.clear()
|
|
1936
2006
|
elif ev_type == "turn.failed":
|
|
1937
2007
|
has_turn_failed = True
|
|
1938
2008
|
err = data.get("error") or {}
|
|
@@ -1962,10 +2032,18 @@ def _extract_output_signals(text: str) -> dict[str, Any]:
|
|
|
1962
2032
|
# Claude result format: {"type":"result","result":"...","is_error":false,...}
|
|
1963
2033
|
if "exitCode" in data:
|
|
1964
2034
|
# Copilot JSONL result
|
|
1965
|
-
|
|
2035
|
+
raw_exit_code = data["exitCode"]
|
|
2036
|
+
if type(raw_exit_code) is not int and not (
|
|
2037
|
+
isinstance(raw_exit_code, str) and re.fullmatch(r"[+-]?[0-9]+", raw_exit_code)
|
|
2038
|
+
):
|
|
2039
|
+
error_messages.append("Agent result has an invalid exitCode")
|
|
2040
|
+
continue
|
|
2041
|
+
exit_code = int(raw_exit_code)
|
|
1966
2042
|
if exit_code == 0:
|
|
1967
2043
|
has_result = True
|
|
1968
2044
|
has_meaningful_content = True
|
|
2045
|
+
has_turn_failed = False
|
|
2046
|
+
error_messages.clear()
|
|
1969
2047
|
else:
|
|
1970
2048
|
error_messages.append(f"Copilot exited with code {exit_code}")
|
|
1971
2049
|
else:
|
|
@@ -1996,12 +2074,14 @@ def _extract_output_signals(text: str) -> dict[str, Any]:
|
|
|
1996
2074
|
# events appeared, the CLI never made an API call. The result
|
|
1997
2075
|
# text is a CLI-level error (e.g. "API Error: Connection error.")
|
|
1998
2076
|
# rather than the agent's actual work output.
|
|
1999
|
-
no_api_call = not has_agent_token_usage(data) and not has_assistant_events
|
|
2077
|
+
no_api_call = not has_agent_token_usage(data, usage_source) and not has_assistant_events
|
|
2000
2078
|
if no_api_call and result_text:
|
|
2001
2079
|
error_messages.append(result_text)
|
|
2002
2080
|
else:
|
|
2003
2081
|
has_result = True
|
|
2004
2082
|
has_meaningful_content = True
|
|
2083
|
+
has_turn_failed = False
|
|
2084
|
+
error_messages.clear()
|
|
2005
2085
|
elif ev_type == "session.error":
|
|
2006
2086
|
err = data.get("data") or {}
|
|
2007
2087
|
if isinstance(err, dict):
|
|
@@ -4988,6 +5068,62 @@ def is_copilot_model_discovery_auth_error(*messages: str | None) -> bool:
|
|
|
4988
5068
|
)
|
|
4989
5069
|
|
|
4990
5070
|
|
|
5071
|
+
# Transport failures a plain re-run can recover from. 421 Misdirected Request
|
|
5072
|
+
# is deliberately absent: it keeps its own recovery (retry without token env
|
|
5073
|
+
# vars) because it has historically indicated a credential-routing mismatch,
|
|
5074
|
+
# not a network blip.
|
|
5075
|
+
COPILOT_TRANSIENT_TRANSPORT_ERROR_PATTERNS = (
|
|
5076
|
+
"timed out",
|
|
5077
|
+
"timeout",
|
|
5078
|
+
"unexpected eof",
|
|
5079
|
+
"error sending request",
|
|
5080
|
+
"connection reset",
|
|
5081
|
+
"connection refused",
|
|
5082
|
+
"connection timed out",
|
|
5083
|
+
"connection error",
|
|
5084
|
+
"connection closed",
|
|
5085
|
+
"connection terminated",
|
|
5086
|
+
"network is unreachable",
|
|
5087
|
+
"no such host",
|
|
5088
|
+
"temporary failure in name resolution",
|
|
5089
|
+
)
|
|
5090
|
+
|
|
5091
|
+
COPILOT_STABLE_AUTH_ERROR_PATTERNS = (
|
|
5092
|
+
"unauthorized",
|
|
5093
|
+
"forbidden",
|
|
5094
|
+
"http 401",
|
|
5095
|
+
"http 403",
|
|
5096
|
+
"not logged in",
|
|
5097
|
+
"authentication required",
|
|
5098
|
+
"invalid api key",
|
|
5099
|
+
"your github token may be invalid",
|
|
5100
|
+
"rejected the cli github credential",
|
|
5101
|
+
)
|
|
5102
|
+
|
|
5103
|
+
|
|
5104
|
+
def is_copilot_model_discovery_transient_transport_error(*messages: str | None) -> bool:
|
|
5105
|
+
"""Return whether Copilot failed model discovery on a transient transport error.
|
|
5106
|
+
|
|
5107
|
+
Matches connect timeouts, unexpected EOF, resets and DNS hiccups during
|
|
5108
|
+
model discovery — the class of failures observed as intermittent on
|
|
5109
|
+
unstable routes (a daemon-side rerun typically succeeds, and the same
|
|
5110
|
+
login works interactively), as opposed to stable auth/config breakage.
|
|
5111
|
+
Copilot appends its remediation boilerplate ("To resolve this, ... run
|
|
5112
|
+
'/login' ...") to every discovery failure, so auth patterns are only
|
|
5113
|
+
trusted when they appear before that boilerplate.
|
|
5114
|
+
"""
|
|
5115
|
+
detail = " ".join(str(message or "").lower() for message in messages)
|
|
5116
|
+
detail = detail.partition("to resolve this")[0]
|
|
5117
|
+
if not (
|
|
5118
|
+
"retrieve the list of available models" in detail
|
|
5119
|
+
or "failed to load models" in detail
|
|
5120
|
+
):
|
|
5121
|
+
return False
|
|
5122
|
+
if any(pattern in detail for pattern in COPILOT_STABLE_AUTH_ERROR_PATTERNS):
|
|
5123
|
+
return False
|
|
5124
|
+
return any(pattern in detail for pattern in COPILOT_TRANSIENT_TRANSPORT_ERROR_PATTERNS)
|
|
5125
|
+
|
|
5126
|
+
|
|
4991
5127
|
def is_runtime_resource_exhaustion_error(*messages: str | None) -> bool:
|
|
4992
5128
|
"""Return whether the OS refused an agent process, thread, or file watch."""
|
|
4993
5129
|
detail = " ".join(str(message or "").lower() for message in messages)
|
|
@@ -1234,7 +1234,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
1234
1234
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
1235
1235
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
1236
1236
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
1237
|
-
DAEMON_VERSION = "1.85.
|
|
1237
|
+
DAEMON_VERSION = "1.85.2"
|
|
1238
1238
|
|
|
1239
1239
|
|
|
1240
1240
|
def _detect_client_type() -> str:
|
|
@@ -6439,8 +6439,8 @@ class ProcessManager:
|
|
|
6439
6439
|
int(result.metrics.get("token_input", 0) or 0)
|
|
6440
6440
|
+ int(result.metrics.get("token_output", 0) or 0)
|
|
6441
6441
|
) > 0
|
|
6442
|
-
signals =
|
|
6443
|
-
"\n".join(part for part in (stdout, stderr) if part)
|
|
6442
|
+
signals = agent_core._extract_output_signals(
|
|
6443
|
+
"\n".join(part for part in (stdout, stderr) if part), result.metrics,
|
|
6444
6444
|
)
|
|
6445
6445
|
if not has_token_usage and ProcessManager._should_scan_short_success_stdout(stdout, signals):
|
|
6446
6446
|
error_channels = "\n".join(filter(None, [error_channels, stdout.strip()]))
|
|
@@ -8484,15 +8484,18 @@ class ProcessManager:
|
|
|
8484
8484
|
|
|
8485
8485
|
model_discovery_detail = "\n".join([*structured_errors, stderr]).lower()
|
|
8486
8486
|
if (
|
|
8487
|
-
|
|
8488
|
-
|
|
8487
|
+
agent_core.is_copilot_model_discovery_transient_transport_error(
|
|
8488
|
+
*structured_errors, stderr,
|
|
8489
|
+
)
|
|
8490
|
+
and "misdirected request" not in model_discovery_detail
|
|
8489
8491
|
and not _retry_model_discovery_timeout
|
|
8490
8492
|
and not (copilot_exit == 0 and returncode == 0)
|
|
8491
8493
|
and not signals["has_meaningful_content"]
|
|
8492
8494
|
and not self._copilot_called_any_tools(stdout)
|
|
8493
8495
|
):
|
|
8494
8496
|
logger.warning(
|
|
8495
|
-
"Copilot model discovery
|
|
8497
|
+
"Copilot model discovery hit a transient transport error "
|
|
8498
|
+
"(timeout / connect reset) before work for task %s; "
|
|
8496
8499
|
"retrying once with the same credentials and configuration",
|
|
8497
8500
|
task_id,
|
|
8498
8501
|
)
|
|
@@ -14466,6 +14469,7 @@ class RuntimeDaemon:
|
|
|
14466
14469
|
audit_contract = input_data.get("implementation_audit_contract")
|
|
14467
14470
|
result.metrics.pop("implementation_audit_node_complete", None)
|
|
14468
14471
|
result.metrics.pop("implementation_audit_subject_blocked", None)
|
|
14472
|
+
result.metrics.pop("failure_domain", None)
|
|
14469
14473
|
if isinstance(audit_contract, dict):
|
|
14470
14474
|
agent_id = str(result.metrics.get("actual_agent") or getattr(task, "agent_type", "") or "")
|
|
14471
14475
|
audit_report = agent_core.extract_implementation_audit(_daemon_extract_agent_output(agent_id, result.stdout))
|
|
@@ -14476,13 +14480,17 @@ class RuntimeDaemon:
|
|
|
14476
14480
|
if isinstance(audit_report, dict):
|
|
14477
14481
|
result.observations.append({"type": "implementation_audit", "data": audit_report})
|
|
14478
14482
|
audit_result = agent_core.assess_implementation_audit(audit_contract, audit_report)
|
|
14483
|
+
failure_domain = agent_core.implementation_audit_failure_domain(audit_contract, audit_result)
|
|
14484
|
+
if failure_domain is not None:
|
|
14485
|
+
result.metrics["failure_domain"] = failure_domain
|
|
14479
14486
|
result.metrics["implementation_audit_node_complete"] = bool(
|
|
14480
14487
|
audit_result["report_valid"] and audit_result["node_complete"]
|
|
14481
14488
|
)
|
|
14482
14489
|
result.metrics["implementation_audit_subject_blocked"] = bool(
|
|
14483
14490
|
audit_result["report_valid"] and not audit_result["node_complete"]
|
|
14484
|
-
and
|
|
14485
|
-
|
|
14491
|
+
and (failure_domain == "environment" or any(
|
|
14492
|
+
finding.get("task_id") == audit_contract.get("subject_task_id") and finding.get("status") == "blocked"
|
|
14493
|
+
for finding in audit_result.get("findings") or []))
|
|
14486
14494
|
)
|
|
14487
14495
|
if audit_result["report_valid"] and audit_result["status"] == "plan_revision_required":
|
|
14488
14496
|
result.status = "failed"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|