Skip to content

feat(trust-score): v0.2 formula module + 67 unit tests (no integration yet) - #4

Open
jlromeiro wants to merge 1 commit into
mainfrom
feat/trust-score-v0.2-impl
Open

feat(trust-score): v0.2 formula module + 67 unit tests (no integration yet)#4
jlromeiro wants to merge 1 commit into
mainfrom
feat/trust-score-v0.2-impl

Conversation

@jlromeiro

Copy link
Copy Markdown
Collaborator

Summary

Adds lib/trust-score.js — a pure-function module implementing the v0.2 score formula from docs/rfc/x402-trust-score.md §5.1 — plus 67 unit tests that all pass.

No callsites are migrated in this PR. The legacy Math.min(100, paidCount * 5) calls in index.js, lib/agent-status.js, and lib/ratelimit.js are still in place. Integration is the next PR (WS-B part 2 in the internal plan).

This PR depends conceptually on #2 (the v0.2 RFC) but is independent at the file level — they don't touch the same files. Either can merge first.

What's in lib/trust-score.js

Five subscores all normalized to 0-100 BEFORE weighting (so published weights match effective contribution — RFC §5.1.2):

Subscore Weight Formula
P1 persistence: paid_count, log-scaled 0.30 min(100, log10(1 + paid_count) * 20)
P2 persistence: tenure 0.15 min(100, sqrt(months_in_network) * 12)
D2 distribution across providers 0.10 (1 − loyalty_concentration) * 100
H1 Laplace-smoothed no-dispute ratio 0.20 ((paid − disp + 1) / (paid + 2)) * 100
R1 recency 0.25 exp(−idle_days / 60) * 100

Plus:

  • H2 binary gate — any active fraud_flag forces score → 0
  • cross_provider_bonusmin(1.5, 1 + 0.1 * (N − 1)) where N is the count of distinct attesting operators (binary by operator, not weighted by volume — this is what gives small operators network leverage per the RFC)
  • Conditional renormalization (RFC §5.1.3) — when h1Active=false (Phase 1 default, no /report endpoint), drops H1 and renormalizes remaining weights to sum to 1.0. Silent-zero would lower the headline ceiling without changing published weights — auditability footgun avoided
  • extendReputationFields() — computes active_in_n_providers, loyalty_concentration, per_provider for the v0.2 ReputationRecord
  • defaultPolicy() — returns the JSON brokers SHOULD publish in GET /info

Test coverage

test/trust-score.test.js: 67 assertions, no I/O, follows the test/detection.test.js style (plain node test/<file>.test.js, no Jest).

— P1 (paid_count, log10 * 20) —          9 tests
— P2 (months_in_network, sqrt * 12) —    7 tests
— D2 ((1 - loyalty_concentration)) —     6 tests
— H1 (Laplace) —                         7 tests
— R1 (exp(-idle/60)) —                   7 tests
— cross_provider_bonus —                 8 tests
— distinctOperators / loyaltyConc. —     6 tests
— extendReputationFields —               3 tests
— Phase 1 (H1 inactive) composite —      6 tests
— Phase 1.5+ (H1 active) composite —     3 tests
— defaultPolicy —                        5 tests

Sanity-check scenarios for top/mid/new agents in both Phase 1 (H1 inactive, weights renormalized) AND Phase 1.5+ (H1 active with /report), with hand-computed expected values.

Wired into package.json: npm run test:trust-score (or runs as part of npm test).

What is NOT in this PR (intentionally)

Out of scope Reason Where it lands
index.js:375, 457, 1273, 1498 callsite changes Behavior-altering — needs a focused PR WS-B part 2
lib/agent-status.js:65, lib/ratelimit.js:70 Same as above WS-B part 2
GET /reputation/:pubkey v0.2 response shape Needs callsite migration first WS-B part 2
GET /info exposing score_components Same WS-B part 2
test/agent-status-handler.test.js:165 (asserts old behavior) Will break when callsites flip — update together WS-B part 2
/report endpoint Broker scope (RFC §4.3) WS-C
Broker extraction Roadmap Phase 1 WS-C
weight(p) formula (RFC §5.2) Broker concern, not Shield WS-C

Test plan

  • npm run test:trust-score → 67/67 passed (verified locally)
  • npm test → still passes (this PR adds new tests but does not modify any existing test or source code)
  • No regressions in lib/detection.js, lib/agent-status.js, lib/ratelimit.js, lib/store.js, lib/enforcement.js — none of those files are touched
  • lib/trust-score.js exports unchanged after merge (verified via git diff)
  • Reviewer sanity-checks: hand-compute one of the Phase 1.5+ scenarios in the test file, confirm formula matches the RFC §5.1 text literally

🤖 Generated with Claude Code

…n yet)

Implements docs/rfc/x402-trust-score.md §5.1 as a pure-function module.
Drop-in replacement for the legacy `Math.min(100, paidCount * 5)` callsites,
but NOT yet wired in — integration is the next chunk of WS-B.

What this commit adds:

  lib/trust-score.js
    - 5 normalized subscores (P1, P2, D2, H1, R1) all on the 0-100 scale
      so published weights match effective contribution
    - H1 uses Laplace (add-one) smoothing — paid=0/disp=0 yields 50, not
      100. New agents don't inherit perfect hygiene from absence of evidence
    - Conditional renormalization (RFC §5.1.3): when h1Active=false (Phase 1
      default, no /report endpoint), drops H1 and renormalizes remaining
      weights to sum to 1.0. Silent-zero would lower the headline ceiling
      without changing published weights — auditability footgun avoided
    - Cross-provider bonus: binary-by-operator multiplier capped at 1.5
      (RFC §5.1.4). Small operators contribute the same +0.1 increment as
      large ones — preserves network leverage for tier-2 operators
    - H2 binary gate: any active fraud_flag forces score → 0
    - extendReputationFields() computes active_in_n_providers,
      loyalty_concentration, and per_provider for the v0.2 ReputationRecord
    - defaultPolicy() returns the JSON brokers SHOULD publish in GET /info

  test/trust-score.test.js
    - 67 assertions, no I/O, follows the test/detection.test.js style
    - Each subscore has unit tests with hand-computed exact values
    - Composite scenarios covering Phase 1 (H1 inactive) AND Phase 1.5+
      (H1 active with /report) for top/mid/new agent profiles
    - Edge cases: null reputation, paid=0, H2 gate, empty attestations
    - defaultPolicy shape verification

  package.json
    - test:trust-score script wired into npm test pipeline

What is NOT in this commit (intentionally):

  - No changes to index.js (4 callsites: 375, 457, 1273, 1498 still use
    paid_count*5)
  - No changes to lib/agent-status.js (line 65) or lib/ratelimit.js (line 70)
  - No changes to GET /reputation/:pubkey response shape
  - No changes to GET /info to expose score_components
  - No /report endpoint, no broker extraction (those are WS-C/WS-D scope)
  - test/agent-status-handler.test.js still asserts the old paid_count*5
    behavior — will be updated in the integration commit when callsites flip

Test run: 67/67 passed via `npm run test:trust-score`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kilo-code-bot

kilo-code-bot Bot commented May 16, 2026

Copy link
Copy Markdown

Kilo Code Review could not run — your account is out of credits.

Add credits or switch to a free model to enable reviews on this change.

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