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.
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/PKG-INFO +1 -1
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/agent_core.py +118 -2
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/daemon.py +20 -4
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/pyproject.toml +1 -1
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/README.md +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/aws_evidence.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/aws_logs_evidence.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/observability_contract.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/observability_evidence.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/setup.cfg +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_publish_script.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.85.2 → forgexa_cli-1.85.3}/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.3"
|
|
@@ -79,10 +79,114 @@ LOCAL_BIND_PRIVATE_VALIDATION_METRIC_KEYS = frozenset({
|
|
|
79
79
|
})
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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."
|
|
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
|