forgexa-cli 1.66.3__tar.gz → 1.67.0__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 (31) hide show
  1. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/PKG-INFO +1 -1
  2. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/aws_evidence.py +12 -0
  4. forgexa_cli-1.67.0/forgexa_cli/aws_logs_evidence.py +136 -0
  5. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/daemon.py +7 -5
  6. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli.egg-info/PKG-INFO +1 -1
  7. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli.egg-info/SOURCES.txt +1 -0
  8. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/pyproject.toml +1 -1
  9. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/README.md +0 -0
  10. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/_build_config.py +0 -0
  11. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/_local_bind.py +0 -0
  12. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/agent_core.py +0 -0
  13. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/autoupgrade.py +0 -0
  14. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/main.py +0 -0
  15. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/py.typed +0 -0
  16. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/runtime_evidence.py +0 -0
  17. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli/zcode_protocol.py +0 -0
  18. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  19. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
  20. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli.egg-info/requires.txt +0 -0
  21. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/forgexa_cli.egg-info/top_level.txt +0 -0
  22. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/setup.cfg +0 -0
  23. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_auth_and_runtime_commands.py +0 -0
  24. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_autoupgrade.py +0 -0
  25. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_check_command.py +0 -0
  26. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_expiry_warnings_and_revoke.py +0 -0
  27. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_local_bind_commands.py +0 -0
  28. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_runtime_credentials.py +0 -0
  29. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_session_credentials.py +0 -0
  30. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/tests/test_silent_install.py +0 -0
  31. {forgexa_cli-1.66.3 → forgexa_cli-1.67.0}/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.66.3
3
+ Version: 1.67.0
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.66.3"
2
+ __version__ = "1.67.0"
@@ -155,6 +155,12 @@ def model_destination(driver_id: str, environment: dict) -> dict:
155
155
 
156
156
 
157
157
  def validate_reference(reference: Any) -> dict:
158
+ if isinstance(reference, dict) and reference.get("evidence_type") == "aws_logs":
159
+ try:
160
+ from app import aws_logs_evidence
161
+ except ImportError:
162
+ import aws_logs_evidence
163
+ return aws_logs_evidence.validate_reference(reference)
158
164
  if not isinstance(reference, dict) or set(reference) != REFERENCE_FIELDS:
159
165
  raise EvidenceError("aws_evidence_invalid_reference")
160
166
  for field in ("workspace_id", "project_id", "user_id", "snapshot_id"):
@@ -206,6 +212,12 @@ def agent_transfer_allowed(policy: Any, model_provider: str | None = None) -> bo
206
212
 
207
213
 
208
214
  def validate_payload(reference: Any, metadata: Any, payload: Any, model_provider: str | None = None) -> dict:
215
+ if isinstance(reference, dict) and reference.get("evidence_type") == "aws_logs":
216
+ try:
217
+ from app import aws_logs_evidence
218
+ except ImportError:
219
+ import aws_logs_evidence
220
+ return aws_logs_evidence.validate_payload(reference, metadata, payload)
209
221
  reference = validate_reference(reference)
210
222
  if not isinstance(payload, str) or len(payload.encode("utf-8")) > PAYLOAD_MAX_BYTES:
211
223
  raise EvidenceError("aws_evidence_size_limit")
@@ -0,0 +1,136 @@
1
+ """Portable contracts for frozen CloudWatch log evidence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import re
8
+ from datetime import datetime, timezone
9
+ from uuid import UUID
10
+
11
+ LOGS_PROTOCOL = 1
12
+ PAYLOAD_LIMIT = 10 * 1024 * 1024
13
+ ATTACHMENT_LIMIT = 32 * 1024
14
+ REFERENCE_FIELDS = {"issuer", "workspace_id", "project_id", "user_id", "snapshot_id",
15
+ "payload_sha256", "summary_sha256", "evidence_type", "attachment_id"}
16
+
17
+
18
+ def canonical(value):
19
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True, allow_nan=False)
20
+
21
+
22
+ def digest(value):
23
+ return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
24
+
25
+
26
+ def overview(evidence):
27
+ return {key: value for key, value in evidence.items() if key != "events"}
28
+
29
+
30
+ def select_attachment(evidence, event_ids, ranges, attachment_id):
31
+ available = {event["event_ref"]: event for event in evidence["events"]}
32
+ if len(event_ids) != len(set(event_ids)) or set(event_ids) - available.keys() or set(ranges) - set(event_ids):
33
+ raise ValueError("aws_logs_selection_invalid")
34
+ if not event_ids and evidence["events"]:
35
+ raise ValueError("aws_logs_selection_required")
36
+ selected = []
37
+ for event_id in event_ids:
38
+ event = dict(available[event_id])
39
+ if event_id in ranges:
40
+ start, end = ranges[event_id]
41
+ if not isinstance(start, int) or not isinstance(end, int) or not 0 <= start < end <= len(event["message"]):
42
+ raise ValueError("aws_logs_selection_invalid")
43
+ event["message"] = event["message"][start:end]
44
+ event["selection_range"] = [start, end]
45
+ event["selection_truncated"] = True
46
+ selected.append(event)
47
+ packet = {**evidence, "attachment_id": attachment_id, "parent_payload_sha256": digest(evidence), "events": selected,
48
+ "attachment_selection": {"returned_events": len(evidence["events"]), "selected_events": len(selected),
49
+ "selection_truncated": len(selected) < len(evidence["events"]) or bool(ranges)}}
50
+ if len(canonical(packet).encode("utf-8")) > ATTACHMENT_LIMIT:
51
+ raise ValueError("aws_logs_attachment_budget")
52
+ return packet
53
+
54
+
55
+ def validate_reference(reference):
56
+ if not isinstance(reference, dict) or set(reference) != REFERENCE_FIELDS or reference.get("evidence_type") != "aws_logs":
57
+ raise ValueError("aws_evidence_invalid_reference")
58
+ for field in ("workspace_id", "project_id", "user_id", "snapshot_id", "attachment_id"):
59
+ value = reference.get(field)
60
+ try:
61
+ if not isinstance(value, str) or str(UUID(value)) != value:
62
+ raise ValueError
63
+ except (ValueError, TypeError):
64
+ raise ValueError("aws_evidence_invalid_reference") from None
65
+ for field in ("payload_sha256", "summary_sha256"):
66
+ if not isinstance(reference[field], str) or not re.fullmatch(r"[a-f0-9]{64}", reference[field]):
67
+ raise ValueError("aws_evidence_invalid_reference")
68
+ if not isinstance(reference["issuer"], str) or not reference["issuer"].startswith(("https://", "http://")) or len(reference["issuer"]) > 2048:
69
+ raise ValueError("aws_evidence_invalid_reference")
70
+ return dict(reference)
71
+
72
+
73
+ def validate_payload(reference, metadata, payload):
74
+ reference = validate_reference(reference)
75
+ if not isinstance(payload, str) or len(payload.encode("utf-8")) > ATTACHMENT_LIMIT:
76
+ raise ValueError("aws_logs_attachment_budget")
77
+ if hashlib.sha256(payload.encode("utf-8")).hexdigest() != reference["payload_sha256"]:
78
+ raise ValueError("aws_evidence_integrity_failed")
79
+ if not isinstance(metadata, dict) or metadata.get("aws_logs_evidence") != LOGS_PROTOCOL or metadata.get("status") not in {"ready", "partial"}:
80
+ raise ValueError("aws_evidence_protocol_unsupported")
81
+ if any(metadata.get(field) != reference[field] for field in REFERENCE_FIELDS - {"issuer"}):
82
+ raise ValueError("aws_evidence_identity_mismatch")
83
+ policy = metadata.get("data_policy", {})
84
+ if policy.get("approval_mode") != "user_consent" or policy.get("allow_agent_transfer") is not True or policy.get("allow_logs_agent_transfer") is not True or type(policy.get("policy_version")) is not int or policy["policy_version"] < 1:
85
+ raise ValueError("aws_evidence_policy_denied")
86
+ try:
87
+ expiration = datetime.fromisoformat(metadata["expires_at"].replace("Z", "+00:00"))
88
+ if expiration.tzinfo is None or expiration <= datetime.now(timezone.utc):
89
+ raise ValueError
90
+ except (ValueError, TypeError, KeyError):
91
+ raise ValueError("aws_evidence_expired") from None
92
+
93
+ def object_pairs(pairs):
94
+ result = {}
95
+ for key, value in pairs:
96
+ if key in result:
97
+ raise ValueError("aws_evidence_invalid_payload")
98
+ result[key] = value
99
+ return result
100
+
101
+ try:
102
+ evidence = json.loads(payload, object_pairs_hook=object_pairs)
103
+ except (ValueError, TypeError, RecursionError):
104
+ raise ValueError("aws_evidence_invalid_payload") from None
105
+ if not isinstance(evidence, dict) or evidence.get("schema_version") != 1 or evidence.get("provider") != "aws_logs":
106
+ raise ValueError("aws_evidence_invalid_payload")
107
+ if evidence.get("snapshot_id") != reference["snapshot_id"] or evidence.get("attachment_id") != reference["attachment_id"]:
108
+ raise ValueError("aws_evidence_identity_mismatch")
109
+ origin = evidence.get("origin", {})
110
+ for field, key in (("issuer", "issuer"), ("workspace_id", "workspace_id"), ("project_id", "project_id"), ("user_id", "created_by_user_id")):
111
+ if origin.get(key) != reference[field]:
112
+ raise ValueError("aws_evidence_identity_mismatch")
113
+ sources = evidence.get("sources")
114
+ events = evidence.get("events")
115
+ if not isinstance(sources, list) or not 1 <= len(sources) <= 4 or not isinstance(events, list) or len(events) > 1000:
116
+ raise ValueError("aws_evidence_size_limit")
117
+ identities = set()
118
+ for source in sources:
119
+ if not isinstance(source, dict) or source.get("source_ref") in identities or not re.fullmatch(r"AWS-L1-S\d+", str(source.get("source_ref", ""))):
120
+ raise ValueError("aws_logs_source_mismatch")
121
+ if not re.fullmatch(r"\d{12}", str(source.get("account_id", ""))) or source.get("partition") not in {"aws", "aws-cn", "aws-us-gov"}:
122
+ raise ValueError("aws_logs_source_mismatch")
123
+ identities.add(source["source_ref"])
124
+ references = set()
125
+ for event in events:
126
+ if not isinstance(event, dict) or event.get("source_ref") not in identities or event.get("event_ref") in references or not re.fullmatch(r"AWS-L1-E\d+", str(event.get("event_ref", ""))):
127
+ raise ValueError("aws_logs_source_mismatch")
128
+ if not isinstance(event.get("message"), str) or not isinstance(event.get("timestamp"), str) or any(key.startswith("_") for key in event):
129
+ raise ValueError("aws_evidence_invalid_payload")
130
+ references.add(event["event_ref"])
131
+ summary = canonical(evidence)
132
+ if hashlib.sha256(summary.encode("utf-8")).hexdigest() != reference["summary_sha256"]:
133
+ raise ValueError("aws_evidence_integrity_failed")
134
+ return {"reference": reference, "summary": evidence, "summary_json": summary,
135
+ "data_policy": dict(policy), "expires_at": metadata["expires_at"], "model_provider": None,
136
+ "data_category": "aws_logs"}
@@ -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.66.3"
1237
+ DAEMON_VERSION = "1.67.0"
1238
1238
 
1239
1239
 
1240
1240
  def _detect_client_type() -> str:
@@ -9113,8 +9113,11 @@ class HeartbeatService:
9113
9113
  self._agents = [dict(agent) for agent in agents]
9114
9114
 
9115
9115
  async def _sync_local_workspace_bindings(self) -> None:
9116
- if _MANAGED_WSL_POLICY is not None and _MANAGED_WSL_POLICY.task is not None:
9117
- return # The task already retains mapping admission and the physical lock.
9116
+ if _MANAGED_WSL_POLICY is not None:
9117
+ if _MANAGED_WSL_POLICY.task is None:
9118
+ await asyncio.wait_for(_MANAGED_WSL_POLICY.refresh_binding(self),
9119
+ timeout=_LOCAL_BINDING_VERIFICATION_TIMEOUT_SECONDS)
9120
+ return # One exact Linux binding; a task already holds its admission/physical locks.
9118
9121
  try:
9119
9122
  response = await asyncio.wait_for(self.client.get(
9120
9123
  f"{self.server_url}/api/v1/runtimes/{self.runtime_id}/local-workspace-bindings/verification",
@@ -9137,8 +9140,7 @@ class HeartbeatService:
9137
9140
  (binding for binding in bindings if isinstance(binding, dict) and binding.get("id")),
9138
9141
  key=lambda binding: binding.get("status") != "pending",
9139
9142
  )
9140
- limit = 1 if _MANAGED_WSL_POLICY is not None else _LOCAL_BINDING_VERIFICATION_CONCURRENCY
9141
- semaphore = asyncio.Semaphore(limit)
9143
+ semaphore = asyncio.Semaphore(_LOCAL_BINDING_VERIFICATION_CONCURRENCY)
9142
9144
 
9143
9145
  async def verify_one(binding):
9144
9146
  async with semaphore:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.66.3
3
+ Version: 1.67.0
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
@@ -6,6 +6,7 @@ forgexa_cli/_local_bind.py
6
6
  forgexa_cli/agent_core.py
7
7
  forgexa_cli/autoupgrade.py
8
8
  forgexa_cli/aws_evidence.py
9
+ forgexa_cli/aws_logs_evidence.py
9
10
  forgexa_cli/daemon.py
10
11
  forgexa_cli/main.py
11
12
  forgexa_cli/py.typed
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.66.3"
3
+ version = "1.67.0"
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