Skip to content

Commit 070e0d3

Browse files
author
J.A.R.V.I.S.
committed
feat: v2.95 skill-scoped trust scores — per-skill IR evidence → per-skill score
Core implementation: - _compute_skill_trust_scores(): skill_id→float dict from bilateral IR base=0.3 + min(unique_callers,10)*0.04 + min(bilateral_count,50)*0.005 clamped [0.0, 1.0]; {} when no evidence - _build_governance_metadata(): trust_scores dict + trust_score_method always populated; global trust_score updated to per-skill avg when IR present New endpoint: - GET /trust/skill-scores: trust_scores dict, method, algorithm, skill_count QuerySkill: - POST /skills/query response: skill_trust_score field (null|float) AgentCard: - capabilities.skill_scoped_trust_scores: true - endpoints.skill_trust_scores: /trust/skill-scores Tests: SS01-SS16, 16/16 PASS Regression: PD01-16 + GM01-16 + adversarial_fixtures = 45/45 PASS VERSION: 2.94.0 → 2.95.0 A2A #1717 community convergence: governance_metadata skill-scoped trust aeoess APS v1.37.0 importBilateralEvidence() per-skill accumulation
1 parent 17206cb commit 070e0d3

4 files changed

Lines changed: 527 additions & 5 deletions

File tree

docs/CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,44 @@ Dates: Asia/Shanghai (UTC+8)
77

88
---
99

10+
## v2.95.0 — Skill-Scoped Trust Scores (2026-04-10)
11+
12+
### Added
13+
- **`_compute_skill_trust_scores()`** — computes per-skill trust scores from bilateral IR evidence
14+
- Algorithm: `base=0.3 + min(unique_callers,10)*0.04 + min(bilateral_count,50)*0.005`; clamped to [0.0, 1.0]
15+
- Returns `{}` when no bilateral IR records exist (no evidence → no score)
16+
- A2A #1717 community convergence: skill-scoped trust for granular authorization
17+
- aeoess APS v1.37.0 `importBilateralEvidence()` per-skill accumulation pattern aligned
18+
- **`GET /trust/skill-scores`** — new endpoint returning per-skill trust scores dict
19+
- `trust_scores: {"<skill_id>": float, ...}`, `method: "skill_scoped_v1"`, `algorithm` block
20+
- Returns `{}` trust_scores when no IR evidence
21+
- **`governance_metadata.trust_scores`** — dict (skill_id → float) embedded in GM block
22+
- `trust_score_method: "skill_scoped_v1"` declared alongside
23+
- Backward compat: global `trust_score` retained; updated to per-skill average when IR evidence present
24+
- **QuerySkill `skill_trust_score` field**`POST /skills/query` response includes per-skill score
25+
- `null` when no bilateral IR evidence for that skill_id
26+
- `float [0.0, 1.0]` when IR evidence exists
27+
- **`capabilities.skill_scoped_trust_scores: true`** in AgentCard
28+
- **`endpoints.skill_trust_scores: /trust/skill-scores`** in AgentCard
29+
- **Tests SS01–SS16** (`tests/test_skill_scoped_trust_v295.py`) — 16/16 PASS
30+
- SS01–SS04: VERSION + capability/endpoint flags + backward compat
31+
- SS05–SS07: empty no-IR response, schema, algorithm fields
32+
- SS08–SS11: score computation (single skill, multi-skill, clamping, skill_count)
33+
- SS12–SS14: QuerySkill skill_trust_score field (null/populated)
34+
- SS15–SS16: governance_metadata integration + global trust_score backward compat
35+
36+
### Changed
37+
- VERSION: 2.94.0 → 2.95.0
38+
- `test_principal_diversity_v294.py`: PD01/PD15 version assertions relaxed to `>= 2.94.0` (version-agnostic)
39+
- `_build_governance_metadata()`: always populates `trust_scores` and `trust_score_method`
40+
41+
### Notes
42+
- **Backward compatibility preserved**: global `trust_score` scalar still present in GM block
43+
- **Empty dict semantics**: `{}` trust_scores = "no bilateral IR evidence yet" (not a 404)
44+
- **No breaking changes**: all existing /trust/ endpoints unchanged
45+
46+
---
47+
1048
## v2.94.0 — Principal Diversity Defense for Bilateral IR (2026-04-10)
1149

1250
### Added

relay/acp_relay.py

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
logging.basicConfig(level=logging.INFO, format="%(asctime)s [acp] %(message)s", datefmt="%H:%M:%S")
163163
log = logging.getLogger("acp-p2p")
164164

165-
VERSION = "2.94.0" # v2.94: principal_diversity_defense — colluding-pair penalty in bilateral IR (aeoess A2A #1718); GET /trust/bilateral-ir/diversity; same_counterparty_penalty (concentration>60% → 0.10x weight)
165+
VERSION = "2.95.0" # v2.95: skill-scoped trust scores — governance_metadata.trust_scores dict (skill_id→float); QuerySkill returns skill_trust_score; backward-compat global trust_score retained (A2A #1717 community convergence)
166166

167167
_heartbeat_period_ms = None # v2.80: optional heartbeat period in ms declared in AgentCard
168168

@@ -2105,6 +2105,7 @@ def _make_agent_card(name, skills):
21052105
"governance_metadata": bool(_governance_metadata), # v2.60: governance metadata in AgentCard (trust_score/capability_manifest/policy_compliance/audit_trail_reference)
21062106
"derivation_rights": bool(_governance_metadata), # v2.92: derivation_rights in governance_metadata (GDPR retention/export control, aeoess SDK v1.37.0)
21072107
"credential_lifecycle": bool(_governance_metadata), # v2.92: credential_lifecycle in governance_metadata (session TTL + revocation, aeoess SDK v1.37.0)
2108+
"skill_scoped_trust_scores": True, # v2.95: governance_metadata.trust_scores dict (skill_id→float); QuerySkill returns skill_trust_score (A2A #1717)
21082109
"ir_test_vectors": _ED25519_AVAILABLE, # v2.64: GET /ir/test-vectors — deterministic bilateral IR test vectors for cross-impl verification (@aeoess A2A #1718)
21092110
"ir_adversarial_fixtures": _ED25519_AVAILABLE, # v2.91: GET /ir/adversarial-fixtures — collusion/inflation adversarial test fixtures (scan24 / A2A #1718)
21102111
"import_evidence": _ED25519_AVAILABLE, # v2.65: POST /ir/import-evidence — import external bilateral IR + generate APS-compatible reputation_update (@aeoess A2A #1718)
@@ -2183,6 +2184,7 @@ def _make_agent_card(name, skills):
21832184
"trust_signals_security_posture": "/trust/signals/security-posture", # v2.71: GET — detailed security posture report (CVE scan, component versions)
21842185
"bilateral_ir_log": "/trust/bilateral-ir/log", # v2.72: GET — queryable bilateral IR log (filter: caller_did/skill_id/bilateral/since)
21852186
"bilateral_ir_diversity": "/trust/bilateral-ir/diversity", # v2.94: GET — principal diversity analysis; colluding-pair penalty (aeoess A2A #1718)
2187+
"skill_trust_scores": "/trust/skill-scores", # v2.95: GET — per-skill trust scores from bilateral IR evidence (A2A #1717)
21862188
"agent_limitations_schema": "/agent-limitations/schema", # v2.73: GET — JSON Schema for agent_limitations typed constraint dict
21872189
"capability_token_detail": "/trust/signals/capability-token", # v2.74: GET — detailed capability token declaration & issuance config (A2A #1716 SINT aligned)
21882190
"capability_token_fixtures": "/trust/signals/capability-token/fixtures", # v2.75: GET — canonical authorization fixture vectors (A2A #1716 @pshkv 4-deny+1-allow)
@@ -2443,6 +2445,21 @@ def _build_governance_metadata() -> dict:
24432445
"revocation_check_frequency": None, # None = not specified
24442446
}
24452447

2448+
# v2.95: skill-scoped trust scores — per-skill IR evidence → per-skill score
2449+
# Format: {"trust_scores": {"<skill_id>": <float 0.0-1.0>, ...}, "trust_score_method": "skill_scoped_v1"}
2450+
# Backward compat: global trust_score retained as computed average
2451+
# Community convergence: A2A #1717 discussion thread (2026-04-09), @kevinkaylie proposal
2452+
if "trust_scores" not in gm:
2453+
trust_scores = _compute_skill_trust_scores()
2454+
gm["trust_scores"] = trust_scores # {} = no IR evidence yet
2455+
gm["trust_score_method"] = "skill_scoped_v1"
2456+
# Update global trust_score to be the average of skill-scoped scores (if any);
2457+
# if no per-skill evidence yet, retain the configured/computed scalar trust_score
2458+
if trust_scores:
2459+
gm["trust_score"] = round(
2460+
sum(trust_scores.values()) / len(trust_scores), 3
2461+
)
2462+
24462463
# Live counters (always override)
24472464
gm["interaction_record_count"] = len(_interaction_records)
24482465
gm["peer_count"] = len(_peers)
@@ -2451,6 +2468,53 @@ def _build_governance_metadata() -> dict:
24512468
return gm
24522469

24532470

2471+
def _compute_skill_trust_scores() -> dict:
2472+
"""
2473+
v2.95: Compute per-skill trust scores from bilateral interaction record evidence.
2474+
2475+
Algorithm:
2476+
For each skill_id seen in _interaction_records:
2477+
- bilateral_count = number of bilateral records for this skill
2478+
- unique_callers = number of unique caller_dids for this skill
2479+
- base_score = 0.3 + (min(unique_callers,10) * 0.04) + (min(bilateral_count,50) * 0.005)
2480+
- score = clamp(base_score, 0.0, 1.0)
2481+
2482+
Result: {"<skill_id>": <float>, ...}
2483+
2484+
If no interaction records exist, returns {} (empty dict = no per-skill evidence yet).
2485+
2486+
References:
2487+
- A2A #1717: governance_metadata skill-scoped trust proposal (2026-04-09)
2488+
- aeoess APS v1.37.0 SDK: importBilateralEvidence() per-skill accumulation
2489+
- ACP v2.64: bilateral IR records carry skill_id field
2490+
"""
2491+
if not _interaction_records:
2492+
return {}
2493+
2494+
from collections import defaultdict
2495+
skill_bilateral: dict = defaultdict(int) # skill_id → bilateral count
2496+
skill_callers: dict = defaultdict(set) # skill_id → set of caller_dids
2497+
2498+
for rec in _interaction_records:
2499+
sid = rec.get("skill_id")
2500+
if not sid:
2501+
continue
2502+
if rec.get("bilateral") is True:
2503+
skill_bilateral[sid] += 1
2504+
caller = rec.get("caller_did")
2505+
if caller:
2506+
skill_callers[sid].add(caller)
2507+
2508+
trust_scores = {}
2509+
for sid in set(skill_bilateral.keys()) | set(skill_callers.keys()):
2510+
bilateral_n = skill_bilateral.get(sid, 0)
2511+
unique_callers = len(skill_callers.get(sid, set()))
2512+
base = 0.3 + (min(unique_callers, 10) * 0.04) + (min(bilateral_n, 50) * 0.005)
2513+
trust_scores[sid] = round(min(1.0, max(0.0, base)), 3)
2514+
2515+
return trust_scores
2516+
2517+
24542518
# ══════════════════════════════════════════════════════════════════════════════
24552519

24562520
def _generate_ir_test_vectors() -> dict:
@@ -6833,6 +6897,54 @@ def do_GET(self):
68336897
"version": VERSION,
68346898
})
68356899

6900+
# ── GET /trust/skill-scores — per-skill trust scores from bilateral IR (v2.95) ──
6901+
elif p == "/trust/skill-scores":
6902+
"""
6903+
GET /trust/skill-scores — Per-skill trust scores from bilateral IR evidence (v2.95).
6904+
6905+
Returns a trust score for each skill_id that appears in bilateral interaction records.
6906+
Score is computed from: unique_callers (diversity) + bilateral_count (volume).
6907+
6908+
Response 200:
6909+
{
6910+
"ok": true,
6911+
"trust_scores": { "<skill_id>": <float 0.0-1.0>, ... },
6912+
"method": "skill_scoped_v1",
6913+
"algorithm": {
6914+
"base": 0.3,
6915+
"caller_diversity": "min(unique_callers, 10) * 0.04",
6916+
"volume": "min(bilateral_count, 50) * 0.005",
6917+
"max": 1.0
6918+
},
6919+
"skill_count": int,
6920+
"ir_count": int,
6921+
"note": str,
6922+
"version": str,
6923+
}
6924+
6925+
References:
6926+
- A2A #1717: governance_metadata skill-scoped trust proposal (2026-04-09)
6927+
- aeoess APS v1.37.0 SDK: importBilateralEvidence() per-skill accumulation
6928+
"""
6929+
ss_scores = _compute_skill_trust_scores()
6930+
self._json({
6931+
"ok": True,
6932+
"trust_scores": ss_scores,
6933+
"method": "skill_scoped_v1",
6934+
"algorithm": {
6935+
"base": 0.3,
6936+
"caller_diversity": "min(unique_callers, 10) * 0.04",
6937+
"volume": "min(bilateral_count, 50) * 0.005",
6938+
"max": 1.0,
6939+
},
6940+
"skill_count": len(ss_scores),
6941+
"ir_count": len(_interaction_records),
6942+
"note": "Per-skill trust scores derived from bilateral IR evidence. "
6943+
"Empty dict = no bilateral IR records yet. "
6944+
"A2A #1717 community convergence: skill-scoped trust for granular authorization.",
6945+
"version": VERSION,
6946+
})
6947+
68366948
# ── GET /agent-limitations/schema — JSON Schema for agent_limitations dict (v2.73) ──
68376949
elif p == "/agent-limitations/schema":
68386950
"""
@@ -11240,13 +11352,19 @@ def _do_cancel(tid):
1124011352
queried_skill_obj.get("limitations") if queried_skill_obj else None
1124111353
) or [] # v2.28: per-skill limitations[] in QuerySkill response
1124211354

11355+
# v2.95: attach per-skill trust score from bilateral IR evidence
11356+
# Returns null if no IR evidence for this skill yet
11357+
_skill_trust_scores = _compute_skill_trust_scores()
11358+
skill_trust_score_val = _skill_trust_scores.get(skill_id) # None if no evidence
11359+
1124311360
self._json({
1124411361
"skill_id": skill_id,
1124511362
"support_level": support_level,
1124611363
"reason": reason,
1124711364
"constraints_applied": constraints_applied,
1124811365
"skill_constraints_declared": skill_constraints_declared, # v2.26
1124911366
"skill_limitations_declared": skill_limitations_declared, # v2.28
11367+
"skill_trust_score": skill_trust_score_val, # v2.95: per-skill IR evidence trust score (null = no evidence yet)
1125011368
"known_skills": known_skills_str,
1125111369
"agent": {
1125211370
"name": agent_card.get("name"),

tests/test_principal_diversity_v294.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,13 @@ def _card(hp):
9696
class TestVersionAndFlags:
9797

9898
def test_pd01_version(self):
99-
"""PD01: VERSION == 2.94.0"""
99+
"""PD01: VERSION >= 2.94.0 (principal_diversity_defense introduced)"""
100100
proc, hp = _start_relay(_free_port())
101101
try:
102102
card = _card(hp)
103-
assert card.get("acp_version") == "2.94.0"
103+
ver = card.get("acp_version", "")
104+
major, minor, patch = (int(x) for x in ver.split("."))
105+
assert (major, minor) >= (2, 94), f"Expected >= 2.94.0, got {ver}"
104106
finally:
105107
_stop(proc)
106108

@@ -380,7 +382,7 @@ def test_pd14_insufficient_records_no_penalty(self):
380382
class TestVersionAndCompat:
381383

382384
def test_pd15_version_in_diversity_response(self):
383-
"""PD15: /trust/bilateral-ir/diversity response version == 2.94.0"""
385+
"""PD15: /trust/bilateral-ir/diversity response version >= 2.94.0"""
384386
proc, hp = _start_relay(_free_port())
385387
try:
386388
# Inject 3 records to get past the 404 guard
@@ -403,7 +405,9 @@ def test_pd15_version_in_diversity_response(self):
403405
status, body = _get(hp, "/trust/bilateral-ir/diversity?peer_id=did:key:z6MkPD15a")
404406
if status == 404:
405407
pytest.skip("IR injection not supported")
406-
assert body.get("version") == "2.94.0"
408+
ver = body.get("version", "")
409+
major, minor, _patch = (int(x) for x in ver.split("."))
410+
assert (major, minor) >= (2, 94), f"Expected >= 2.94.0, got {ver}"
407411
finally:
408412
_stop(proc)
409413

0 commit comments

Comments
 (0)