forgexa-cli 1.85.2__tar.gz → 1.85.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.
Files changed (34) hide show
  1. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/PKG-INFO +1 -1
  2. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/agent_core.py +118 -2
  4. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/daemon.py +20 -4
  5. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
  6. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/pyproject.toml +1 -1
  7. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/README.md +0 -0
  8. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/_build_config.py +0 -0
  9. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/_local_bind.py +0 -0
  10. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/autoupgrade.py +0 -0
  11. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/aws_evidence.py +0 -0
  12. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/aws_logs_evidence.py +0 -0
  13. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/main.py +0 -0
  14. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/observability_contract.py +0 -0
  15. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/observability_evidence.py +0 -0
  16. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/py.typed +0 -0
  17. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/runtime_evidence.py +0 -0
  18. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/zcode_protocol.py +0 -0
  19. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
  20. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  21. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
  22. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/requires.txt +0 -0
  23. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/top_level.txt +0 -0
  24. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/setup.cfg +0 -0
  25. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_auth_and_runtime_commands.py +0 -0
  26. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_autoupgrade.py +0 -0
  27. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_check_command.py +0 -0
  28. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_expiry_warnings_and_revoke.py +0 -0
  29. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_local_bind_commands.py +0 -0
  30. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_publish_script.py +0 -0
  31. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_runtime_credentials.py +0 -0
  32. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_session_credentials.py +0 -0
  33. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_silent_install.py +0 -0
  34. {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/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.85.2
3
+ Version: 1.85.3
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.85.2"
2
+ __version__ = "1.85.3"
@@ -79,10 +79,114 @@ LOCAL_BIND_PRIVATE_VALIDATION_METRIC_KEYS = frozenset({
79
79
  })
80
80
 
81
81
 
82
- def assess_approved_plan_conformance(baseline: dict, report: object) -> dict:
82
+ def verification_obligation_fingerprint(task: dict) -> str:
83
+ task_input = task.get("input") if isinstance(task.get("input"), dict) else {}
84
+ responsibility = {
85
+ "id": task.get("id"), "type": task.get("type"), "description": task.get("description") or "",
86
+ "items": task.get("items") or [], "depends_on": task.get("depends_on") or [],
87
+ "files": task_input.get("files") or task.get("files") or [],
88
+ }
89
+ return hashlib.sha256(json.dumps(responsibility, sort_keys=True, ensure_ascii=True, separators=(",", ":")).encode()).hexdigest()
90
+
91
+
92
+ def approved_contribution_review_instructions(baseline: dict, audit_contract: dict | None) -> str:
93
+ task = baseline.get("task") or {}
94
+ obligations = baseline.get("verification_obligations") or {}
95
+ if (
96
+ not isinstance(audit_contract, dict) or audit_contract.get("version") != 2
97
+ or audit_contract.get("plan_id") != baseline.get("plan_id") or audit_contract.get("subject_task_id") != task.get("id")
98
+ or audit_contract.get("final_verification") is not False or task.get("type") not in ("coding", "fix")
99
+ or not isinstance(obligations, dict) or type(obligations.get("version")) is not int or obligations["version"] != 1
100
+ ):
101
+ return ""
102
+ verifiers = [check for check in obligations.get("checks") or [] if isinstance(check, dict)
103
+ and check.get("type") in ("testing", "verify") and check.get("task_id") != task["id"]]
104
+ if not verifiers:
105
+ return ""
106
+ example = {"contribution_check": {"check_id": str(task["id"]) + ":assigned", "status": "unverified",
107
+ "evidence": ["<independent source and every assigned check evidence>"]},
108
+ "pending_checks": [{"check_id": verifiers[0]["check_id"], "task_id": verifiers[0]["task_id"],
109
+ "status": "unverified", "evidence": ["<observed outstanding approved verification>"]}]}
110
+ return (
111
+ "\n## Assigned Contribution and Pending Verification\n"
112
+ "Within the bound plan_conformance object, contribution_check covers all requirements and mandatory checks of this task, "
113
+ "not only static source. When independently evidenced as conformant, other approved verification owners may remain pending. "
114
+ "Use the exact frozen check_id/task_id pairs; this does not authorize moving an own check to another owner, "
115
+ "inventing an owner, waiving a check or changing its phase. Keep the overall status unverified while pending_checks remain. "
116
+ "The score and verdict assess the assigned contribution, not unfinished work assigned elsewhere. "
117
+ "A real own gap stays deviation or unverified. The default example is not completion evidence.\n"
118
+ "Scoped contribution fields example:\n" + json.dumps(example, ensure_ascii=True, separators=(",", ":"))
119
+ )
120
+
121
+
122
+ def _assess_contribution_conformance(baseline: dict, report: dict, audit_contract: dict | None, result: dict) -> dict:
123
+ obligations = baseline.get("verification_obligations")
124
+ subject = result["task_id"]
125
+ if (
126
+ not isinstance(audit_contract, dict) or audit_contract.get("version") != 2
127
+ or audit_contract.get("plan_id") != baseline.get("plan_id")
128
+ or audit_contract.get("subject_task_id") != subject or audit_contract.get("final_verification") is not False
129
+ or (baseline.get("task") or {}).get("type") not in ("coding", "fix")
130
+ or not isinstance(obligations, dict) or type(obligations.get("version")) is not int or obligations["version"] != 1
131
+ ):
132
+ return result
133
+ checks = obligations.get("checks")
134
+ if not isinstance(checks, list) or any(not isinstance(check, dict) for check in checks):
135
+ return result
136
+ by_id = {check.get("check_id"): check for check in checks if isinstance(check.get("check_id"), str)}
137
+ own = by_id.get(str(subject) + ":assigned")
138
+ tasks = audit_contract.get("tasks")
139
+ if not isinstance(tasks, list) or any(not isinstance(task, dict) or not isinstance(task.get("id"), str) for task in tasks):
140
+ return result
141
+ approved = {task["id"]: task for task in tasks}
142
+ contribution = report.get("contribution_check")
143
+ pending = report.get("pending_checks")
144
+ if (
145
+ len(by_id) != len(checks) or not isinstance(own, dict) or own.get("task_id") != subject
146
+ or subject not in approved or own.get("task_fingerprint") != verification_obligation_fingerprint(approved[subject])
147
+ or own.get("task_fingerprint") != verification_obligation_fingerprint(baseline["task"])
148
+ or own.get("required") is not True or not isinstance(contribution, dict)
149
+ or contribution.get("check_id") != own["check_id"] or contribution.get("status") != "conformant"
150
+ or contribution.get("plan_revision") is not None
151
+ or not isinstance(pending, list) or not pending
152
+ ):
153
+ return result
154
+ evidence = contribution.get("evidence")
155
+ if not isinstance(evidence, list) or not evidence or any(not isinstance(item, str) or not item.strip() for item in evidence):
156
+ return result
157
+ normalized = []
158
+ seen = set()
159
+ for check in pending:
160
+ if not isinstance(check, dict) or not isinstance(check.get("check_id"), str):
161
+ return result
162
+ binding = by_id.get(check["check_id"])
163
+ references = check.get("evidence")
164
+ reason = check.get("blocking_reason")
165
+ if (
166
+ not isinstance(binding, dict) or check["check_id"] in seen or binding.get("required") is not True
167
+ or check.get("task_id") == subject or binding.get("task_id") != check.get("task_id")
168
+ or binding.get("type") not in ("testing", "verify") or approved.get(check.get("task_id"), {}).get("type") != binding.get("type")
169
+ or binding.get("task_fingerprint") != verification_obligation_fingerprint(approved.get(check.get("task_id"), {}))
170
+ or check.get("status") not in ("blocked", "unverified") or check.get("plan_revision") is not None
171
+ or not isinstance(references, list) or not references
172
+ or any(not isinstance(item, str) or not item.strip() for item in references)
173
+ or reason is not None and (not isinstance(reason, str) or reason not in IMPLEMENTATION_ENVIRONMENT_BLOCKERS)
174
+ ):
175
+ return result
176
+ seen.add(check["check_id"])
177
+ normalized.append({"check_id": check["check_id"], "task_id": check["task_id"], "status": check["status"],
178
+ "evidence_refs": [hashlib.sha256(item.encode()).hexdigest() for item in references],
179
+ **({"blocking_reason": reason} if reason is not None else {})})
180
+ result.update(node_complete=True, contribution_check={"check_id": own["check_id"], "status": "conformant",
181
+ "evidence_refs": [hashlib.sha256(item.encode()).hexdigest() for item in evidence]}, pending_checks=normalized,
182
+ verification_pending=sorted({check["task_id"] for check in normalized}))
183
+ return result
184
+
185
+
186
+ def assess_approved_plan_conformance(baseline: dict, report: object, *, audit_contract: dict | None = None) -> dict:
83
187
  result = {
84
188
  "status": "unverified", "baseline_fingerprint": baseline.get("fingerprint"),
85
- "task_id": (baseline.get("task") or {}).get("id"), "evidence_refs": [],
189
+ "task_id": (baseline.get("task") or {}).get("id"), "evidence_refs": [], "node_complete": False,
86
190
  }
87
191
  expected = {key: value for key, value in baseline.items() if key != "fingerprint"}
88
192
  fingerprint = hashlib.sha256(json.dumps(
@@ -114,8 +218,20 @@ def assess_approved_plan_conformance(baseline: dict, report: object) -> dict:
114
218
  result["reason"] = "approved_plan_review_evidence_missing"
115
219
  result["diagnostic_code"] = "approved_plan_review_evidence_missing"
116
220
  return result
221
+ contribution = report.get("contribution_check")
222
+ if report["status"] == "conformant" and (
223
+ bool(report.get("pending_checks"))
224
+ or "pending_checks" in report and not isinstance(report["pending_checks"], list)
225
+ or contribution is not None and (not isinstance(contribution, dict) or contribution.get("status") != "conformant")
226
+ ):
227
+ result["reason"] = "approved_plan_review_missing_or_mismatched"
228
+ result["diagnostic_code"] = "approved_plan_review_contribution_conflict"
229
+ return result
117
230
  result["status"] = report["status"]
118
231
  result["evidence_refs"] = [hashlib.sha256(item.encode()).hexdigest() for item in evidence]
232
+ result["node_complete"] = report["status"] == "conformant"
233
+ if report["status"] == "unverified" and report.get("contribution_check") is not None:
234
+ return _assess_contribution_conformance(baseline, report, audit_contract, result)
119
235
  return result
120
236
 
121
237
 
@@ -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.2"
1237
+ DAEMON_VERSION = "1.85.3"
1238
1238
 
1239
1239
 
1240
1240
  def _detect_client_type() -> str:
@@ -1809,6 +1809,7 @@ def _build_runtime_critic_prompt(
1809
1809
  "failed checks remain blocking. State the concrete missing evidence; never invent a run, "
1810
1810
  "treat asset existence as proof of correctness, or claim final QA or release acceptance."
1811
1811
  )
1812
+ parts.append(agent_core.approved_contribution_review_instructions(baseline, audit_contract))
1812
1813
  else:
1813
1814
  planned_context = {
1814
1815
  key: (task.input_data or {}).get(key)
@@ -14292,7 +14293,8 @@ class RuntimeDaemon:
14292
14293
  **metadata,
14293
14294
  }
14294
14295
  conformance = (
14295
- agent_core.assess_approved_plan_conformance(baseline, structured_result.get("plan_conformance"))
14296
+ agent_core.assess_approved_plan_conformance(baseline, structured_result.get("plan_conformance"),
14297
+ audit_contract=(task.input_data or {}).get("implementation_audit_contract"))
14296
14298
  if isinstance(baseline, dict) else None
14297
14299
  )
14298
14300
  if conformance is not None and conformance["status"] != "conformant":
@@ -14320,6 +14322,15 @@ class RuntimeDaemon:
14320
14322
  "evidence": conformance["evidence_refs"],
14321
14323
  },
14322
14324
  }
14325
+ if conformance.get("node_complete") and conformance["status"] == "unverified":
14326
+ contribution = conformance["contribution_check"]
14327
+ structured_result["plan_conformance"]["contribution_check"] = {
14328
+ "check_id": contribution["check_id"], "status": contribution["status"], "evidence": contribution["evidence_refs"],
14329
+ }
14330
+ structured_result["plan_conformance"]["pending_checks"] = [{
14331
+ "check_id": check["check_id"], "task_id": check["task_id"], "status": check["status"], "evidence": check["evidence_refs"],
14332
+ **({"blocking_reason": check["blocking_reason"]} if "blocking_reason" in check else {}),
14333
+ } for check in conformance["pending_checks"]]
14323
14334
  encoded_result = json.dumps(
14324
14335
  structured_result,
14325
14336
  ensure_ascii=True,
@@ -14512,6 +14523,7 @@ class RuntimeDaemon:
14512
14523
  delivered_files = list(result.files_changed)
14513
14524
  delivered_files.extend(input_data.get("planned_task_predecessor_files") or [])
14514
14525
  for missing_path in agent_core.missing_planned_task_files(input_data, delivered_files):
14526
+ result.metrics["failure_domain"] = "delivery"
14515
14527
  issues.append(_OutputValidationIssue(
14516
14528
  "planned_task_incomplete",
14517
14529
  f"Planned task file has no implementation evidence: {missing_path}. "
@@ -16516,7 +16528,10 @@ class RuntimeDaemon:
16516
16528
  ])
16517
16529
 
16518
16530
  if (
16519
- agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
16531
+ result.metrics.get("failure_domain") == "environment" and all(
16532
+ issue.code in {"implementation_audit_incomplete", "acceptance_criteria_uncovered"} for issue in issues
16533
+ )
16534
+ or agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
16520
16535
  and result.metrics.get("implementation_audit_subject_blocked") is True
16521
16536
  ):
16522
16537
  result.status = "failed"
@@ -16719,7 +16734,8 @@ class RuntimeDaemon:
16719
16734
  result.failure_code = "implementation_audit_incomplete"
16720
16735
  result.error = "; ".join(remaining[:3])
16721
16736
  if (
16722
- agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
16737
+ result.metrics.get("failure_domain") == "environment"
16738
+ or agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
16723
16739
  and result.metrics.get("implementation_audit_subject_blocked") is True
16724
16740
  ):
16725
16741
  result.error += ". Resolve the reported verification prerequisites before retrying."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.85.2
3
+ Version: 1.85.3
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.85.2"
3
+ version = "1.85.3"
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