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

This commit is contained in:
ogt
2026-07-10 10:51:18 +08:00
parent fa68f35e5b
commit 210d7db338
16 changed files with 1519 additions and 239 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 governanceOpenAI 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",

View File

@@ -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"

View File

@@ -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"):

View File

@@ -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"]
)