@reinconsole/graph 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/index.d.ts +374 -0
- package/dist/index.js +910 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rein contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @reinconsole/graph
|
|
2
|
+
|
|
3
|
+
Phase 3 of **[Rein](https://github.com/bugiiiii11/rein)** — the reputation graph. Guard receipts say what agents tried to spend; gate receipts say what vendors actually earned. The graph listens to both, scores every vendor and payer it has evidence on, and feeds the scores back into enforcement on both sides of the wire.
|
|
4
|
+
|
|
5
|
+
> **Status: v0.1 — early open-source infrastructure, live on testnet.** APIs may change before 1.0. See the live scoreboard: [Rein console](https://reinconsole-production.up.railway.app/).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @reinconsole/graph
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { ReputationGraph, payerCheck } from '@reinconsole/graph';
|
|
17
|
+
|
|
18
|
+
const graph = new ReputationGraph().observe(engine).observe(indexer).observe(gate);
|
|
19
|
+
|
|
20
|
+
// Agent side: pushed scores make `vendorReputationLt` policies fire.
|
|
21
|
+
await graph.syncVendors(engine.spend);
|
|
22
|
+
|
|
23
|
+
// Vendor side: confidently-bad wallets are turned away at the door.
|
|
24
|
+
createGate({ screen: { check: payerCheck(graph, { denyBelow: 40 }) }, /* ... */ });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## How it scores
|
|
28
|
+
|
|
29
|
+
- **Evidence, not vibes.** Per-subject history accumulates straight off the event buses — settlements, refusals (replays weighted heaviest), shadow spends, settled-money edges between counterparties, manual dispute/endorsement reports. Scores are **never stored**: they recompute from raw evidence on demand, so every score is explainable down to the counts behind it.
|
|
30
|
+
- **Five components + confidence.** Settlement reliability, dispute hygiene, volume, longevity, and one-hop counterparty quality, blended 0–100. Confidence is first-class: thin or brand-new history yields low confidence, not a fake number — and same-day evidence is discounted until it ages.
|
|
31
|
+
- **Unknown is not bad.** The evaluator never fires `vendorReputationLt` without data, `syncVendors` withholds scores under the confidence floor, and `payerCheck` passes wallets it knows nothing about. A newcomer is served; a *confidently* bad actor is refused.
|
|
32
|
+
- **No-fault refusals carry no evidence.** Rate limits, velocity caps, and rails outages at one vendor's gate never bleed into a payer's global score.
|
|
33
|
+
- **One identity, one history.** `graph.link(canonical, alias)` merges subjects across id spaces — an agent's engine ULID, its paying wallets, its on-chain [ERC-8004](https://www.npmjs.com/package/@reinconsole/erc8004) identity — so evidence follows the party, not the key. Key rotation never splits a score.
|
|
34
|
+
- **Run it as a service.** `buildGraphServer()` — remote producers `POST /v1/events`, anyone reads `GET /v1/scores/:kind/:subject` and gets the score *and* the evidence behind it. The durable variant (evidence survives restarts) lives in the [monorepo](https://github.com/bugiiiii11/rein) (`@reinconsole/store`).
|
|
35
|
+
|
|
36
|
+
Scoring weights are deliberately transparent v0.1 heuristics (`DEFAULT_WEIGHTS`, overridable) — scores are pure functions of the evidence ledger, so the model can evolve without migrations.
|
|
37
|
+
|
|
38
|
+
MIT © Rein contributors · [Repository](https://github.com/bugiiiii11/rein) · [Issues](https://github.com/bugiiiii11/rein/issues)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { ReputationSubject, ReputationComponents, ReinEvent, ReputationScore, Receipt } from '@reinconsole/core';
|
|
2
|
+
import { FastifyInstance } from 'fastify';
|
|
3
|
+
|
|
4
|
+
/** A value that may be produced synchronously or awaited (durable writes). */
|
|
5
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
6
|
+
/** One settled-money edge to a counterparty (the "graph" in reputation graph). */
|
|
7
|
+
interface CounterpartyLine {
|
|
8
|
+
settled: number;
|
|
9
|
+
/** Settled decimal volume across this edge. */
|
|
10
|
+
volume: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Everything the graph has observed about one subject. Counters are raw and
|
|
14
|
+
* append-only — scoring (scoring.ts) is a pure function over this record, so a
|
|
15
|
+
* score is always re-derivable and explainable from the evidence behind it.
|
|
16
|
+
*/
|
|
17
|
+
interface SubjectEvidence {
|
|
18
|
+
subject: ReputationSubject;
|
|
19
|
+
firstSeenMs: number;
|
|
20
|
+
lastSeenMs: number;
|
|
21
|
+
/** Payment attempts observed (allowed intents, presented payments). */
|
|
22
|
+
attempts: number;
|
|
23
|
+
/** Confirmed settlements (indexer-reconciled or gate-receipted). */
|
|
24
|
+
settled: number;
|
|
25
|
+
/** Settled decimal volume. */
|
|
26
|
+
volume: string;
|
|
27
|
+
/** Refusals by code (gate refusal codes, signer refusal codes). */
|
|
28
|
+
refusals: Record<string, number>;
|
|
29
|
+
/** Managed-wallet spend with no ALLOW decision behind it (bypass). */
|
|
30
|
+
shadowSpends: number;
|
|
31
|
+
/** Manual out-of-band reports (chargebacks, fraud complaints). */
|
|
32
|
+
disputes: number;
|
|
33
|
+
/** Manual out-of-band vouches. */
|
|
34
|
+
endorsements: number;
|
|
35
|
+
/** Who this subject has settled money with, keyed by subjectKey(). */
|
|
36
|
+
counterparties: Map<string, CounterpartyLine>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Canonical subject identity. Hosts and EVM addresses are case-insensitive,
|
|
40
|
+
* so they normalize to lowercase; ULID agent ids are case-sensitive and pass
|
|
41
|
+
* through. ERC-8004 ids (`eip155:{chainId}:{registry}/{tokenId}`) normalize
|
|
42
|
+
* via parse->format — @reinconsole/erc8004 always emits the canonical form, but this
|
|
43
|
+
* is the graph's OWN boundary (remote producers hit POST /v1/links with
|
|
44
|
+
* hand-built strings), and a checksummed registry or zero-padded tokenId
|
|
45
|
+
* variant must not mint a split subject row. Note the two id spaces under
|
|
46
|
+
* kind "agent": the engine names agents by ULID, a gate names them by paying
|
|
47
|
+
* wallet — ERC-8004 linking (S17) folds both into the on-chain identity.
|
|
48
|
+
*/
|
|
49
|
+
declare function normalizeSubject(subject: ReputationSubject): ReputationSubject;
|
|
50
|
+
declare function subjectKey(subject: ReputationSubject): string;
|
|
51
|
+
/**
|
|
52
|
+
* The evidence store seam the graph depends on. Writes may be asynchronous (a
|
|
53
|
+
* durable ledger persists behind them); reads are synchronous from the
|
|
54
|
+
* hydrated working set so `score()` / `payerCheck()` stay sync in the gate hot
|
|
55
|
+
* path. Mutations are explicit, atomic operations rather than a returned record
|
|
56
|
+
* the caller mutates — that is what lets a durable implementation persist them.
|
|
57
|
+
*/
|
|
58
|
+
interface EvidenceLedgerPort {
|
|
59
|
+
/** A payment attempt (allowed intent / presented payment). */
|
|
60
|
+
recordAttempt(subject: ReputationSubject, atMs: number): MaybePromise<void>;
|
|
61
|
+
/** A confirmed settlement between two subjects (credits both sides + the edge). */
|
|
62
|
+
recordSettlement(a: ReputationSubject, b: ReputationSubject, amount: string, atMs: number): MaybePromise<void>;
|
|
63
|
+
/** A refusal against a subject, by code. */
|
|
64
|
+
recordRefusal(subject: ReputationSubject, code: string, atMs: number): MaybePromise<void>;
|
|
65
|
+
/** Managed-wallet spend with no ALLOW behind it. */
|
|
66
|
+
recordShadowSpend(subject: ReputationSubject, atMs: number): MaybePromise<void>;
|
|
67
|
+
/** An out-of-band dispute. */
|
|
68
|
+
recordDispute(subject: ReputationSubject, atMs: number): MaybePromise<void>;
|
|
69
|
+
/** An out-of-band endorsement. */
|
|
70
|
+
recordEndorsement(subject: ReputationSubject, atMs: number): MaybePromise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Fold everything known about `alias` into `canonical` and forget the alias:
|
|
73
|
+
* counters sum, volumes add, first/last seen widen, and settled-money edges
|
|
74
|
+
* re-key on BOTH endpoints (peers that pointed at the alias now point at the
|
|
75
|
+
* canonical subject). No-op when the alias has no record — which makes
|
|
76
|
+
* re-asserting links at boot idempotent. This is the identity-linking
|
|
77
|
+
* primitive (ERC-8004: one on-chain identity, many addresses/ids).
|
|
78
|
+
*/
|
|
79
|
+
merge(canonical: ReputationSubject, alias: ReputationSubject): MaybePromise<void>;
|
|
80
|
+
get(subject: ReputationSubject): SubjectEvidence | undefined;
|
|
81
|
+
getByKey(key: string): SubjectEvidence | undefined;
|
|
82
|
+
all(): IterableIterator<SubjectEvidence>;
|
|
83
|
+
readonly size: number;
|
|
84
|
+
/**
|
|
85
|
+
* Await any pending durable writes (no-op in memory). Durable
|
|
86
|
+
* implementations surface write failures HERE — the graph fire-and-forgets
|
|
87
|
+
* its writes (a bus handler cannot await), so flush() is the error channel.
|
|
88
|
+
*/
|
|
89
|
+
flush?(): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
/** The evidence store: one record per subject, created on first sighting. */
|
|
92
|
+
declare class EvidenceLedger implements EvidenceLedgerPort {
|
|
93
|
+
private readonly records;
|
|
94
|
+
recordAttempt(subject: ReputationSubject, atMs: number): void;
|
|
95
|
+
recordSettlement(a: ReputationSubject, b: ReputationSubject, amount: string, atMs: number): void;
|
|
96
|
+
recordRefusal(subject: ReputationSubject, code: string, atMs: number): void;
|
|
97
|
+
recordShadowSpend(subject: ReputationSubject, atMs: number): void;
|
|
98
|
+
recordDispute(subject: ReputationSubject, atMs: number): void;
|
|
99
|
+
recordEndorsement(subject: ReputationSubject, atMs: number): void;
|
|
100
|
+
merge(canonical: ReputationSubject, alias: ReputationSubject): void;
|
|
101
|
+
get(subject: ReputationSubject): SubjectEvidence | undefined;
|
|
102
|
+
getByKey(key: string): SubjectEvidence | undefined;
|
|
103
|
+
all(): IterableIterator<SubjectEvidence>;
|
|
104
|
+
get size(): number;
|
|
105
|
+
/**
|
|
106
|
+
* Insert a fully-formed record verbatim — the hydration primitive a durable
|
|
107
|
+
* store uses to rebuild the working set on open. Bypasses the live counters
|
|
108
|
+
* (the record already holds its accumulated state and timestamps).
|
|
109
|
+
*/
|
|
110
|
+
load(record: SubjectEvidence): void;
|
|
111
|
+
/** Get-or-create the record for a subject and stamp first/last seen. */
|
|
112
|
+
private touch;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* What an `intent.created` event tells us, kept until the matching
|
|
117
|
+
* `payment.settled` (which carries only an intentId) can be attributed.
|
|
118
|
+
*/
|
|
119
|
+
interface IntentFacts {
|
|
120
|
+
agentId: string;
|
|
121
|
+
host: string;
|
|
122
|
+
amount: string;
|
|
123
|
+
}
|
|
124
|
+
declare const DEFAULT_CORRELATION_LIMIT = 10000;
|
|
125
|
+
/**
|
|
126
|
+
* Bridges `intent.created` -> later `payment.settled`/`decision.made`. A
|
|
127
|
+
* `payment.settled` event names only an intentId, so the graph remembers every
|
|
128
|
+
* undecided intent until it settles. Bounded (oldest-evicted) so undecided
|
|
129
|
+
* intents that never settle cannot leak. Writes may be async (a durable store
|
|
130
|
+
* persists them so an in-flight intent survives a restart); reads are sync.
|
|
131
|
+
*/
|
|
132
|
+
interface IntentCorrelationPort {
|
|
133
|
+
remember(intentId: string, facts: IntentFacts): MaybePromise<void>;
|
|
134
|
+
/** Read the facts without consuming them (decision.made reads, doesn't burn). */
|
|
135
|
+
peek(intentId: string): IntentFacts | undefined;
|
|
136
|
+
/** Read and consume (payment.settled attributes once, then forgets). */
|
|
137
|
+
take(intentId: string): IntentFacts | undefined;
|
|
138
|
+
readonly size: number;
|
|
139
|
+
/**
|
|
140
|
+
* Await any pending durable writes (no-op in memory). Durable
|
|
141
|
+
* implementations surface write failures HERE — the graph fire-and-forgets
|
|
142
|
+
* its writes, so flush() is the error channel.
|
|
143
|
+
*/
|
|
144
|
+
flush?(): Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
/** In-memory correlation map with FIFO eviction once `limit` is reached. */
|
|
147
|
+
declare class InMemoryIntentStore implements IntentCorrelationPort {
|
|
148
|
+
private readonly limit;
|
|
149
|
+
private readonly intents;
|
|
150
|
+
constructor(limit?: number);
|
|
151
|
+
remember(intentId: string, facts: IntentFacts): void;
|
|
152
|
+
peek(intentId: string): IntentFacts | undefined;
|
|
153
|
+
take(intentId: string): IntentFacts | undefined;
|
|
154
|
+
get size(): number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Component weights, summing to 1. The blend is a deliberately transparent
|
|
159
|
+
* heuristic: every component is 0–100 with higher = healthier, so a score is
|
|
160
|
+
* readable straight off its explanation. Sophistication can replace this
|
|
161
|
+
* later without touching the evidence model — scores are pure functions of
|
|
162
|
+
* evidence, never stored.
|
|
163
|
+
*/
|
|
164
|
+
interface ScoreWeights {
|
|
165
|
+
volume: number;
|
|
166
|
+
longevity: number;
|
|
167
|
+
disputeRate: number;
|
|
168
|
+
counterpartyQuality: number;
|
|
169
|
+
settlementReliability: number;
|
|
170
|
+
}
|
|
171
|
+
declare const DEFAULT_WEIGHTS: ScoreWeights;
|
|
172
|
+
/** Saturating settled-transaction volume: 0 at none, ~63 at 20, ~92 at 50. */
|
|
173
|
+
declare function volumeComponent(ev: SubjectEvidence): number;
|
|
174
|
+
/** How long the subject has been known: linear to 100 at 30 days. */
|
|
175
|
+
declare function longevityComponent(ev: SubjectEvidence, nowMs: number): number;
|
|
176
|
+
/**
|
|
177
|
+
* Dispute hygiene (the core field is named `disputeRate`; the component is
|
|
178
|
+
* stored INVERTED — 100 = a clean record — so all five components blend in
|
|
179
|
+
* the same direction). Weighted badness over total interactions; manual
|
|
180
|
+
* endorsements buy back a little slack.
|
|
181
|
+
*/
|
|
182
|
+
declare function disputeComponent(ev: SubjectEvidence): number;
|
|
183
|
+
/** Settled / attempted. No attempts yet = neutral 50, not perfect 100. */
|
|
184
|
+
declare function reliabilityComponent(ev: SubjectEvidence): number;
|
|
185
|
+
/**
|
|
186
|
+
* Confidence is first-class (see core ReputationScore): a thin or brand-new
|
|
187
|
+
* history yields LOW confidence rather than a misleading score, so consumers
|
|
188
|
+
* (engine sync, gate screening) can refuse to act on it. Depth of evidence
|
|
189
|
+
* saturates around 10 observations; age discounts same-day evidence to 40%
|
|
190
|
+
* and stops mattering after a week.
|
|
191
|
+
*/
|
|
192
|
+
declare function confidence(ev: SubjectEvidence, nowMs: number): number;
|
|
193
|
+
/** Blend all five components into the headline 0–100 score. */
|
|
194
|
+
declare function blend(components: ReputationComponents, weights: ScoreWeights): number;
|
|
195
|
+
/**
|
|
196
|
+
* The one-hop base score: the blend WITHOUT counterpartyQuality, with the
|
|
197
|
+
* remaining weights renormalized. Counterparty quality for subject A averages
|
|
198
|
+
* the base scores of A's counterparties — base, not full, so the computation
|
|
199
|
+
* is a single hop and cycles (A pays B pays A) cannot recurse.
|
|
200
|
+
*/
|
|
201
|
+
declare function blendBase(ev: SubjectEvidence, nowMs: number, weights: ScoreWeights): number;
|
|
202
|
+
|
|
203
|
+
/** Anything with a Rein event bus: engine, indexer, gate, signer. */
|
|
204
|
+
interface EventSource {
|
|
205
|
+
onEvent(handler: (event: ReinEvent) => void): void;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Where vendor scores land. `PolicyEngine.spend` satisfies this structurally,
|
|
209
|
+
* so `graph.syncVendors(engine.spend)` closes the loop — durable when the
|
|
210
|
+
* engine runs on @reinconsole/store.
|
|
211
|
+
*/
|
|
212
|
+
interface VendorReputationSink {
|
|
213
|
+
setVendorReputation(host: string, score: number): void | Promise<void>;
|
|
214
|
+
}
|
|
215
|
+
interface ReputationGraphOptions {
|
|
216
|
+
weights?: Partial<ScoreWeights>;
|
|
217
|
+
/** Injectable clock (scores are functions of evidence AND time). */
|
|
218
|
+
now?: () => Date;
|
|
219
|
+
/**
|
|
220
|
+
* Max undecided intents remembered for settlement correlation. Ignored when
|
|
221
|
+
* a custom `intents` store is injected (that store owns its own bound).
|
|
222
|
+
*/
|
|
223
|
+
correlationLimit?: number;
|
|
224
|
+
/**
|
|
225
|
+
* Durable evidence ledger. Defaults to in-memory; @reinconsole/store provides a
|
|
226
|
+
* PGlite-backed one so scores survive a restart.
|
|
227
|
+
*/
|
|
228
|
+
ledger?: EvidenceLedgerPort;
|
|
229
|
+
/** Durable intent correlation store. Defaults to in-memory (bounded by `correlationLimit`). */
|
|
230
|
+
intents?: IntentCorrelationPort;
|
|
231
|
+
}
|
|
232
|
+
interface ManualReport {
|
|
233
|
+
subject: ReputationSubject;
|
|
234
|
+
kind: 'dispute' | 'endorsement';
|
|
235
|
+
/** When the incident happened (defaults to now). */
|
|
236
|
+
at?: Date;
|
|
237
|
+
note?: string;
|
|
238
|
+
}
|
|
239
|
+
interface SyncedVendorScore {
|
|
240
|
+
host: string;
|
|
241
|
+
score: number;
|
|
242
|
+
confidence: number;
|
|
243
|
+
}
|
|
244
|
+
interface SyncOptions {
|
|
245
|
+
/**
|
|
246
|
+
* Scores below this confidence are NOT pushed — the evaluator treats an
|
|
247
|
+
* unknown reputation as indeterminate (never triggers vendorReputationLt),
|
|
248
|
+
* and a thin history must stay unknown rather than condemn a newcomer.
|
|
249
|
+
*/
|
|
250
|
+
minConfidence?: number;
|
|
251
|
+
}
|
|
252
|
+
/** A score plus the raw evidence it was derived from, render-ready. */
|
|
253
|
+
interface ReputationExplanation {
|
|
254
|
+
score: ReputationScore;
|
|
255
|
+
weights: ScoreWeights;
|
|
256
|
+
evidence: {
|
|
257
|
+
attempts: number;
|
|
258
|
+
settled: number;
|
|
259
|
+
volume: string;
|
|
260
|
+
refusals: Record<string, number>;
|
|
261
|
+
shadowSpends: number;
|
|
262
|
+
disputes: number;
|
|
263
|
+
endorsements: number;
|
|
264
|
+
firstSeen: Date;
|
|
265
|
+
lastSeen: Date;
|
|
266
|
+
counterparties: {
|
|
267
|
+
subject: ReputationSubject;
|
|
268
|
+
settled: number;
|
|
269
|
+
volume: string;
|
|
270
|
+
}[];
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The Rein reputation graph (Phase 3). Subscribes to the event buses both
|
|
275
|
+
* sides already publish — the engine/indexer name vendors by host and agents
|
|
276
|
+
* by id; a gate names payers by wallet — and accumulates per-subject evidence:
|
|
277
|
+
* settlements, refusals, shadow spends, manual disputes, and settled-money
|
|
278
|
+
* edges between counterparties. Scores are computed on demand (never stored)
|
|
279
|
+
* from that evidence, so every number is explainable, and they feed back into
|
|
280
|
+
* enforcement on both sides of the wire:
|
|
281
|
+
*
|
|
282
|
+
* graph.syncVendors(engine.spend) -> policies with vendorReputationLt fire
|
|
283
|
+
* screen: { check: payerCheck(graph) } -> a gate turns away low-rep wallets
|
|
284
|
+
*
|
|
285
|
+
* Event mapping notes: engine-side evidence keys agents by ULID and vendors by
|
|
286
|
+
* host; gate-side evidence keys payers by wallet and recipients by payTo
|
|
287
|
+
* address — distinct subjects, so feeding one graph from BOTH buses (the
|
|
288
|
+
* console world) never double-counts. `payment.settled` carries only an
|
|
289
|
+
* intentId, so the graph keeps a bounded intent.created correlation map.
|
|
290
|
+
* Denied decisions are deliberately NOT held against anyone: a deny means
|
|
291
|
+
* policy worked, not that the agent or vendor misbehaved.
|
|
292
|
+
*/
|
|
293
|
+
declare class ReputationGraph {
|
|
294
|
+
private readonly ledger;
|
|
295
|
+
private readonly intents;
|
|
296
|
+
private readonly weights;
|
|
297
|
+
private readonly now;
|
|
298
|
+
/** aliasKey -> canonical subject. Links are derived state (an agent registry
|
|
299
|
+
* or ERC-8004 lookup knows them) — re-assert at boot; merges are idempotent. */
|
|
300
|
+
private readonly aliases;
|
|
301
|
+
constructor(options?: ReputationGraphOptions);
|
|
302
|
+
/** Subscribe to a bus. Returns `this` so construction chains. */
|
|
303
|
+
observe(source: EventSource): this;
|
|
304
|
+
/**
|
|
305
|
+
* Fire-and-forget a port write: bus handlers cannot await, and a rejecting
|
|
306
|
+
* durable write must never become an unhandled rejection (Node kills the
|
|
307
|
+
* process). Failures are the port's to surface — flush() throws them.
|
|
308
|
+
*/
|
|
309
|
+
private fire;
|
|
310
|
+
/**
|
|
311
|
+
* Declare that `alias` is the same real-world party as `canonical` (the
|
|
312
|
+
* ERC-8004 identity story: one identity, many addresses/ids — an engine
|
|
313
|
+
* agent's ULID and its paying wallet, or a vendor's host and its payTo
|
|
314
|
+
* address). Everything already known about the alias is folded into the
|
|
315
|
+
* canonical subject, and all future evidence and lookups for the alias
|
|
316
|
+
* resolve to it. Idempotent — re-asserting known links at boot is free.
|
|
317
|
+
*/
|
|
318
|
+
link(canonical: ReputationSubject, alias: ReputationSubject): void;
|
|
319
|
+
/** The canonical subject for a possibly-aliased one. */
|
|
320
|
+
private resolve;
|
|
321
|
+
ingest(event: ReinEvent): void;
|
|
322
|
+
/**
|
|
323
|
+
* Agent-side alternative to observing the engine bus: feed the SDK guard's
|
|
324
|
+
* onReceipt straight in. Connect events OR receipts per side, not both —
|
|
325
|
+
* a receipt restates the decision/settlement the bus already carried.
|
|
326
|
+
*/
|
|
327
|
+
ingestReceipt(receipt: Receipt): void;
|
|
328
|
+
/** Record an out-of-band dispute or endorsement against a subject. */
|
|
329
|
+
report(input: ManualReport): void;
|
|
330
|
+
/** Await any pending durable writes (no-op for in-memory stores). */
|
|
331
|
+
flush(): Promise<void>;
|
|
332
|
+
/** The score for one subject, or undefined when nothing is known (fairness).
|
|
333
|
+
* Aliased subjects resolve to their canonical identity — a linked wallet
|
|
334
|
+
* answers with the merged history, on both sides of the wire. */
|
|
335
|
+
score(subject: ReputationSubject): ReputationScore | undefined;
|
|
336
|
+
/** All known scores, best first. */
|
|
337
|
+
scores(kind?: ReputationSubject['kind']): ReputationScore[];
|
|
338
|
+
/** The score plus the raw evidence behind it. */
|
|
339
|
+
explain(subject: ReputationSubject): ReputationExplanation | undefined;
|
|
340
|
+
/**
|
|
341
|
+
* Push every vendor score that clears the confidence floor into the engine's
|
|
342
|
+
* spend store (or any sink). Returns what was pushed. Low-confidence scores
|
|
343
|
+
* are withheld so `vendorReputationLt` keeps treating thin histories as
|
|
344
|
+
* unknown — the evaluator's no-data-no-trigger rule stays intact end to end.
|
|
345
|
+
*/
|
|
346
|
+
syncVendors(sink: VendorReputationSink, options?: SyncOptions): Promise<SyncedVendorScore[]>;
|
|
347
|
+
/** Number of subjects with evidence. */
|
|
348
|
+
subjects(): number;
|
|
349
|
+
private scoreOf;
|
|
350
|
+
/** One-hop mean of the counterparties' base scores; neutral 50 when alone. */
|
|
351
|
+
private counterpartyQuality;
|
|
352
|
+
}
|
|
353
|
+
interface PayerCheckOptions {
|
|
354
|
+
/** Refuse payers scoring below this (default 40). */
|
|
355
|
+
denyBelow?: number;
|
|
356
|
+
/** Ignore scores below this confidence (default 0.3) — newcomers pass. */
|
|
357
|
+
minConfidence?: number;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Reputation-driven gate screening: plugs into @reinconsole/gate's `screen.check`.
|
|
361
|
+
* Unknown wallets and thin histories pass (same fairness rule as the engine
|
|
362
|
+
* sync); a confident low score is turned away at the door, before any
|
|
363
|
+
* facilitator round-trip.
|
|
364
|
+
*/
|
|
365
|
+
declare function payerCheck(graph: ReputationGraph, options?: PayerCheckOptions): (payer: string) => string | undefined;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Build the graph HTTP API. Remote producers POST their events here; anyone
|
|
369
|
+
* can read scores with the evidence behind them. Pass a graph for tests, or
|
|
370
|
+
* let it create a fresh in-memory one.
|
|
371
|
+
*/
|
|
372
|
+
declare function buildGraphServer(graph?: ReputationGraph): FastifyInstance;
|
|
373
|
+
|
|
374
|
+
export { type CounterpartyLine, DEFAULT_CORRELATION_LIMIT, DEFAULT_WEIGHTS, type EventSource, EvidenceLedger, type EvidenceLedgerPort, InMemoryIntentStore, type IntentCorrelationPort, type IntentFacts, type ManualReport, type MaybePromise, type PayerCheckOptions, type ReputationExplanation, ReputationGraph, type ReputationGraphOptions, type ScoreWeights, type SubjectEvidence, type SyncOptions, type SyncedVendorScore, type VendorReputationSink, blend, blendBase, buildGraphServer, confidence, disputeComponent, longevityComponent, normalizeSubject, payerCheck, reliabilityComponent, subjectKey, volumeComponent };
|