forgexa-cli 1.65.0__tar.gz → 1.65.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.
Files changed (30) hide show
  1. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/PKG-INFO +1 -1
  2. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/daemon.py +97 -20
  4. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli.egg-info/PKG-INFO +1 -1
  5. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/pyproject.toml +1 -1
  6. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/README.md +0 -0
  7. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/_build_config.py +0 -0
  8. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/_local_bind.py +0 -0
  9. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/agent_core.py +0 -0
  10. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/autoupgrade.py +0 -0
  11. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/aws_evidence.py +0 -0
  12. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/main.py +0 -0
  13. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/py.typed +0 -0
  14. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/runtime_evidence.py +0 -0
  15. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli/zcode_protocol.py +0 -0
  16. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli.egg-info/SOURCES.txt +0 -0
  17. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  18. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli.egg-info/entry_points.txt +0 -0
  19. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli.egg-info/requires.txt +0 -0
  20. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/forgexa_cli.egg-info/top_level.txt +0 -0
  21. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/setup.cfg +0 -0
  22. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_auth_and_runtime_commands.py +0 -0
  23. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_autoupgrade.py +0 -0
  24. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_check_command.py +0 -0
  25. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_expiry_warnings_and_revoke.py +0 -0
  26. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_local_bind_commands.py +0 -0
  27. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_runtime_credentials.py +0 -0
  28. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_session_credentials.py +0 -0
  29. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_silent_install.py +0 -0
  30. {forgexa_cli-1.65.0 → forgexa_cli-1.65.2}/tests/test_upgrade_observability.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.65.0
3
+ Version: 1.65.2
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.65.0"
2
+ __version__ = "1.65.2"
@@ -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.65.0"
1237
+ DAEMON_VERSION = "1.65.2"
1238
1238
 
1239
1239
 
1240
1240
  def _detect_client_type() -> str:
@@ -8494,7 +8494,7 @@ class ProcessManager:
8494
8494
  conventional `main` / `master` refs. Falls back to HEAD~1 if merge-base
8495
8495
  detection fails.
8496
8496
  """
8497
- info: dict[str, Any] = {}
8497
+ info: dict[str, Any] = {"files_changed_verified": False}
8498
8498
  try:
8499
8499
  # Prefer the exact pre-node HEAD when available so the diff is
8500
8500
  # scoped to this node's work, not the whole requirement branch.
@@ -8531,8 +8531,11 @@ class ProcessManager:
8531
8531
  **_agent_process_group_kwargs(),
8532
8532
  )
8533
8533
  stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=10)
8534
+ if proc.returncode != 0:
8535
+ return info
8534
8536
  files = [f for f in stdout.decode().strip().split("\n") if f]
8535
8537
  info["files_changed"] = files
8538
+ info["files_changed_verified"] = True
8536
8539
 
8537
8540
  # Lines added/removed since branch point
8538
8541
  proc2 = await _create_subprocess_exec(
@@ -10533,6 +10536,25 @@ def _parse_frozen_qa_test_level_policy(
10533
10536
  }, []
10534
10537
 
10535
10538
 
10539
+ def _qa_source_test_validation_required(snapshot: object) -> bool:
10540
+ policy, issues = _parse_frozen_qa_test_level_policy(snapshot)
10541
+ return bool(issues or (
10542
+ policy is not None
10543
+ and policy["enforcement"] == "enforce"
10544
+ and any(not config["enabled"] for config in policy["levels"].values())
10545
+ ))
10546
+
10547
+
10548
+ def _mark_qa_source_validation_unavailable(result: TaskResult) -> None:
10549
+ result.status = "failed"
10550
+ result.failure_code = "validation_retry_exhausted"
10551
+ result.error = (
10552
+ "qa_policy.validation_unavailable: source-test output validation could not be completed; "
10553
+ "refusing to commit or push unvalidated changes."
10554
+ )
10555
+ result.metrics["validation_issues"] = [result.error]
10556
+
10557
+
10536
10558
  def _validate_enforced_qa_test_level_artifacts(
10537
10559
  policy: dict[str, Any],
10538
10560
  test_intent_data: object,
@@ -12799,19 +12821,18 @@ class RuntimeDaemon:
12799
12821
  if result.metrics.get("validation_issues") is not None or True:
12800
12822
  pre_commit_git = await self.process_manager._collect_git_info(workspace_path)
12801
12823
  except Exception:
12802
- logger.exception("Validation gate error for task %s (proceeding anyway)", task.task_id)
12824
+ if _qa_source_test_validation_required(
12825
+ (task.input_data or {}).get("qa_test_level_policy_snapshot")
12826
+ ):
12827
+ _mark_qa_source_validation_unavailable(result)
12828
+ logger.exception("Validation gate error for task %s (refusing to proceed)", task.task_id)
12829
+ else:
12830
+ logger.exception("Validation gate error for task %s (proceeding anyway)", task.task_id)
12803
12831
 
12804
12832
  if result.failure_code == "verification_work_item_scope_violation" and not is_local_bind:
12805
12833
  await self._discard_verification_scope_changes(
12806
12834
  workspace_path, task, result, node_before_sha,
12807
12835
  )
12808
- elif result.failure_code == "validation_retry_exhausted" and not is_local_bind and any(
12809
- str(issue).startswith("qa_policy.source_test_level_disabled")
12810
- for issue in (result.metrics.get("validation_issues") or [])
12811
- ):
12812
- await self._discard_qa_policy_source_test_changes(
12813
- workspace_path, task, result, node_before_sha,
12814
- )
12815
12836
 
12816
12837
  # 4.6 Post-validation rate-limit fallback.
12817
12838
  # _validate_and_retry returns early (preserving the rate-limit error)
@@ -13005,6 +13026,32 @@ class RuntimeDaemon:
13005
13026
  task.task_id, _aj_err,
13006
13027
  )
13007
13028
 
13029
+ if result.status == "success" and _qa_source_test_validation_required(
13030
+ (task.input_data or {}).get("qa_test_level_policy_snapshot")
13031
+ ):
13032
+ previous_validation_retries = result.metrics.get("validation_retries_used", 0)
13033
+ try:
13034
+ result = await self._validate_and_retry(
13035
+ agent, task, workspace_path, result, reporter, on_output_chunk,
13036
+ max_retries=0, default_branch=default_branch, before_sha=node_before_sha,
13037
+ )
13038
+ except Exception:
13039
+ _mark_qa_source_validation_unavailable(result)
13040
+ logger.exception("Final QA source-test validation error for task %s", task.task_id)
13041
+ result.metrics["validation_retries_used"] = previous_validation_retries
13042
+
13043
+ if result.failure_code == "validation_retry_exhausted" and not is_local_bind and any(
13044
+ str(issue).startswith((
13045
+ "qa_policy.source_test_level_disabled",
13046
+ "qa_policy.source_change_inventory_unavailable",
13047
+ "qa_policy.validation_unavailable",
13048
+ ))
13049
+ for issue in (result.metrics.get("validation_issues") or [])
13050
+ ):
13051
+ await self._discard_qa_policy_source_test_changes(
13052
+ workspace_path, task, result, node_before_sha,
13053
+ )
13054
+
13008
13055
  # 5. Auto-commit and push if changes exist
13009
13056
  if result.status == "success" and not is_local_bind:
13010
13057
  workspace_health_error = await self._workspace_checkout_health_error(
@@ -13468,7 +13515,7 @@ class RuntimeDaemon:
13468
13515
  input_data.get("qa_test_level_policy_snapshot")
13469
13516
  )
13470
13517
  issues.extend(qa_policy_issues)
13471
- if qa_policy is not None and node_type in ("coding", "fix", "testing"):
13518
+ if qa_policy is not None:
13472
13519
  issues.extend(_validate_enforced_qa_source_test_paths(
13473
13520
  qa_policy,
13474
13521
  result.files_changed,
@@ -15353,14 +15400,19 @@ class RuntimeDaemon:
15353
15400
 
15354
15401
  Returns the (possibly updated) TaskResult.
15355
15402
  """
15403
+ require_source_inventory = _qa_source_test_validation_required(
15404
+ (task.input_data or {}).get("qa_test_level_policy_snapshot")
15405
+ )
15356
15406
  for attempt in range(max_retries):
15357
- await self._refresh_validation_changed_paths(
15407
+ inventory_issues = await self._refresh_validation_changed_paths(
15358
15408
  workspace_path,
15359
15409
  result,
15360
15410
  default_branch=default_branch,
15361
15411
  before_sha=before_sha,
15362
15412
  )
15363
15413
  issues = self._validate_outputs(workspace_path, task, result)
15414
+ if require_source_inventory:
15415
+ issues.extend(inventory_issues)
15364
15416
  if not issues:
15365
15417
  self._record_testing_telemetry(
15366
15418
  workspace_path, task, result, retries_used=attempt
@@ -15475,13 +15527,15 @@ class RuntimeDaemon:
15475
15527
  return result
15476
15528
 
15477
15529
  # Final check after all retries
15478
- await self._refresh_validation_changed_paths(
15530
+ inventory_issues = await self._refresh_validation_changed_paths(
15479
15531
  workspace_path,
15480
15532
  result,
15481
15533
  default_branch=default_branch,
15482
15534
  before_sha=before_sha,
15483
15535
  )
15484
15536
  remaining = self._validate_outputs(workspace_path, task, result)
15537
+ if require_source_inventory:
15538
+ remaining.extend(inventory_issues)
15485
15539
  if remaining:
15486
15540
  if getattr(task, "workspace_resolution", None) == "local_bind":
15487
15541
  _append_local_bind_output_log(task.task_id, [
@@ -15572,11 +15626,12 @@ class RuntimeDaemon:
15572
15626
  *,
15573
15627
  default_branch: str,
15574
15628
  before_sha: str,
15575
- ) -> None:
15629
+ ) -> list[str]:
15576
15630
  """Merge reported, working-tree, and already-committed task paths."""
15577
15631
  reported_paths = ProcessManager._normalize_repo_paths(result.files_changed)
15578
15632
  paths: set[str] = set()
15579
15633
  collected_paths = False
15634
+ inventory_issues: list[str] = []
15580
15635
  try:
15581
15636
  status_output = await self.workspace_manager._git(
15582
15637
  "status",
@@ -15590,6 +15645,9 @@ class RuntimeDaemon:
15590
15645
  ))
15591
15646
  collected_paths = True
15592
15647
  except Exception:
15648
+ inventory_issues.append(
15649
+ "qa_policy.source_change_inventory_unavailable: working-tree changed paths could not be verified."
15650
+ )
15593
15651
  logger.debug(
15594
15652
  "Could not collect working-tree paths for validation", exc_info=True,
15595
15653
  )
@@ -15600,15 +15658,27 @@ class RuntimeDaemon:
15600
15658
  default_branch=default_branch,
15601
15659
  before_sha=before_sha,
15602
15660
  )
15661
+ if committed_git.get("files_changed_verified") is False:
15662
+ inventory_issues.append(
15663
+ "qa_policy.source_change_inventory_unavailable: committed changed paths could not be verified."
15664
+ )
15603
15665
  paths.update(ProcessManager._normalize_repo_paths(
15604
15666
  committed_git.get("files_changed") or []
15605
15667
  ))
15606
15668
  collected_paths = True
15607
15669
  except Exception:
15670
+ inventory_issues.append(
15671
+ "qa_policy.source_change_inventory_unavailable: committed changed paths could not be verified."
15672
+ )
15608
15673
  logger.debug(
15609
15674
  "Could not collect committed paths for validation", exc_info=True,
15610
15675
  )
15676
+ else:
15677
+ inventory_issues.append(
15678
+ "qa_policy.source_change_inventory_unavailable: the pre-node commit baseline is unavailable."
15679
+ )
15611
15680
  result.files_changed = sorted(paths if collected_paths else reported_paths)
15681
+ return inventory_issues
15612
15682
 
15613
15683
  def _record_testing_telemetry(
15614
15684
  self, workspace_path: Path, task: TaskInfo, result: TaskResult, retries_used: int
@@ -15980,7 +16050,7 @@ class RuntimeDaemon:
15980
16050
  before_sha: str,
15981
16051
  ) -> None:
15982
16052
  """Discard cloud-worktree changes that violate disabled test levels."""
15983
- if task.node_type not in ("coding", "fix", "testing") or not before_sha:
16053
+ if not before_sha or getattr(task, "workspace_resolution", None) == "local_bind":
15984
16054
  return
15985
16055
 
15986
16056
  policy, _ = _parse_frozen_qa_test_level_policy(
@@ -15997,18 +16067,25 @@ class RuntimeDaemon:
15997
16067
  and not policy["levels"][level]["enabled"]
15998
16068
  )
15999
16069
  )
16000
- if not forbidden_paths:
16070
+ unverified_changes = any(
16071
+ str(issue).startswith((
16072
+ "qa_policy.source_change_inventory_unavailable", "qa_policy.validation_unavailable",
16073
+ ))
16074
+ for issue in (result.metrics.get("validation_issues") or [])
16075
+ )
16076
+ if not forbidden_paths and not unverified_changes:
16001
16077
  return
16002
16078
 
16003
16079
  try:
16004
16080
  await self.workspace_manager._git(
16005
16081
  "reset", "--hard", before_sha, cwd=workspace_path,
16006
16082
  )
16007
- await self.workspace_manager._git(
16008
- "clean", "-f", "--", *forbidden_paths, cwd=workspace_path,
16009
- )
16083
+ if forbidden_paths:
16084
+ await self.workspace_manager._git(
16085
+ "clean", "-f", "--", *forbidden_paths, cwd=workspace_path,
16086
+ )
16010
16087
  logger.warning(
16011
- "Discarded disabled QA source-test changes for task %s: %s",
16088
+ "Rolled back task %s after QA source-test validation failure: %s",
16012
16089
  task.task_id,
16013
16090
  forbidden_paths,
16014
16091
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.65.0
3
+ Version: 1.65.2
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.65.0"
3
+ version = "1.65.2"
4
4
  description = "Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform"
5
5
  requires-python = ">=3.9"
6
6
  license = "MIT"
File without changes
File without changes