forgexa-cli 1.86.1__tar.gz → 1.86.3__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.86.1 → forgexa_cli-1.86.3}/PKG-INFO +1 -1
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/agent_core.py +73 -4
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/daemon.py +54 -8
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/pyproject.toml +1 -1
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/README.md +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/aws_evidence.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/aws_logs_evidence.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/observability_contract.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/observability_evidence.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/setup.cfg +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_publish_script.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.86.1 → forgexa_cli-1.86.3}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.86.
|
|
2
|
+
__version__ = "1.86.3"
|
|
@@ -309,6 +309,14 @@ def requires_task_delivery_baseline(node_type: str, task_input: dict) -> bool:
|
|
|
309
309
|
|
|
310
310
|
|
|
311
311
|
def extract_implementation_audit(text: str) -> dict | None:
|
|
312
|
+
return extract_agent_report(text, "implementation_audit")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def extract_execution_delivery(text: str) -> dict | None:
|
|
316
|
+
return extract_agent_report(text, "execution_delivery")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def extract_agent_report(text: str, field: str) -> dict | None:
|
|
312
320
|
"""Read the last complete audit envelope from mixed assistant output."""
|
|
313
321
|
decoder = json.JSONDecoder()
|
|
314
322
|
offset = 0
|
|
@@ -323,11 +331,11 @@ def extract_implementation_audit(text: str) -> dict | None:
|
|
|
323
331
|
except json.JSONDecodeError:
|
|
324
332
|
offset = start + 1
|
|
325
333
|
continue
|
|
326
|
-
if isinstance(value, dict) and
|
|
327
|
-
report = value[
|
|
334
|
+
if isinstance(value, dict) and field in value:
|
|
335
|
+
report = value[field] if isinstance(value[field], dict) else None
|
|
328
336
|
report_end = end
|
|
329
337
|
offset = end
|
|
330
|
-
if
|
|
338
|
+
if json.dumps(field) in text[report_end:]:
|
|
331
339
|
return None
|
|
332
340
|
return report
|
|
333
341
|
|
|
@@ -774,9 +782,56 @@ def lock_provenance_projection(path: str, source: str) -> str | None:
|
|
|
774
782
|
}, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
|
|
775
783
|
|
|
776
784
|
|
|
777
|
-
def
|
|
785
|
+
def implementation_delivery_contract(node_type: str, task_input: dict, *, supported: bool, graph_type: str = "execution") -> dict | None:
|
|
786
|
+
if supported is not True or not isinstance(node_type, str) or not isinstance(graph_type, str) or node_type not in {"design", "coding", "testing", "review", "fix"} or graph_type not in {"execution", "implementation", "fix"}:
|
|
787
|
+
return None
|
|
788
|
+
if task_input.get("verification_work_item") or task_input.get("execution_actor") == "human":
|
|
789
|
+
return None
|
|
790
|
+
recovery = task_input.get("verification_recovery")
|
|
791
|
+
if isinstance(recovery, dict) and recovery.get("mode") == "review_only" and not task_input.get("implementation_delivery"):
|
|
792
|
+
return None
|
|
793
|
+
baseline = task_input.get("approved_plan_baseline")
|
|
794
|
+
if not isinstance(baseline, dict) or assess_approved_plan_conformance(baseline, None).get("reason") == "approved_plan_baseline_invalid":
|
|
795
|
+
return None
|
|
796
|
+
task_id = (baseline.get("task") or {}).get("id")
|
|
797
|
+
if not isinstance(task_id, str) or not task_id:
|
|
798
|
+
return None
|
|
799
|
+
contract = {"version": 1, "mode": "execution_and_artifacts", "node_type": node_type, "graph_type": graph_type,
|
|
800
|
+
"task_id": task_id, "baseline_fingerprint": baseline.get("fingerprint"),
|
|
801
|
+
"qa_policy": task_input.get("qa_test_level_policy_snapshot"), "change_scope": task_input.get("change_scope")}
|
|
802
|
+
contract["fingerprint"] = hashlib.sha256(json.dumps(contract, sort_keys=True, ensure_ascii=True, separators=(",", ":")).encode()).hexdigest()
|
|
803
|
+
return contract
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def uses_controlled_delivery(task_input: dict) -> bool:
|
|
807
|
+
contract = task_input.get("implementation_delivery")
|
|
808
|
+
if not isinstance(contract, dict):
|
|
809
|
+
return False
|
|
810
|
+
expected = implementation_delivery_contract(contract.get("node_type"), task_input, supported=True, graph_type=contract.get("graph_type"))
|
|
811
|
+
return expected is not None and contract == expected
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def assess_execution_delivery(contract: dict, report: object) -> dict:
|
|
815
|
+
if not isinstance(report, dict) or type(report.get("version")) is not int or report["version"] != 1:
|
|
816
|
+
return {"report_valid": False, "status": "unknown", "issues": ["Execution delivery declaration is missing or invalid"]}
|
|
817
|
+
if set(report) != {"version", "contract_fingerprint", "status"} or report.get("contract_fingerprint") != contract.get("fingerprint") or not isinstance(report.get("status"), str) or report["status"] not in {"complete", "partial", "blocked"}:
|
|
818
|
+
return {"report_valid": False, "status": "unknown", "issues": ["Execution delivery declaration does not match the current contract"]}
|
|
819
|
+
return {"report_valid": True, "status": report["status"], "issues": []}
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def redact_execution_delivery(report: object) -> dict | None:
|
|
823
|
+
if not isinstance(report, dict):
|
|
824
|
+
return None
|
|
825
|
+
return {"version": report.get("version") if type(report.get("version")) is int else 0,
|
|
826
|
+
"contract_fingerprint": report.get("contract_fingerprint") if isinstance(report.get("contract_fingerprint"), str) and re.fullmatch(r"[0-9a-f]{64}", report["contract_fingerprint"]) else "",
|
|
827
|
+
"status": report.get("status") if report.get("status") in ("complete", "partial", "blocked") else "unknown"}
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def task_review_policy(node_type: str, task_input: dict, *, changed_files: list[str] | None = None, policy_supported: bool = True) -> dict:
|
|
778
831
|
baseline = task_input.get("approved_plan_baseline")
|
|
779
832
|
reasons = []
|
|
833
|
+
if policy_supported is not True:
|
|
834
|
+
reasons.append("runtime_policy_upgrade_required")
|
|
780
835
|
fingerprint = None
|
|
781
836
|
if isinstance(baseline, dict):
|
|
782
837
|
expected = {key: value for key, value in baseline.items() if key != "fingerprint"}
|
|
@@ -808,6 +863,8 @@ def task_review_policy(node_type: str, task_input: dict, *, changed_files: list[
|
|
|
808
863
|
if node_type in {"coding", "fix"} and files and all(path.startswith("docs/") for path in files):
|
|
809
864
|
reasons.append("scope_unknown")
|
|
810
865
|
decision = {"version": 1, "mode": "required" if reasons else "advisory", "reasons": sorted(set(reasons))}
|
|
866
|
+
if uses_controlled_delivery(task_input):
|
|
867
|
+
decision.update(mode="advisory", decision_scope="controlled_delivery", delivery_contract=task_input["implementation_delivery"]["fingerprint"])
|
|
811
868
|
decision["fingerprint"] = hashlib.sha256(json.dumps({
|
|
812
869
|
**decision, "baseline_fingerprint": fingerprint,
|
|
813
870
|
"qa_policy": str(task_input.get("qa_test_level_policy_fingerprint") or ""),
|
|
@@ -817,6 +874,8 @@ def task_review_policy(node_type: str, task_input: dict, *, changed_files: list[
|
|
|
817
874
|
|
|
818
875
|
|
|
819
876
|
def review_blocks_completion(policy: dict, assessment: dict) -> bool:
|
|
877
|
+
if policy.get("decision_scope") == "controlled_delivery":
|
|
878
|
+
return False
|
|
820
879
|
return not assessment.get("node_complete", assessment.get("status") == "conformant") and (
|
|
821
880
|
policy.get("mode") != "advisory" or assessment.get("status") == "deviation"
|
|
822
881
|
)
|
|
@@ -2166,6 +2225,7 @@ def _extract_output_signals(text: str, usage_source: object = None) -> dict[str,
|
|
|
2166
2225
|
contains words like 'rate limit', 'connection error', 'API error', etc.
|
|
2167
2226
|
(e.g. implementing a rate-limiter feature, or documenting error codes).
|
|
2168
2227
|
"""
|
|
2228
|
+
terminal_status = None
|
|
2169
2229
|
has_turn_completed = False
|
|
2170
2230
|
has_turn_failed = False
|
|
2171
2231
|
has_result = False
|
|
@@ -2191,10 +2251,12 @@ def _extract_output_signals(text: str, usage_source: object = None) -> dict[str,
|
|
|
2191
2251
|
ev_type = str(data.get("type", ""))
|
|
2192
2252
|
|
|
2193
2253
|
if ev_type == "turn.completed":
|
|
2254
|
+
terminal_status = "success"
|
|
2194
2255
|
has_turn_completed = True
|
|
2195
2256
|
has_turn_failed = False
|
|
2196
2257
|
error_messages.clear()
|
|
2197
2258
|
elif ev_type == "turn.failed":
|
|
2259
|
+
terminal_status = "failed"
|
|
2198
2260
|
has_turn_failed = True
|
|
2199
2261
|
err = data.get("error") or {}
|
|
2200
2262
|
if isinstance(err, dict):
|
|
@@ -2227,19 +2289,23 @@ def _extract_output_signals(text: str, usage_source: object = None) -> dict[str,
|
|
|
2227
2289
|
if type(raw_exit_code) is not int and not (
|
|
2228
2290
|
isinstance(raw_exit_code, str) and re.fullmatch(r"[+-]?[0-9]+", raw_exit_code)
|
|
2229
2291
|
):
|
|
2292
|
+
terminal_status = "failed"
|
|
2230
2293
|
error_messages.append("Agent result has an invalid exitCode")
|
|
2231
2294
|
continue
|
|
2232
2295
|
exit_code = int(raw_exit_code)
|
|
2233
2296
|
if exit_code == 0:
|
|
2297
|
+
terminal_status = "success"
|
|
2234
2298
|
has_result = True
|
|
2235
2299
|
has_meaningful_content = True
|
|
2236
2300
|
has_turn_failed = False
|
|
2237
2301
|
error_messages.clear()
|
|
2238
2302
|
else:
|
|
2303
|
+
terminal_status = "failed"
|
|
2239
2304
|
error_messages.append(f"Copilot exited with code {exit_code}")
|
|
2240
2305
|
else:
|
|
2241
2306
|
result_text = str(data.get("result", "") or "")
|
|
2242
2307
|
if data.get("is_error"):
|
|
2308
|
+
terminal_status = "failed"
|
|
2243
2309
|
raw_errors = data.get("errors")
|
|
2244
2310
|
if isinstance(raw_errors, list):
|
|
2245
2311
|
err_text = "; ".join(
|
|
@@ -2267,8 +2333,10 @@ def _extract_output_signals(text: str, usage_source: object = None) -> dict[str,
|
|
|
2267
2333
|
# rather than the agent's actual work output.
|
|
2268
2334
|
no_api_call = not has_agent_token_usage(data, usage_source) and not has_assistant_events
|
|
2269
2335
|
if no_api_call and result_text:
|
|
2336
|
+
terminal_status = "failed"
|
|
2270
2337
|
error_messages.append(result_text)
|
|
2271
2338
|
else:
|
|
2339
|
+
terminal_status = "success"
|
|
2272
2340
|
has_result = True
|
|
2273
2341
|
has_meaningful_content = True
|
|
2274
2342
|
has_turn_failed = False
|
|
@@ -2312,6 +2380,7 @@ def _extract_output_signals(text: str, usage_source: object = None) -> dict[str,
|
|
|
2312
2380
|
has_meaningful_content = True
|
|
2313
2381
|
|
|
2314
2382
|
return {
|
|
2383
|
+
"terminal_status": "failed" if error_messages or has_turn_failed else terminal_status,
|
|
2315
2384
|
"has_turn_completed": has_turn_completed,
|
|
2316
2385
|
"has_turn_failed": has_turn_failed,
|
|
2317
2386
|
"has_result": has_result,
|
|
@@ -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.86.
|
|
1237
|
+
DAEMON_VERSION = "1.86.3"
|
|
1238
1238
|
|
|
1239
1239
|
|
|
1240
1240
|
def _detect_client_type() -> str:
|
|
@@ -6502,6 +6502,8 @@ class ProcessManager:
|
|
|
6502
6502
|
signals = agent_core._extract_output_signals(
|
|
6503
6503
|
"\n".join(part for part in (stdout, stderr) if part), result.metrics,
|
|
6504
6504
|
)
|
|
6505
|
+
if result.exit_code == 0 and not error and (signals.get("terminal_status") == "success" or result.has_verified_completion is True and signals.get("terminal_status") != "failed"):
|
|
6506
|
+
error_channels = ""
|
|
6505
6507
|
if not has_token_usage and ProcessManager._should_scan_short_success_stdout(stdout, signals):
|
|
6506
6508
|
error_channels = "\n".join(filter(None, [error_channels, stdout.strip()]))
|
|
6507
6509
|
# Always include structured error messages parsed from JSONL stream events
|
|
@@ -10219,6 +10221,9 @@ class ServerConnection:
|
|
|
10219
10221
|
},
|
|
10220
10222
|
"implementation_audit": {"protocol_version": agent_core.IMPLEMENTATION_AUDIT_PROTOCOL_VERSION},
|
|
10221
10223
|
"task_delivery": {"protocol_version": 1},
|
|
10224
|
+
"verification_recovery": {"protocol_version": 1},
|
|
10225
|
+
"task_review_policy": {"protocol_version": 1},
|
|
10226
|
+
"implementation_delivery": {"protocol_version": 1},
|
|
10222
10227
|
"recording_suggestions": {"protocol_version": 1, "isolated_workspace": True},
|
|
10223
10228
|
"task_attachment_staging": {"protocol_version": 1, "runtime_proxy": True, "image_agents": ["claude"]},
|
|
10224
10229
|
},
|
|
@@ -10645,6 +10650,9 @@ class ServerConnection:
|
|
|
10645
10650
|
},
|
|
10646
10651
|
"implementation_audit": {"protocol_version": agent_core.IMPLEMENTATION_AUDIT_PROTOCOL_VERSION},
|
|
10647
10652
|
"task_delivery": {"protocol_version": 1},
|
|
10653
|
+
"verification_recovery": {"protocol_version": 1},
|
|
10654
|
+
"task_review_policy": {"protocol_version": 1},
|
|
10655
|
+
"implementation_delivery": {"protocol_version": 1},
|
|
10648
10656
|
"recording_suggestions": {"protocol_version": 1, "isolated_workspace": True},
|
|
10649
10657
|
"local_evidence": {"protocol_version": 1, "structured_summary": True},
|
|
10650
10658
|
"task_attachment_staging": {"protocol_version": 1, "runtime_proxy": True, "image_agents": ["claude"]},
|
|
@@ -11434,6 +11442,20 @@ class RuntimeDaemon:
|
|
|
11434
11442
|
if managed_policy is not _MANAGED_WSL_POLICY:
|
|
11435
11443
|
raise ValueError("wsl_runtime_entry_not_admitted")
|
|
11436
11444
|
self.managed_policy = managed_policy
|
|
11445
|
+
# Ops-tunable staging budget. Keep aligned with the server's
|
|
11446
|
+
# MAX_ANALYSIS_ATTACHMENT_COUNT: the server only admits analysis
|
|
11447
|
+
# selections within its own budget, so a lower value here would
|
|
11448
|
+
# reject tasks the server considered valid.
|
|
11449
|
+
try:
|
|
11450
|
+
self._TASK_ATTACHMENT_MAX_COUNT = max(
|
|
11451
|
+
1, int(os.environ.get("DAEMON_TASK_ATTACHMENT_MAX_COUNT", self._TASK_ATTACHMENT_MAX_COUNT))
|
|
11452
|
+
)
|
|
11453
|
+
except ValueError:
|
|
11454
|
+
logger.warning(
|
|
11455
|
+
"Ignoring invalid DAEMON_TASK_ATTACHMENT_MAX_COUNT=%r; using %d",
|
|
11456
|
+
os.environ.get("DAEMON_TASK_ATTACHMENT_MAX_COUNT"),
|
|
11457
|
+
self._TASK_ATTACHMENT_MAX_COUNT,
|
|
11458
|
+
)
|
|
11437
11459
|
# Compute stable hardware fingerprint from machine-id / MAC address.
|
|
11438
11460
|
# This allows the server to identify the same physical machine even when
|
|
11439
11461
|
# its IP address changes (e.g., DHCP reassignment, home vs. office).
|
|
@@ -13944,7 +13966,7 @@ class RuntimeDaemon:
|
|
|
13944
13966
|
|
|
13945
13967
|
review_policy = agent_core.task_review_policy(task.node_type, task.input_data or {}, changed_files=result.files_changed)
|
|
13946
13968
|
result.metrics["task_review_policy"] = review_policy
|
|
13947
|
-
if result.status == "success" and review_policy["mode"] == "required" and
|
|
13969
|
+
if result.status == "success" and (task.critic_review_required or review_policy["mode"] == "required" and isinstance((task.input_data or {}).get("approved_plan_baseline"), dict)):
|
|
13948
13970
|
await self._collect_task_delivery_git(
|
|
13949
13971
|
task, workspace_path, result, attempt_before_sha=node_before_sha,
|
|
13950
13972
|
)
|
|
@@ -14615,6 +14637,16 @@ class RuntimeDaemon:
|
|
|
14615
14637
|
issues: list[str] = []
|
|
14616
14638
|
node_type = task.node_type
|
|
14617
14639
|
input_data = task.input_data or {}
|
|
14640
|
+
controlled_delivery = agent_core.uses_controlled_delivery(input_data)
|
|
14641
|
+
if controlled_delivery:
|
|
14642
|
+
report = agent_core.extract_execution_delivery(_daemon_extract_agent_output(str(result.metrics.get("actual_agent") or getattr(task, "agent_type", "") or ""), result.stdout))
|
|
14643
|
+
delivery = agent_core.assess_execution_delivery(input_data["implementation_delivery"], report)
|
|
14644
|
+
result.metrics["execution_delivery"] = agent_core.redact_execution_delivery(report)
|
|
14645
|
+
result.metrics["execution_delivery_valid"] = delivery["report_valid"]
|
|
14646
|
+
if not delivery["report_valid"]:
|
|
14647
|
+
issues.extend(_OutputValidationIssue("validation_unavailable", message) for message in delivery["issues"])
|
|
14648
|
+
elif delivery["status"] != "complete":
|
|
14649
|
+
issues.append(_OutputValidationIssue("planned_task_incomplete", "Agent explicitly reported incomplete execution delivery"))
|
|
14618
14650
|
fix_context = input_data.get("fix_context")
|
|
14619
14651
|
if node_type == "testing" or (
|
|
14620
14652
|
node_type == "fix" and input_data.get("task") == "phase_remediation_fix"
|
|
@@ -14647,18 +14679,18 @@ class RuntimeDaemon:
|
|
|
14647
14679
|
result.observations.append({"type": "implementation_audit", "data": audit_report})
|
|
14648
14680
|
audit_result = agent_core.assess_implementation_audit(audit_contract, audit_report)
|
|
14649
14681
|
failure_domain = agent_core.implementation_audit_failure_domain(audit_contract, audit_result)
|
|
14650
|
-
if failure_domain is not None:
|
|
14682
|
+
if failure_domain is not None and not controlled_delivery:
|
|
14651
14683
|
result.metrics["failure_domain"] = failure_domain
|
|
14652
14684
|
result.metrics["implementation_audit_node_complete"] = bool(
|
|
14653
|
-
audit_result["report_valid"] and audit_result["node_complete"]
|
|
14685
|
+
controlled_delivery or audit_result["report_valid"] and audit_result["node_complete"]
|
|
14654
14686
|
)
|
|
14655
14687
|
result.metrics["implementation_audit_subject_blocked"] = bool(
|
|
14656
|
-
audit_result["report_valid"] and not audit_result["node_complete"]
|
|
14688
|
+
not controlled_delivery and audit_result["report_valid"] and not audit_result["node_complete"]
|
|
14657
14689
|
and (failure_domain == "environment" or any(
|
|
14658
14690
|
finding.get("task_id") == audit_contract.get("subject_task_id") and finding.get("status") == "blocked"
|
|
14659
14691
|
for finding in audit_result.get("findings") or []))
|
|
14660
14692
|
)
|
|
14661
|
-
if audit_result["report_valid"] and audit_result["status"] == "plan_revision_required":
|
|
14693
|
+
if not controlled_delivery and audit_result["report_valid"] and audit_result["status"] == "plan_revision_required":
|
|
14662
14694
|
result.status = "failed"
|
|
14663
14695
|
result.failure_code = "implementation_plan_revision_required"
|
|
14664
14696
|
requests = audit_result.get("blocking_revision_requests", audit_result["revision_requests"])
|
|
@@ -14666,7 +14698,7 @@ class RuntimeDaemon:
|
|
|
14666
14698
|
f"{request['task_id']}: {request['reason']}" for request in requests[:3]
|
|
14667
14699
|
)
|
|
14668
14700
|
issues.append(_OutputValidationIssue(result.failure_code, result.error))
|
|
14669
|
-
elif not audit_result["report_valid"] or not audit_result["node_complete"]:
|
|
14701
|
+
elif not controlled_delivery and (not audit_result["report_valid"] or not audit_result["node_complete"]):
|
|
14670
14702
|
issues.extend(_OutputValidationIssue("implementation_audit_incomplete", issue) for issue in (
|
|
14671
14703
|
audit_result["issues"] or audit_result.get("blocking_reasons") or [
|
|
14672
14704
|
"The selected approved task still has implementation gaps",
|
|
@@ -15207,7 +15239,9 @@ class RuntimeDaemon:
|
|
|
15207
15239
|
ac for ac in ac_list
|
|
15208
15240
|
if ac["status"] != "covered" and ac["id"] not in pending
|
|
15209
15241
|
]
|
|
15210
|
-
if uncovered:
|
|
15242
|
+
if uncovered and controlled_delivery:
|
|
15243
|
+
result.metrics["business_acceptance"] = {"status": "pending_human_qa", "criteria": [ac["id"] for ac in uncovered]}
|
|
15244
|
+
elif uncovered:
|
|
15211
15245
|
ids = ", ".join(ac["id"] for ac in uncovered[:20])
|
|
15212
15246
|
if len(uncovered) > 20:
|
|
15213
15247
|
ids += f" (+{len(uncovered) - 20} more)"
|
|
@@ -16682,6 +16716,18 @@ class RuntimeDaemon:
|
|
|
16682
16716
|
*[f"[daemon] {issue}" for issue in issues],
|
|
16683
16717
|
])
|
|
16684
16718
|
|
|
16719
|
+
if (
|
|
16720
|
+
agent_core.uses_controlled_delivery(task.input_data or {})
|
|
16721
|
+
and result.metrics.get("execution_delivery_valid") is True
|
|
16722
|
+
and (result.metrics.get("execution_delivery") or {}).get("status") in {"partial", "blocked"}
|
|
16723
|
+
):
|
|
16724
|
+
result.status = "failed"
|
|
16725
|
+
result.failure_code = "planned_task_incomplete"
|
|
16726
|
+
result.error = "Agent explicitly reported incomplete execution delivery; preserve work and resolve prerequisites before an authorized retry"
|
|
16727
|
+
_record_output_validation_issues(result, issues)
|
|
16728
|
+
self._record_testing_telemetry(workspace_path, task, result, retries_used=attempt)
|
|
16729
|
+
return result
|
|
16730
|
+
|
|
16685
16731
|
if (
|
|
16686
16732
|
result.metrics.get("failure_domain") == "environment" and all(
|
|
16687
16733
|
issue.code in {"implementation_audit_incomplete", "acceptance_criteria_uncovered"} for issue in issues
|
|
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
|