feat(agents): schedule mainstream agent version governance
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
Agent market discovery classifier
|
||||
=================================
|
||||
|
||||
Classifies AI controlled discovery repositories from primary GitHub
|
||||
Classifies AI controlled discovery candidates from supplied primary-source
|
||||
metadata. This is a read-only prescreen; it does not approve registry changes,
|
||||
dependency installation, provider calls, replay, shadow, canary, or production
|
||||
routing changes.
|
||||
dependency installation, provider calls, replay, shadow, canary, production
|
||||
routing changes, or GitHub live metadata reads.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -37,7 +37,7 @@ def run_agent_market_discovery_classification(
|
||||
"generated_at": generated_at or datetime.now(timezone.utc).isoformat(), # noqa: UP017
|
||||
"inputs": {
|
||||
"discovery_review_generated_at": discovery_review.get("generated_at"),
|
||||
"metadata_source": "github_repository_api_summary",
|
||||
"metadata_source": "supplied_primary_source_metadata_or_policy_blocked_stub",
|
||||
},
|
||||
"policy": {
|
||||
"auto_watch_registry_addition_approved": False,
|
||||
|
||||
@@ -85,6 +85,12 @@ def run_agent_market_watch(
|
||||
|
||||
changed_candidates = sum(1 for candidate in candidates if candidate["changed"])
|
||||
watch_only_candidates = sum(1 for candidate in candidates if not candidate["changed"])
|
||||
policy_skipped_sources = sum(
|
||||
1
|
||||
for candidate in candidates
|
||||
for source in candidate.get("sources") or []
|
||||
if source.get("status") == "skipped_by_policy"
|
||||
)
|
||||
|
||||
return {
|
||||
"schema_version": "agent_market_watch_report_v1",
|
||||
@@ -104,6 +110,7 @@ def run_agent_market_watch(
|
||||
"watch_only_candidates": watch_only_candidates,
|
||||
"integration_queue_count": len(integration_queue),
|
||||
"failure_count": len(failures),
|
||||
"policy_skipped_source_count": policy_skipped_sources,
|
||||
},
|
||||
"candidates": candidates,
|
||||
"integration_queue": integration_queue,
|
||||
@@ -232,6 +239,21 @@ def _evaluate_source(
|
||||
"reference_version": reference_version,
|
||||
"error": None,
|
||||
}
|
||||
if _is_policy_blocked_source(source_type, url):
|
||||
return {
|
||||
"source_id": source_id,
|
||||
"type": source_type,
|
||||
"url": url,
|
||||
"status": "skipped_by_policy",
|
||||
"http_status": None,
|
||||
"version": reference_version,
|
||||
"published_at": None,
|
||||
"content_hash": None,
|
||||
"changed_since_reference": False,
|
||||
"reference_version": reference_version,
|
||||
"error": None,
|
||||
"policy_blocker": "github_global_freeze_no_github_api_or_github_web_reads",
|
||||
}
|
||||
|
||||
fetched = fetcher(url, timeout_seconds)
|
||||
previous = previous_sources.get((candidate_id, source_id), {})
|
||||
@@ -285,6 +307,13 @@ def _is_github_rate_limited(url: str, fetched: FetchedSource) -> bool:
|
||||
return "rate limit" in body or "api rate limit exceeded" in body
|
||||
|
||||
|
||||
def _is_policy_blocked_source(source_type: str, url: str) -> bool:
|
||||
if source_type in {"github_release", "github_tags", "github_search"}:
|
||||
return True
|
||||
host = urlparse(url).netloc.lower()
|
||||
return host in {"github.com", "www.github.com", "api.github.com"}
|
||||
|
||||
|
||||
def _parse_source(source_type: str, body: bytes) -> dict[str, str | None]:
|
||||
if source_type == "pypi":
|
||||
payload = _loads_json(body)
|
||||
@@ -335,7 +364,19 @@ def _fetch_discovery_source(
|
||||
timeout_seconds: int,
|
||||
) -> dict[str, Any]:
|
||||
source_id = str(source.get("source_id", "")).strip()
|
||||
source_type = str(source.get("type", "")).strip()
|
||||
url = str(source.get("url", "")).strip()
|
||||
if _is_policy_blocked_source(source_type, url):
|
||||
return {
|
||||
"source_id": source_id,
|
||||
"type": source.get("type"),
|
||||
"url": url,
|
||||
"status": "skipped_by_policy",
|
||||
"http_status": None,
|
||||
"items": [],
|
||||
"error": None,
|
||||
"policy_blocker": "github_global_freeze_no_github_api_or_github_web_reads",
|
||||
}
|
||||
fetched = fetcher(url, timeout_seconds)
|
||||
result: dict[str, Any] = {
|
||||
"source_id": source_id,
|
||||
|
||||
@@ -741,6 +741,30 @@ _COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS: list[dict[str, Any]] = [
|
||||
"next_action": "將每個 P0 runbook 補齊 AI controlled repair loop 欄位。",
|
||||
"mapped_workplan_id": "P1-006",
|
||||
},
|
||||
{
|
||||
"id": "CIR-P1-AI-002",
|
||||
"priority": "P1",
|
||||
"order": 40,
|
||||
"status": "in_progress",
|
||||
"lane": "agent_market_research_version_lifecycle",
|
||||
"request": "把目前市場上所有主流且專業的 AI Agent 完整研究、評估導入整合,且目前 AI Agent 版本是否需要更新/更版要納入定期排程。",
|
||||
"normalized_work_item": (
|
||||
"AI Agent market governance:OpenAI Agents SDK、LangGraph、Google ADK、Microsoft Agent Framework、"
|
||||
"CrewAI、LlamaIndex AgentWorkflow、Pydantic AI、AWS Strands、AutoGen legacy migration、"
|
||||
"NVIDIA NeMo/Nemotron、Claude Agent SDK 等主流候選都要進入 primary-source watch、"
|
||||
"integration review、offline replay gate、version lifecycle proposal 與 no-write update cadence。"
|
||||
),
|
||||
"current_state": (
|
||||
"已開始補 agent-market / ai-technology registries、GitHub-freeze policy skip、replacement candidates "
|
||||
"與版本生命週期 proposal;正式整合仍需 replay / shadow / canary / cost / security evidence。"
|
||||
),
|
||||
"acceptance": (
|
||||
"Work Items、Agent Market Watch、AI Technology Watch 與 Agent Version Lifecycle API 可讀回候選清單、"
|
||||
"版本 baseline、排程 cadence、GitHub-freeze 邊界與下一個 offline replay / update proposal gate。"
|
||||
),
|
||||
"next_action": "跑 registry/schema/test 驗證後推 Gitea,讀回 production API/UI,確認此項出現在 Work Items。",
|
||||
"mapped_workplan_id": "P1-006",
|
||||
},
|
||||
{
|
||||
"id": "CIR-P1-KM-001",
|
||||
"priority": "P1",
|
||||
|
||||
@@ -225,7 +225,7 @@ def test_docs_hash_ignores_dynamic_script_noise():
|
||||
assert second_report["candidates"][0]["sources"][0]["changed_since_reference"] is False
|
||||
|
||||
|
||||
def test_market_watch_parses_github_tags_for_projects_without_releases():
|
||||
def test_market_watch_policy_skips_github_tags_without_fetching():
|
||||
registry = {
|
||||
"schema_version": "agent_market_watch_sources_v1",
|
||||
"policy": {"replacement_decision_allowed": False},
|
||||
@@ -247,11 +247,7 @@ def test_market_watch_parses_github_tags_for_projects_without_releases():
|
||||
}
|
||||
|
||||
def fetcher(_url: str, _timeout: int) -> FetchedSource:
|
||||
return FetchedSource(
|
||||
status="ok",
|
||||
http_status=200,
|
||||
body=json.dumps([{"name": "v0.8.0"}]).encode(),
|
||||
)
|
||||
raise AssertionError("GitHub sources must be skipped by policy")
|
||||
|
||||
report = run_agent_market_watch(
|
||||
registry,
|
||||
@@ -262,9 +258,11 @@ def test_market_watch_parses_github_tags_for_projects_without_releases():
|
||||
)
|
||||
|
||||
source = report["candidates"][0]["sources"][0]
|
||||
assert source["status"] == "ok"
|
||||
assert source["version"] == "v0.8.0"
|
||||
assert source["status"] == "skipped_by_policy"
|
||||
assert source["version"] is None
|
||||
assert source["policy_blocker"] == "github_global_freeze_no_github_api_or_github_web_reads"
|
||||
assert report["summary"]["failure_count"] == 0
|
||||
assert report["summary"]["policy_skipped_source_count"] == 1
|
||||
|
||||
|
||||
def test_versioned_source_ignores_metadata_hash_noise_when_version_is_unchanged():
|
||||
@@ -335,7 +333,7 @@ def test_versioned_source_ignores_metadata_hash_noise_when_version_is_unchanged(
|
||||
assert report["candidates"][0]["sources"][0]["changed_since_reference"] is False
|
||||
|
||||
|
||||
def test_github_rate_limit_carries_forward_previous_source_without_failure():
|
||||
def test_github_release_source_is_policy_skipped_before_rate_limit_handling():
|
||||
registry = {
|
||||
"schema_version": "agent_market_watch_sources_v1",
|
||||
"policy": {"replacement_decision_allowed": False},
|
||||
@@ -370,12 +368,7 @@ def test_github_rate_limit_carries_forward_previous_source_without_failure():
|
||||
}
|
||||
|
||||
def fetcher(_url: str, _timeout: int) -> FetchedSource:
|
||||
return FetchedSource(
|
||||
status="error",
|
||||
http_status=403,
|
||||
body=b'{"message":"API rate limit exceeded"}',
|
||||
error="http_403",
|
||||
)
|
||||
raise AssertionError("GitHub sources must be skipped by policy")
|
||||
|
||||
report = run_agent_market_watch(
|
||||
registry,
|
||||
@@ -389,8 +382,9 @@ def test_github_rate_limit_carries_forward_previous_source_without_failure():
|
||||
source = report["candidates"][0]["sources"][0]
|
||||
assert report["summary"]["failure_count"] == 0
|
||||
assert report["summary"]["changed_candidates"] == 0
|
||||
assert source["status"] == "carried_forward_rate_limited"
|
||||
assert source["version"] == "v1.2.3"
|
||||
assert report["summary"]["policy_skipped_source_count"] == 1
|
||||
assert source["status"] == "skipped_by_policy"
|
||||
assert source["version"] is None
|
||||
assert source["changed_since_reference"] is False
|
||||
assert source["error"] is None
|
||||
assert source["carried_forward_from_previous"] is True
|
||||
assert source["policy_blocker"] == "github_global_freeze_no_github_api_or_github_web_reads"
|
||||
|
||||
@@ -13,9 +13,11 @@ def test_load_latest_ai_agent_version_lifecycle_update_proposal_reads_committed_
|
||||
data = load_latest_ai_agent_version_lifecycle_update_proposal()
|
||||
|
||||
assert data["schema_version"] == "ai_agent_version_lifecycle_update_proposal_v1"
|
||||
assert data["program_status"]["overall_completion_percent"] == 78
|
||||
assert data["program_status"]["current_task_id"] == "P2-413"
|
||||
assert data["program_status"]["next_task_id"] == "P2-414"
|
||||
assert data["program_status"]["overall_completion_percent"] == 80
|
||||
assert data["program_status"]["current_task_id"] == "CIR-P1-AI-002"
|
||||
assert data["program_status"]["next_task_id"] == (
|
||||
"P1-006-agent-market-version-lifecycle-readback"
|
||||
)
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert data["program_status"]["runtime_authority"] == (
|
||||
"version_lifecycle_update_proposal_only_no_write_or_upgrade"
|
||||
@@ -31,8 +33,8 @@ def test_load_latest_ai_agent_version_lifecycle_update_proposal_reads_committed_
|
||||
assert data["telegram_digest_contract"]["gateway_queue_write_allowed"] is False
|
||||
assert data["telegram_digest_contract"]["bot_api_call_allowed"] is False
|
||||
assert data["rollups"]["domain_count"] == len(data["lifecycle_domains"]) == 12
|
||||
assert data["rollups"]["proposal_count"] == len(data["update_proposals"]) == 12
|
||||
assert data["rollups"]["cadence_count"] == len(data["cadence_matrix"]) == 6
|
||||
assert data["rollups"]["proposal_count"] == len(data["update_proposals"]) == 13
|
||||
assert data["rollups"]["cadence_count"] == len(data["cadence_matrix"]) == 7
|
||||
assert data["rollups"]["approval_gate_count"] == len(data["approval_gate_matrix"]) == 9
|
||||
assert data["rollups"]["false_runtime_boundary_count"] == 29
|
||||
assert data["rollups"]["auto_execution_allowed_count"] == 0
|
||||
@@ -43,6 +45,10 @@ def test_load_latest_ai_agent_version_lifecycle_update_proposal_reads_committed_
|
||||
proposal["proposal_id"] == "openclaw_challenger_replay_bench"
|
||||
for proposal in data["update_proposals"]
|
||||
)
|
||||
assert any(
|
||||
proposal["proposal_id"] == "mainstream_agent_framework_version_lifecycle_review"
|
||||
for proposal in data["update_proposals"]
|
||||
)
|
||||
assert all(proposal["requires_owner_approval"] is True for proposal in data["update_proposals"])
|
||||
assert all(proposal["direct_update_allowed"] is False for proposal in data["update_proposals"])
|
||||
|
||||
@@ -68,7 +74,7 @@ def test_ai_agent_version_lifecycle_update_proposal_rejects_rollup_mismatch(tmp_
|
||||
def test_ai_agent_version_lifecycle_update_proposal_rejects_direct_update(tmp_path):
|
||||
snapshot = _snapshot()
|
||||
snapshot["update_proposals"][0]["direct_update_allowed"] = True
|
||||
snapshot["rollups"]["read_only_proposal_count"] = 11
|
||||
snapshot["rollups"]["read_only_proposal_count"] = 12
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="direct update"):
|
||||
|
||||
@@ -16,9 +16,11 @@ def test_ai_agent_version_lifecycle_update_proposal_endpoint_returns_committed_s
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "ai_agent_version_lifecycle_update_proposal_v1"
|
||||
assert data["program_status"]["overall_completion_percent"] == 78
|
||||
assert data["program_status"]["current_task_id"] == "P2-413"
|
||||
assert data["program_status"]["next_task_id"] == "P2-414"
|
||||
assert data["program_status"]["overall_completion_percent"] == 80
|
||||
assert data["program_status"]["current_task_id"] == "CIR-P1-AI-002"
|
||||
assert data["program_status"]["next_task_id"] == (
|
||||
"P1-006-agent-market-version-lifecycle-readback"
|
||||
)
|
||||
assert data["program_status"]["read_only_mode"] is True
|
||||
assert data["runtime_boundaries"]["package_upgrade_allowed"] is False
|
||||
assert data["runtime_boundaries"]["k3s_upgrade_allowed"] is False
|
||||
@@ -26,8 +28,8 @@ def test_ai_agent_version_lifecycle_update_proposal_endpoint_returns_committed_s
|
||||
assert data["runtime_boundaries"]["production_write_allowed"] is False
|
||||
assert data["runtime_boundaries"]["openclaw_replacement_allowed"] is False
|
||||
assert data["rollups"]["domain_count"] == 12
|
||||
assert data["rollups"]["proposal_count"] == 12
|
||||
assert data["rollups"]["approval_required_count"] == 12
|
||||
assert data["rollups"]["proposal_count"] == 13
|
||||
assert data["rollups"]["approval_required_count"] == 13
|
||||
assert data["rollups"]["auto_execution_allowed_count"] == 0
|
||||
assert data["rollups"]["telegram_direct_send_count"] == 0
|
||||
assert data["rollups"]["production_write_count"] == 0
|
||||
@@ -35,3 +37,11 @@ def test_ai_agent_version_lifecycle_update_proposal_endpoint_returns_committed_s
|
||||
proposal["proposal_id"] == "telegram_bot_gateway_policy_refresh"
|
||||
for proposal in data["update_proposals"]
|
||||
)
|
||||
assert any(
|
||||
proposal["proposal_id"] == "mainstream_agent_framework_version_lifecycle_review"
|
||||
for proposal in data["update_proposals"]
|
||||
)
|
||||
assert any(
|
||||
binding["binding_id"] == "agent_version_lifecycle_weekly_tuesday_0900_taipei"
|
||||
for binding in data["periodic_schedule_bindings"]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user