feat(bridge): ERC-8004 Day 3 — normalizer + score_ingest + trust score wiring#23
Merged
Merged
Conversation
…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
AgentGraph Trust ScanSecurity Scan Grade: ? (0/100) — No summary available
Findings: 0 critical, 0 high, 61 medium, 0 low View full report | Add badge to README
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 signaturescore_ingest.score(attestations)— derives 0-1 contribution with per-claim_type caps + provider-diversity weightingsrc/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
attestation_normalizer.pyscore_ingest.pysrc/trust/score.py_external_score_with_attestations()helper. Lazy bridge import. Backward-compat verified.fixtures/*DEPLOY.mdtests/test_attestation_normalizer.pytests/test_score_ingest.pytests/test_fixtures.pyTest results
Backward-compatibility verified
Architectural decisions worth eyeballing
Hard signature requirement —
NormalizationErroron any signature/JWKS/envelope failure. No partial-trust shortcuts. The trust score never sees an unverified attestation.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.
Provider diversity dominates count — 3 attestations from 3 distinct providers > 10 from 1. Prevents Sybil-style score inflation from one issuer spam-attesting.
Max-of-two blend, not additive —
blend_with_community_signals()returnsmax(erc, community). Additive blending would let an entity stack on-chain + GitHub signals beyond the 0.40 weight isolation invariant.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.Day 1 bug fixed inline —
NormalizedAttestation.is_admissiblewas comparing tz-awareexpires_atto naivedatetime.utcnow()→ TypeError. Now uses tz-awaredatetime.now(timezone.utc)+ defensive normalization.What's NOT in this PR (post-MVP)
.env. Bridge is functionally inert until they land (anyread_entryreturnsRegistryReadError). When they're verified on mainnet, the deploy guide inDEPLOY.mdcovers the one-liner swap.erc8004_attestationsDB cache for the trust recompute job to read. Schema proposal inDEPLOY.md. Not blocking v0.3.2 publish.TestLiveMainnetSmoke) which auto-enables onceETH_RPC_URLis 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