Skip to content

feat(bridge): ERC-8004 Day 3 — normalizer + score_ingest + trust score wiring#23

Merged
kenneives merged 1 commit into
mainfrom
feat/erc8004-day3-normalizer-score-ingest
May 22, 2026
Merged

feat(bridge): ERC-8004 Day 3 — normalizer + score_ingest + trust score wiring#23
kenneives merged 1 commit into
mainfrom
feat/erc8004-day3-normalizer-score-ingest

Conversation

@kenneives
Copy link
Copy Markdown
Member

Summary

Day 3 (final) of the 3-day ERC-8004 bridge MVP. Closes #87 + #88.

Wires the full read → normalize → score path:

  • attestation_normalizer.normalize(entry) — parses CTEF envelope, resolves did:web JWKS, verifies Ed25519 signature
  • score_ingest.score(attestations) — derives 0-1 contribution with per-claim_type caps + provider-diversity weighting
  • src/trust/score.py::_external_score_with_attestations() — opt-in blender that falls back to existing community-signal score when no attestations passed (backward-compatible)

What's in the box

Module LoC Purpose
attestation_normalizer.py 290 CTEF envelope parser + did:web resolver + Ed25519 verifier. NormalizationError on any failure — no unverified attestation ever reaches downstream.
score_ingest.py 175 Pure-function scoring. Identity cap 0.60, authority 0.25, continuity 0.15. Diversity > count. Max-of-two blend with community signals.
src/trust/score.py +44 New _external_score_with_attestations() helper. Lazy bridge import. Backward-compat verified.
fixtures/* 4 files × 3 dirs Deterministic snapshot fixtures + regen script.
DEPLOY.md 130 Prod deploy guide + sync-job design + cost analysis (~0.2% of Alchemy free tier).
tests/test_attestation_normalizer.py 270 20 tests with real Ed25519 keys (not mocked) + httpx.MockTransport for JWKS.
tests/test_score_ingest.py 200 19 tests across base cases, claim-type caps, provider diversity, admissibility filtering.
tests/test_fixtures.py 75 4 round-trip tests using the 3 snapshot fixtures.

Test results

$ pytest agentgraph_bridge_erc8004/tests/ -v
URN parser:          11 PASS
Registry reader:     13 PASS, 1 skipped (live mainnet smoke)
Attestation normalizer: 20 PASS  (incl. tampered-payload + wrong-signing-key)
Score ingest:        19 PASS  (incl. diversity-vs-count + max-blend)
Snapshot fixtures:   4 PASS   (3 round-trip + 1 presence check)
TOTAL:               67 PASS, 1 skipped

Backward-compatibility verified

>>> _source_reputation_score({'stars': 100})
0.4009
>>> _external_score_with_attestations({'stars': 100})  # no attestations
0.4009
>>> # IDENTICAL — entities without ERC-8004 attestations see zero change

Architectural decisions worth eyeballing

  1. Hard signature requirementNormalizationError on any signature/JWKS/envelope failure. No partial-trust shortcuts. The trust score never sees an unverified attestation.

  2. Per-claim_type caps that sum to 1.0 — identity 0.60 + authority 0.25 + continuity 0.15 = 1.00. Means an entity with all three high-strength signals can reach the full external slot; identity-only caps at 0.60. Matches CTEF's "identity is foundational, authority + continuity stack on top" semantics.

  3. Provider diversity dominates count — 3 attestations from 3 distinct providers > 10 from 1. Prevents Sybil-style score inflation from one issuer spam-attesting.

  4. Max-of-two blend, not additiveblend_with_community_signals() returns max(erc, community). Additive blending would let an entity stack on-chain + GitHub signals beyond the 0.40 weight isolation invariant.

  5. Lazy bridge import_external_score_with_attestations() only imports the bridge when attestations are actually passed in. Main backend stays lean. Bridge stays in [erc8004] optional deps.

  6. Day 1 bug fixed inlineNormalizedAttestation.is_admissible was comparing tz-aware expires_at to naive datetime.utcnow() → TypeError. Now uses tz-aware datetime.now(timezone.utc) + defensive normalization.

What's NOT in this PR (post-MVP)

  • Real EIP-8004 contract addresses — still placeholders in .env. Bridge is functionally inert until they land (any read_entry returns RegistryReadError). When they're verified on mainnet, the deploy guide in DEPLOY.md covers the one-liner swap.
  • Background sync job — the cron job that populates an erc8004_attestations DB cache for the trust recompute job to read. Schema proposal in DEPLOY.md. Not blocking v0.3.2 publish.
  • Live mainnet contract test — covered by the existing skipped smoke test (TestLiveMainnetSmoke) which auto-enables once ETH_RPC_URL is set (it is). Will exercise full integration once real addresses land.

Files changed (15)

  • src/agentgraph_bridge_erc8004/attestation_normalizer.py (NEW)
  • src/agentgraph_bridge_erc8004/score_ingest.py (NEW)
  • src/agentgraph_bridge_erc8004/DEPLOY.md (NEW)
  • src/agentgraph_bridge_erc8004/fixtures/README.md (NEW)
  • src/agentgraph_bridge_erc8004/fixtures/regen_fixtures.py (NEW)
  • src/agentgraph_bridge_erc8004/fixtures/{identity_basic,authority_tier_upgrade,continuity_behavioral}/*.json (NEW, 12 files)
  • src/agentgraph_bridge_erc8004/tests/test_attestation_normalizer.py (NEW)
  • src/agentgraph_bridge_erc8004/tests/test_score_ingest.py (NEW)
  • src/agentgraph_bridge_erc8004/tests/test_fixtures.py (NEW)
  • src/agentgraph_bridge_erc8004/models.py (fixed is_admissible tz bug)
  • src/agentgraph_bridge_erc8004/__init__.py (exports + version 0.1.0 → 0.2.0)
  • src/trust/score.py (+44 LoC: new helper, backward-compat preserved)

🤖 Generated with Claude Code

…e wiring

Day 3 of 3-day MVP per docs/internal/monday-may18-scope.md tasks #87 + #88.
Closes #87 + #88.

Shipped:

attestation_normalizer.py — parse CTEF envelope from ERC8004Entry.data
bytes, resolve provider's did:web to fetch Ed25519 JWKS, verify the
signature against JCS(envelope_without_signature), return
NormalizedAttestation. NormalizationError on any verification failure;
no unverified attestation ever reaches downstream code.

score_ingest.py — pure-function score derivation from a list of
NormalizedAttestation. Per-claim_type caps (identity 0.60, authority
0.25, continuity 0.15, transport 0.0). Provider diversity dominates
attestation count. blend_with_community_signals() takes max-of-two
between erc8004 + existing community-signal score (no additive
stacking, per weight isolation invariant in src/trust/score.py).
score_breakdown() returns diagnostic dict for observability.

src/trust/score.py — added _external_score_with_attestations() helper
that wraps existing _source_reputation_score(). Backward-compatible:
no attestations passed → identical output (verified: old=new=0.4009
for {stars: 100}). When attestations passed, lazy-imports the bridge
+ blends. The lazy import means the main backend doesn't take a
hard dep on web3 — bridge only loaded when caller passes attestations,
which only happens after the post-Day 3 sync job lands.

models.py — fixed Day 1 bug in NormalizedAttestation.is_admissible:
was comparing tz-aware expires_at against naive datetime.utcnow(),
raised TypeError. Now uses tz-aware datetime.now(timezone.utc) +
defensive normalization if expires_at comes in naive.

fixtures/ — 3 mainnet-shaped snapshot fixtures with deterministic
Ed25519 signatures:
  - identity_basic (claim_type=identity)
  - authority_tier_upgrade (ArkForge-shaped, row #8 v0.3.3 matrix)
  - continuity_behavioral (Dominion-shaped, row #5 v0.3.3 matrix)
Each has entry.json + envelope.json + jwks.json + expected_normalized.json.
regen_fixtures.py rebuilds them deterministically; test_fixtures.py
round-trips all 3 through normalize() and verifies match against
expected.

DEPLOY.md — production deployment guide. Covers EIP-8004 address
swap workflow, background sync job design (separate table, ~hourly
cron), failure modes, Alchemy cost estimates (~640K CU/month vs.
300M free tier limit = 0.2% utilization), rollback procedure.

Test results:
- 67 PASS, 1 skipped (live mainnet smoke; needs real EIP-8004 addrs)
- Backward-compat verified: _source_reputation_score({stars: 100})
  == _external_score_with_attestations({stars: 100}) == 0.4009
- Trust score behavior unchanged for entities without ERC-8004 atts

Version bump: agentgraph_bridge_erc8004 0.1.0 -> 0.2.0
@github-actions
Copy link
Copy Markdown

AgentGraph Trust Scan

Security Scan Grade: ? (0/100) — No summary available

Category Score

Findings: 0 critical, 0 high, 61 medium, 0 low

View full report | Add badge to README

This is a code security scan score. Full composite trust score (including identity verification and external signals) is available on AgentGraph.

@kenneives kenneives merged commit c0ef33f into main May 22, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant