fix: remove print statements from library code (issue #23)
Some checks failed
AITBC CI/CD Pipeline / lint-and-test (3.11) (pull_request) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.12) (pull_request) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.13) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (apps/coordinator-api/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (cli/aitbc_cli) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-core/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-crypto/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-sdk/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (tests) (pull_request) Has been cancelled
Security Scanning / CodeQL Security Analysis (javascript) (pull_request) Has been cancelled
Security Scanning / CodeQL Security Analysis (python) (pull_request) Has been cancelled
Security Scanning / Dependency Security Scan (pull_request) Has been cancelled
Security Scanning / Container Security Scan (pull_request) Has been cancelled
Security Scanning / OSSF Scorecard (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-cli (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-services (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-production-services (pull_request) Has been cancelled
AITBC CI/CD Pipeline / security-scan (pull_request) Has been cancelled
AITBC CI/CD Pipeline / build (pull_request) Has been cancelled
AITBC CI/CD Pipeline / deploy-staging (pull_request) Has been cancelled
AITBC CI/CD Pipeline / deploy-production (pull_request) Has been cancelled
AITBC CI/CD Pipeline / performance-test (pull_request) Has been cancelled
AITBC CI/CD Pipeline / docs (pull_request) Has been cancelled
AITBC CI/CD Pipeline / release (pull_request) Has been cancelled
AITBC CI/CD Pipeline / notify (pull_request) Has been cancelled
Security Scanning / Security Summary Report (pull_request) Has been cancelled

- Added logging to aitbc-agent-sdk modules
- Replaced print with logger (info, error, debug)
- Fixed bare except clauses in agent.py and guardian_contract.py (issue #20 partial)
- Added qa-cycle.py (QA automation) and improved scripts
This commit is contained in:
2026-03-15 19:06:23 +00:00
parent 393c80532b
commit 0c071f5d89
5 changed files with 198 additions and 29 deletions

View File

@@ -157,7 +157,7 @@ class GuardianContract:
# Validate address # Validate address
try: try:
to_address = to_checksum_address(to_address) to_address = to_checksum_address(to_address)
except: except Exception:
return { return {
"status": "rejected", "status": "rejected",
"reason": "Invalid recipient address", "reason": "Invalid recipient address",

View File

@@ -4,6 +4,7 @@ Core Agent class for AITBC network participation
import asyncio import asyncio
import json import json
import logging
import uuid import uuid
from datetime import datetime from datetime import datetime
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
@@ -13,6 +14,8 @@ from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import padding
logger = logging.getLogger(__name__)
@dataclass @dataclass
class AgentCapabilities: class AgentCapabilities:
"""Agent capability specification""" """Agent capability specification"""
@@ -73,7 +76,7 @@ class AgentIdentity:
hashes.SHA256() hashes.SHA256()
) )
return True return True
except: except Exception:
return False return False
class Agent: class Agent:
@@ -152,11 +155,11 @@ class Agent:
await asyncio.sleep(1) # Simulate network call await asyncio.sleep(1) # Simulate network call
self.registered = True self.registered = True
print(f"Agent {self.identity.id} registered successfully") logger.info(f"Agent {self.identity.id} registered successfully")
return True return True
except Exception as e: except Exception as e:
print(f"Registration failed: {e}") logger.error(f"Registration failed: {e}")
return False return False
async def get_reputation(self) -> Dict[str, float]: async def get_reputation(self) -> Dict[str, float]:
@@ -172,7 +175,7 @@ class Agent:
async def update_reputation(self, new_score: float) -> None: async def update_reputation(self, new_score: float) -> None:
"""Update agent reputation score""" """Update agent reputation score"""
self.reputation_score = new_score self.reputation_score = new_score
print(f"Reputation updated to {new_score}") logger.info(f"Reputation updated to {new_score}")
async def get_earnings(self, period: str = "30d") -> Dict[str, Any]: async def get_earnings(self, period: str = "30d") -> Dict[str, Any]:
"""Get agent earnings information""" """Get agent earnings information"""
@@ -199,7 +202,7 @@ class Agent:
message["signature"] = signature message["signature"] = signature
# TODO: Send through AITBC agent messaging protocol # TODO: Send through AITBC agent messaging protocol
print(f"Message sent to {recipient_id}: {message_type}") logger.info(f"Message sent to {recipient_id}: {message_type}")
return True return True
async def receive_message(self, message: Dict[str, Any]) -> bool: async def receive_message(self, message: Dict[str, Any]) -> bool:
@@ -210,7 +213,7 @@ class Agent:
# TODO: Verify sender's signature # TODO: Verify sender's signature
# For now, just process the message # For now, just process the message
print(f"Received message from {message.get('from')}: {message.get('type')}") logger.info(f"Received message from {message.get('from')}: {message.get('type')}")
return True return True
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:

View File

@@ -3,11 +3,14 @@ Compute Provider Agent - for agents that provide computational resources
""" """
import asyncio import asyncio
import logging
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dataclasses import dataclass from dataclasses import dataclass
from .agent import Agent, AgentCapabilities from .agent import Agent, AgentCapabilities
logger = logging.getLogger(__name__)
@dataclass @dataclass
class ResourceOffer: class ResourceOffer:
"""Resource offering specification""" """Resource offering specification"""
@@ -66,11 +69,11 @@ class ComputeProvider(Agent):
await self._submit_to_marketplace(offer) await self._submit_to_marketplace(offer)
self.current_offers.append(offer) self.current_offers.append(offer)
print(f"Resource offer submitted: {price_per_hour} AITBC/hour") logger.info(f"Resource offer submitted: {price_per_hour} AITBC/hour")
return True return True
except Exception as e: except Exception as e:
print(f"Failed to offer resources: {e}") logger.error(f"Failed to offer resources: {e}")
return False return False
async def set_availability(self, schedule: Dict[str, Any]) -> bool: async def set_availability(self, schedule: Dict[str, Any]) -> bool:
@@ -81,11 +84,11 @@ class ComputeProvider(Agent):
offer.availability_schedule = schedule offer.availability_schedule = schedule
await self._update_marketplace_offer(offer) await self._update_marketplace_offer(offer)
print("Availability schedule updated") logger.info("Availability schedule updated")
return True return True
except Exception as e: except Exception as e:
print(f"Failed to update availability: {e}") logger.error(f"Failed to update availability: {e}")
return False return False
async def enable_dynamic_pricing(self, base_rate: float, demand_threshold: float = 0.8, max_multiplier: float = 2.0, adjustment_frequency: str = "15min") -> bool: async def enable_dynamic_pricing(self, base_rate: float, demand_threshold: float = 0.8, max_multiplier: float = 2.0, adjustment_frequency: str = "15min") -> bool:
@@ -102,11 +105,11 @@ class ComputeProvider(Agent):
# Start dynamic pricing task # Start dynamic pricing task
asyncio.create_task(self._dynamic_pricing_loop()) asyncio.create_task(self._dynamic_pricing_loop())
print("Dynamic pricing enabled") logger.info("Dynamic pricing enabled")
return True return True
except Exception as e: except Exception as e:
print(f"Failed to enable dynamic pricing: {e}") logger.error(f"Failed to enable dynamic pricing: {e}")
return False return False
async def _dynamic_pricing_loop(self): async def _dynamic_pricing_loop(self):
@@ -134,10 +137,10 @@ class ComputeProvider(Agent):
offer.price_per_hour = new_price offer.price_per_hour = new_price
await self._update_marketplace_offer(offer) await self._update_marketplace_offer(offer)
print(f"Dynamic pricing: utilization={current_utilization:.2f}, price={new_price:.3f} AITBC/h") logger.debug(f"Dynamic pricing: utilization={current_utilization:.2f}, price={new_price:.3f} AITBC/h")
except Exception as e: except Exception as e:
print(f"Dynamic pricing error: {e}") logger.error(f"Dynamic pricing error: {e}")
# Wait for next adjustment # Wait for next adjustment
await asyncio.sleep(900) # 15 minutes await asyncio.sleep(900) # 15 minutes
@@ -163,11 +166,11 @@ class ComputeProvider(Agent):
# Execute job (simulate) # Execute job (simulate)
asyncio.create_task(self._execute_job(job, job_request)) asyncio.create_task(self._execute_job(job, job_request))
print(f"Job accepted: {job.job_id} from {job.consumer_id}") logger.info(f"Job accepted: {job.job_id} from {job.consumer_id}")
return True return True
except Exception as e: except Exception as e:
print(f"Failed to accept job: {e}") logger.error(f"Failed to accept job: {e}")
return False return False
async def _execute_job(self, job: JobExecution, job_request: Dict[str, Any]): async def _execute_job(self, job: JobExecution, job_request: Dict[str, Any]):
@@ -193,11 +196,11 @@ class ComputeProvider(Agent):
# Notify consumer # Notify consumer
await self._notify_job_completion(job, earnings) await self._notify_job_completion(job, earnings)
print(f"Job completed: {job.job_id}, earned {earnings} AITBC") logger.info(f"Job completed: {job.job_id}, earned {earnings} AITBC")
except Exception as e: except Exception as e:
job.status = "failed" job.status = "failed"
print(f"Job execution failed: {job.job_id} - {e}") logger.error(f"Job execution failed: {job.job_id} - {e}")
async def _notify_job_completion(self, job: JobExecution, earnings: float): async def _notify_job_completion(self, job: JobExecution, earnings: float):
"""Notify consumer about job completion""" """Notify consumer about job completion"""

View File

@@ -4,11 +4,14 @@ Swarm Coordinator - for agents participating in collective intelligence
import asyncio import asyncio
import json import json
import logging
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from datetime import datetime from datetime import datetime
from dataclasses import dataclass from dataclasses import dataclass
from .agent import Agent from .agent import Agent
logger = logging.getLogger(__name__)
@dataclass @dataclass
class SwarmMessage: class SwarmMessage:
"""Swarm communication message""" """Swarm communication message"""
@@ -81,11 +84,11 @@ class SwarmCoordinator(Agent):
# Start swarm participation tasks # Start swarm participation tasks
asyncio.create_task(self._swarm_participation_loop(swarm_id)) asyncio.create_task(self._swarm_participation_loop(swarm_id))
print(f"Joined swarm: {swarm_id} as {config.get('role', 'participant')}") logger.info(f"Joined swarm: {swarm_id} as {config.get('role', 'participant')}")
return True return True
except Exception as e: except Exception as e:
print(f"Failed to join swarm {swarm_type}: {e}") logger.error(f"Failed to join swarm {swarm_type}: {e}")
return False return False
async def _swarm_participation_loop(self, swarm_id: str): async def _swarm_participation_loop(self, swarm_id: str):
@@ -107,7 +110,7 @@ class SwarmCoordinator(Agent):
swarm_config["last_activity"] = datetime.utcnow().isoformat() swarm_config["last_activity"] = datetime.utcnow().isoformat()
except Exception as e: except Exception as e:
print(f"Swarm participation error for {swarm_id}: {e}") logger.error(f"Swarm participation error for {swarm_id}: {e}")
# Wait before next participation cycle # Wait before next participation cycle
await asyncio.sleep(60) # 1 minute await asyncio.sleep(60) # 1 minute
@@ -135,11 +138,11 @@ class SwarmCoordinator(Agent):
# Update contribution count # Update contribution count
self.joined_swarms[message.swarm_id]["contribution_count"] += 1 self.joined_swarms[message.swarm_id]["contribution_count"] += 1
print(f"Broadcasted to swarm {message.swarm_id}: {message.message_type}") logger.info(f"Broadcasted to swarm {message.swarm_id}: {message.message_type}")
return True return True
except Exception as e: except Exception as e:
print(f"Failed to broadcast to swarm: {e}") logger.error(f"Failed to broadcast to swarm: {e}")
return False return False
async def _contribute_swarm_data(self, swarm_id: str): async def _contribute_swarm_data(self, swarm_id: str):
@@ -169,7 +172,7 @@ class SwarmCoordinator(Agent):
await self.broadcast_to_swarm(message) await self.broadcast_to_swarm(message)
except Exception as e: except Exception as e:
print(f"Failed to contribute swarm data: {e}") logger.error(f"Failed to contribute swarm data: {e}")
async def _get_load_balancing_data(self) -> Dict[str, Any]: async def _get_load_balancing_data(self) -> Dict[str, Any]:
"""Get load balancing data for swarm contribution""" """Get load balancing data for swarm contribution"""
@@ -237,11 +240,11 @@ class SwarmCoordinator(Agent):
# Submit to swarm for coordination # Submit to swarm for coordination
coordination_result = await self._submit_coordination_proposal(proposal) coordination_result = await self._submit_coordination_proposal(proposal)
print(f"Task coordination initiated: {task} with {collaborators} collaborators") logger.info(f"Task coordination initiated: {task} with {collaborators} collaborators")
return coordination_result return coordination_result
except Exception as e: except Exception as e:
print(f"Failed to coordinate task: {e}") logger.error(f"Failed to coordinate task: {e}")
return {"success": False, "error": str(e)} return {"success": False, "error": str(e)}
async def get_market_intelligence(self) -> Dict[str, Any]: async def get_market_intelligence(self) -> Dict[str, Any]:
@@ -275,7 +278,7 @@ class SwarmCoordinator(Agent):
return {"error": "Not joined to pricing swarm"} return {"error": "Not joined to pricing swarm"}
except Exception as e: except Exception as e:
print(f"Failed to get market intelligence: {e}") logger.error(f"Failed to get market intelligence: {e}")
return {"error": str(e)} return {"error": str(e)}
async def analyze_swarm_benefits(self) -> Dict[str, Any]: async def analyze_swarm_benefits(self) -> Dict[str, Any]:
@@ -302,7 +305,7 @@ class SwarmCoordinator(Agent):
} }
except Exception as e: except Exception as e:
print(f"Failed to analyze swarm benefits: {e}") logger.error(f"Failed to analyze swarm benefits: {e}")
return {"error": str(e)} return {"error": str(e)}
async def _register_with_swarm(self, swarm_id: str, registration: Dict[str, Any]): async def _register_with_swarm(self, swarm_id: str, registration: Dict[str, Any]):

160
scripts/qa-cycle.py Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
QA Cycle: Run tests, exercise scenarios, find bugs, perform code reviews.
Runs periodically to ensure repository health and discover regressions.
"""
import os
import subprocess
import json
import sys
import shutil
import time
import random
from datetime import datetime
from pathlib import Path
# Jitter: random delay up to 15 minutes (900 seconds)
time.sleep(random.randint(0, 900))
REPO_DIR = '/opt/aitbc'
LOG_FILE = '/opt/aitbc/qa-cycle.log'
TOKEN_FILE = '/opt/aitbc/.gitea_token.sh'
def get_token():
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE) as f:
for line in f:
if line.strip().startswith('GITEA_TOKEN='):
return line.strip().split('=', 1)[1].strip()
return os.getenv('GITEA_TOKEN', '')
GITEA_TOKEN = get_token()
API_BASE = os.getenv('GITEA_API_BASE', 'http://gitea.bubuit.net:3000/api/v1')
REPO = 'oib/aitbc'
def log(msg):
now = datetime.utcnow().isoformat() + 'Z'
with open(LOG_FILE, 'a') as f:
f.write(f"[{now}] {msg}\n")
print(msg)
def run_cmd(cmd, cwd=REPO_DIR, timeout=300):
try:
result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True, timeout=timeout)
return result.returncode, result.stdout, result.stderr
except subprocess.TimeoutExpired:
return -1, "", "timeout"
except Exception as e:
return -2, "", str(e)
def fetch_latest_main():
log("Fetching latest main...")
rc, out, err = run_cmd("git fetch origin main")
if rc != 0:
log(f"Fetch failed: {err}")
return False
rc, out, err = run_cmd("git checkout main")
if rc != 0:
log(f"Checkout main failed: {err}")
return False
rc, out, err = run_cmd("git reset --hard origin/main")
if rc != 0:
log(f"Reset to origin/main failed: {err}")
return False
log("Main updated to latest.")
return True
def run_tests():
log("Running test suites...")
results = []
for pkg in ['aitbc-core', 'aitbc-sdk', 'aitbc-crypto']:
testdir = f"packages/py/{pkg}/tests"
if not os.path.exists(os.path.join(REPO_DIR, testdir)):
continue
log(f"Testing {pkg}...")
rc, out, err = run_cmd(f"python3 -m pytest {testdir} -q", timeout=120)
if rc == 0:
log(f"{pkg} tests passed.")
else:
log(f"{pkg} tests failed (rc={rc}). Output: {out}\nError: {err}")
results.append((pkg, rc == 0))
return results
def run_lint():
log("Running linters (flake8 if available)...")
if shutil.which('flake8'):
rc, out, err = run_cmd("flake8 packages/py/ --count --select=E9,F63,F7,F82 --show-source --statistics", timeout=60)
if rc == 0:
log("✅ No critical lint errors.")
else:
log(f"❌ Lint errors: {out}")
else:
log("flake8 not installed; skipping lint.")
def query_api(path, method='GET', data=None):
import urllib.request
import urllib.error
url = f"{API_BASE}/{path}"
headers = {'Authorization': f'token {GITEA_TOKEN}'}
if data:
headers['Content-Type'] = 'application/json'
data = json.dumps(data).encode()
req = urllib.request.Request(url, method=method, headers=headers, data=data)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.load(resp)
except Exception as e:
log(f"API error {path}: {e}")
return None
def review_my_open_prs():
log("Checking my open PRs for missing reviews...")
my_prs = query_api(f'repos/{REPO}/pulls?state=open&author={MY_AGENT}') or []
for pr in my_prs:
num = pr['number']
title = pr['title']
requested = pr.get('requested_reviewers', [])
if not any(r.get('login') == SIBLING_AGENT for r in requested):
log(f"PR #{num} '{title}' missing sibling review. Requesting...")
query_api(f'repos/{REPO}/pulls/{num}/requested_reviewers', method='POST', data={'reviewers': [SIBLING_AGENT]})
else:
log(f"PR #{num} already has sibling review requested.")
def synthesize_status():
log("Collecting repository status...")
issues = query_api(f'repos/{REPO}/issues?state=open') or []
prs = query_api(f'repos/{REPO}/pulls?state=open') or []
log(f"Open issues: {len(issues)}, open PRs: {len(prs)}")
unassigned_issues = [i for i in issues if not i.get('assignees') and 'pull_request' not in i]
log(f"Unassigned issues: {len(unassigned_issues)}")
if unassigned_issues:
for i in unassigned_issues[:3]:
log(f" - #{i['number']} {i['title'][:50]}")
# Check CI for open PRs
for pr in prs:
num = pr['number']
statuses = query_api(f'repos/{REPO}/commits/{pr["head"]["sha"]}/statuses') or []
failing = [s for s in statuses if s.get('status') not in ('success', 'pending')]
if failing:
log(f"PR #{num} has failing checks: {', '.join(s.get('context','?') for s in failing)}")
def main():
now = datetime.utcnow().isoformat() + 'Z'
log(f"\n=== QA Cycle start: {now} ===")
if not GITEA_TOKEN:
log("GITEA_TOKEN not set; aborting.")
sys.exit(1)
global MY_AGENT, SIBLING_AGENT
MY_AGENT = os.getenv('AGENT_NAME', 'aitbc1')
SIBLING_AGENT = 'aitbc' if MY_AGENT == 'aitbc1' else 'aitbc1'
if not fetch_latest_main():
log("Aborting due to fetch failure.")
return
run_tests()
run_lint()
review_my_open_prs()
synthesize_status()
log(f"=== QA Cycle complete ===")
if __name__ == '__main__':
main()