forgexa-cli 1.66.3__tar.gz → 1.67.1__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.66.3 → forgexa_cli-1.67.1}/PKG-INFO +1 -1
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/aws_evidence.py +12 -0
- forgexa_cli-1.67.1/forgexa_cli/aws_logs_evidence.py +136 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/daemon.py +7 -5
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli.egg-info/SOURCES.txt +1 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/pyproject.toml +1 -1
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/README.md +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/agent_core.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/setup.cfg +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.66.3 → forgexa_cli-1.67.1}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.67.1"
|
|
@@ -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.
|
|
1237
|
+
DAEMON_VERSION = "1.67.1"
|
|
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
|
|
9117
|
-
|
|
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
|
-
|
|
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:
|
|
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
|