@xnetjs/abuse 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris Smothers
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,67 @@
1
+ # @xnetjs/abuse
2
+
3
+ Composable abuse, moderation, and reach policy decisions for xNet.
4
+
5
+ This package is intentionally dependency-light so lower-level packages such as
6
+ `@xnetjs/sync`, `@xnetjs/network`, and `@xnetjs/hub` can adopt the decision types
7
+ without depending on `@xnetjs/data`.
8
+
9
+ ## Core Idea
10
+
11
+ Adapters gather facts. Pure functions make decisions.
12
+
13
+ ```typescript
14
+ import { decidePublicInteraction } from '@xnetjs/abuse'
15
+
16
+ const decision = decidePublicInteraction({
17
+ actor: { firstContact: true },
18
+ labels: [{ value: 'spam', confidence: 0.6, sourceWeight: 1, sourceDID: 'did:key:z...' }],
19
+ quality: { slopScore: 0.4 }
20
+ })
21
+
22
+ console.log(decision.admission) // "quarantine"
23
+ ```
24
+
25
+ ## Current Scope
26
+
27
+ - Shared decision/result types
28
+ - Deterministic reason codes
29
+ - Pure decision helpers for transport, remote mutation, public interaction, and reach
30
+ - Adapter helpers under `@xnetjs/abuse/adapters`
31
+ - Explanation helpers for UI/devtools/audit surfaces
32
+ - Fixtures for tests and future package adapters
33
+
34
+ ## Adapter Entry Point
35
+
36
+ Protocol packages should keep their local verification, rate-limit, and scoring code local,
37
+ then adapt the result into shared abuse facts:
38
+
39
+ ```typescript
40
+ import { createRemoteAdmissionPipeline } from '@xnetjs/abuse/adapters'
41
+
42
+ const pipeline = createRemoteAdmissionPipeline({
43
+ adapt: (event: { signatureValid: boolean; overSizeLimit: boolean; peerScore: number }) => ({
44
+ surface: 'remoteMutation',
45
+ crypto: {
46
+ signatureValid: event.signatureValid
47
+ },
48
+ resource: {
49
+ overSizeLimit: event.overSizeLimit
50
+ },
51
+ actor: {
52
+ peerScore: event.peerScore
53
+ }
54
+ })
55
+ })
56
+
57
+ const result = pipeline.evaluate({
58
+ signatureValid: false,
59
+ overSizeLimit: false,
60
+ peerScore: 100
61
+ })
62
+
63
+ console.log(result.shouldMutate) // false
64
+ ```
65
+
66
+ Signed moderation schemas live in future `@xnetjs/data` work so this package can stay
67
+ safe for lower-level imports.
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @xnetjs/abuse - Shared abuse decision types.
3
+ */
4
+ type AbuseSurface = 'transport' | 'remoteMutation' | 'commentThread' | 'messageInbox' | 'searchIndex' | 'feed' | 'crawl' | 'localApi';
5
+ type PolicyScope = 'user' | 'workspace' | 'community' | 'hub' | 'appView' | 'protocol';
6
+ type AbuseAdmission = 'accept' | 'reject' | 'quarantine';
7
+ type AbuseVisibility = 'show' | 'warn' | 'blur' | 'hide';
8
+ type AbuseReach = 'normal' | 'demote' | 'exclude';
9
+ type AbuseResource = 'normal' | 'throttle' | 'block-peer' | 'require-budget';
10
+ type AbuseSeverity = 'low' | 'medium' | 'high' | 'critical';
11
+ type AbuseReviewQueue = 'safety' | 'quality' | 'appeal' | 'operator';
12
+ type AbuseReasonCode = 'accepted' | 'blocked-by-policy' | 'budget-required' | 'failed-admission' | 'first-contact' | 'invalid-doc-binding' | 'invalid-freshness' | 'invalid-hash' | 'invalid-signature' | 'low-confidence-quality-signal' | 'over-rate-limit' | 'over-size-limit' | 'peer-score-block' | 'peer-score-throttle' | 'quality-risk' | 'trusted-abuse-label' | 'trusted-warning-label' | 'unauthorized' | 'unsigned-update' | 'user-override';
13
+ type AbuseLabel = {
14
+ value: string;
15
+ sourceDID?: string;
16
+ sourceWeight: number;
17
+ confidence: number;
18
+ expiresAt?: number;
19
+ evidenceRefs?: readonly string[];
20
+ };
21
+ type AbuseCryptoFacts = {
22
+ hashValid: boolean;
23
+ signatureValid: boolean;
24
+ authorized: boolean;
25
+ freshnessValid: boolean;
26
+ docBindingValid: boolean;
27
+ };
28
+ type AbuseResourceFacts = {
29
+ overSizeLimit: boolean;
30
+ overRateLimit: boolean;
31
+ estimatedCost: number;
32
+ budgetRemaining: number | null;
33
+ };
34
+ type AbuseActorFacts = {
35
+ did?: string;
36
+ peerId?: string;
37
+ firstContact: boolean;
38
+ peerScore: number;
39
+ localBlocked: boolean;
40
+ workspaceBlocked: boolean;
41
+ hubBlocked: boolean;
42
+ };
43
+ type AbuseQualitySignals = {
44
+ duplicateScore: number;
45
+ slopScore: number;
46
+ citationCoverage: number;
47
+ provenanceScore: number;
48
+ };
49
+ type AbusePolicyFacts = {
50
+ peerScoreBlockThreshold: number;
51
+ peerScoreThrottleThreshold: number;
52
+ abuseLabelHideThreshold: number;
53
+ abuseLabelWarnThreshold: number;
54
+ qualityReviewThreshold: number;
55
+ qualityWarnThreshold: number;
56
+ quarantineFirstContact: boolean;
57
+ };
58
+ type AbuseDecisionOverride = Partial<Pick<AbuseDecision, 'visibility' | 'reach' | 'notify' | 'includeInCounters' | 'includeInSearch'>> & {
59
+ reason?: string;
60
+ };
61
+ type AbuseFacts = {
62
+ surface: AbuseSurface;
63
+ crypto?: Partial<AbuseCryptoFacts>;
64
+ resource?: Partial<AbuseResourceFacts>;
65
+ actor?: Partial<AbuseActorFacts>;
66
+ labels?: readonly AbuseLabel[];
67
+ quality?: Partial<AbuseQualitySignals>;
68
+ policy?: Partial<AbusePolicyFacts>;
69
+ override?: AbuseDecisionOverride;
70
+ now?: number;
71
+ };
72
+ type NormalizedAbuseFacts = {
73
+ surface: AbuseSurface;
74
+ crypto: AbuseCryptoFacts;
75
+ resource: AbuseResourceFacts;
76
+ actor: AbuseActorFacts;
77
+ labels: readonly AbuseLabel[];
78
+ quality: AbuseQualitySignals;
79
+ policy: AbusePolicyFacts;
80
+ override?: AbuseDecisionOverride;
81
+ now: number;
82
+ };
83
+ type PendingLabel = {
84
+ value: string;
85
+ confidence: number;
86
+ reason: AbuseReasonCode;
87
+ evidenceRefs: readonly string[];
88
+ };
89
+ type PendingSecurityEvent = {
90
+ eventName: string;
91
+ severity: AbuseSeverity;
92
+ reason: AbuseReasonCode;
93
+ };
94
+ type AbuseReviewDecision = {
95
+ required: false;
96
+ } | {
97
+ required: true;
98
+ queue: AbuseReviewQueue;
99
+ priority: number;
100
+ };
101
+ type AbuseDecision = {
102
+ admission: AbuseAdmission;
103
+ visibility: AbuseVisibility;
104
+ reach: AbuseReach;
105
+ resource: AbuseResource;
106
+ notify: boolean;
107
+ includeInCounters: boolean;
108
+ includeInSearch: boolean;
109
+ review: AbuseReviewDecision;
110
+ reasons: readonly AbuseReasonCode[];
111
+ evidenceRefs: readonly string[];
112
+ labelsToEmit: readonly PendingLabel[];
113
+ telemetry: readonly PendingSecurityEvent[];
114
+ };
115
+ type DecisionExplanationReason = {
116
+ code: AbuseReasonCode;
117
+ severity: AbuseSeverity;
118
+ message: string;
119
+ };
120
+ type DecisionExplanation = {
121
+ summary: string;
122
+ reasons: readonly DecisionExplanationReason[];
123
+ };
124
+
125
+ /**
126
+ * Privacy-preserving telemetry helpers for abuse decisions.
127
+ */
128
+
129
+ type AbusePeerScoreBucket = 'unknown' | '<=10' | '11-30' | '31-50' | '51-80' | '81-100' | '>100';
130
+ type AbuseTelemetryReporter = {
131
+ reportSecurityEvent(eventName: string, severity: AbuseSeverity, details?: Record<string, unknown>): unknown;
132
+ reportUsage?(metricName: string, value: number): unknown;
133
+ };
134
+ type RemoteMutationRejectionTelemetry = {
135
+ eventName: string;
136
+ severity: AbuseSeverity;
137
+ details: {
138
+ actionTaken: 'remote_mutation_rejected';
139
+ surface: AbuseSurface;
140
+ primaryReason: AbuseReasonCode;
141
+ reasons: readonly AbuseReasonCode[];
142
+ peerHash: string;
143
+ peerScoreBucket: AbusePeerScoreBucket;
144
+ resourceAction: AbuseDecision['resource'];
145
+ shouldThrottle: boolean;
146
+ };
147
+ };
148
+ type RemoteMutationRejectionTelemetryInput = {
149
+ facts: AbuseFacts;
150
+ decision: AbuseDecision;
151
+ eventName?: string;
152
+ peerHashSalt?: string;
153
+ };
154
+ declare function bucketAbusePeerScore(score: number | null | undefined): AbusePeerScoreBucket;
155
+ declare function hashAbusePeerIdentifier(peerId: string | null | undefined, salt?: string): string;
156
+ declare function createRemoteMutationRejectionTelemetry(input: RemoteMutationRejectionTelemetryInput): RemoteMutationRejectionTelemetry | null;
157
+ declare function reportRemoteMutationRejection(telemetry: AbuseTelemetryReporter | undefined, input: RemoteMutationRejectionTelemetryInput): boolean;
158
+
159
+ /**
160
+ * Adapter helpers for wiring local package events into abuse decisions.
161
+ */
162
+
163
+ type AbuseFactAdapter<TInput> = (input: TInput) => AbuseFacts;
164
+ type AbuseDecisionFunction = (facts: AbuseFacts) => AbuseDecision;
165
+ type AbuseAdapterResult = {
166
+ facts: AbuseFacts;
167
+ decision: AbuseDecision;
168
+ };
169
+ declare function createAbuseFactAdapter<TInput>(adapter: AbuseFactAdapter<TInput>): AbuseFactAdapter<TInput>;
170
+ declare function decideWithAdapter<TInput>(input: TInput, adapter: AbuseFactAdapter<TInput>, decide?: AbuseDecisionFunction): AbuseAdapterResult;
171
+ declare function createAbuseDecisionAdapter<TInput>(adapter: AbuseFactAdapter<TInput>, decide?: AbuseDecisionFunction): (input: TInput) => AbuseAdapterResult;
172
+ type RemoteAdmissionResult = AbuseAdapterResult & {
173
+ accepted: boolean;
174
+ shouldMutate: boolean;
175
+ shouldRelay: boolean;
176
+ shouldThrottle: boolean;
177
+ };
178
+ type RemoteAdmissionPipeline<TInput> = {
179
+ evaluate(input: TInput): RemoteAdmissionResult;
180
+ };
181
+ type RemoteAdmissionPipelineOptions<TInput> = {
182
+ adapt: AbuseFactAdapter<TInput>;
183
+ decide?: AbuseDecisionFunction;
184
+ telemetry?: AbuseTelemetryReporter;
185
+ telemetryEventName?: string;
186
+ telemetryPeerHashSalt?: string;
187
+ };
188
+ declare function createRemoteAdmissionPipeline<TInput>(options: RemoteAdmissionPipelineOptions<TInput>): RemoteAdmissionPipeline<TInput>;
189
+
190
+ export { type AbuseLabel as A, type AbuseAdmission as B, type AbuseCryptoFacts as C, type DecisionExplanation as D, type AbuseDecisionOverride as E, type AbuseResourceFacts as F, type AbuseReviewDecision as G, type AbuseSeverity as H, type AbuseVisibility as I, type PendingLabel as J, type PendingSecurityEvent as K, type NormalizedAbuseFacts as N, type PolicyScope as P, type RemoteAdmissionPipeline as R, type AbuseFacts as a, type AbuseDecision as b, type AbuseReasonCode as c, type DecisionExplanationReason as d, type AbuseSurface as e, type AbuseQualitySignals as f, type AbuseReviewQueue as g, type AbuseResource as h, createAbuseDecisionAdapter as i, createAbuseFactAdapter as j, createRemoteAdmissionPipeline as k, decideWithAdapter as l, bucketAbusePeerScore as m, createRemoteMutationRejectionTelemetry as n, hashAbusePeerIdentifier as o, type AbuseAdapterResult as p, type AbuseDecisionFunction as q, reportRemoteMutationRejection as r, type AbuseFactAdapter as s, type RemoteAdmissionPipelineOptions as t, type RemoteAdmissionResult as u, type AbusePeerScoreBucket as v, type AbuseTelemetryReporter as w, type RemoteMutationRejectionTelemetry as x, type RemoteMutationRejectionTelemetryInput as y, type AbuseActorFacts as z };
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @xnetjs/abuse - Shared abuse decision types.
3
+ */
4
+ type AbuseSurface = 'transport' | 'remoteMutation' | 'commentThread' | 'messageInbox' | 'searchIndex' | 'feed' | 'crawl' | 'localApi';
5
+ type PolicyScope = 'user' | 'workspace' | 'community' | 'hub' | 'appView' | 'protocol';
6
+ type AbuseAdmission = 'accept' | 'reject' | 'quarantine';
7
+ type AbuseVisibility = 'show' | 'warn' | 'blur' | 'hide';
8
+ type AbuseReach = 'normal' | 'demote' | 'exclude';
9
+ type AbuseResource = 'normal' | 'throttle' | 'block-peer' | 'require-budget';
10
+ type AbuseSeverity = 'low' | 'medium' | 'high' | 'critical';
11
+ type AbuseReviewQueue = 'safety' | 'quality' | 'appeal' | 'operator';
12
+ type AbuseReasonCode = 'accepted' | 'blocked-by-policy' | 'budget-required' | 'failed-admission' | 'first-contact' | 'invalid-doc-binding' | 'invalid-freshness' | 'invalid-hash' | 'invalid-signature' | 'low-confidence-quality-signal' | 'over-rate-limit' | 'over-size-limit' | 'peer-score-block' | 'peer-score-throttle' | 'quality-risk' | 'trusted-abuse-label' | 'trusted-warning-label' | 'unauthorized' | 'unsigned-update' | 'policy-override' | 'user-override';
13
+ type AbuseLabel = {
14
+ id?: string;
15
+ value: string;
16
+ sourceDID?: string;
17
+ sourceWeight: number;
18
+ confidence: number;
19
+ expiresAt?: number;
20
+ evidenceRefs?: readonly string[];
21
+ negates?: string;
22
+ };
23
+ type AbuseCryptoFacts = {
24
+ hashValid: boolean;
25
+ signatureValid: boolean;
26
+ authorized: boolean;
27
+ freshnessValid: boolean;
28
+ docBindingValid: boolean;
29
+ };
30
+ type AbuseResourceFacts = {
31
+ overSizeLimit: boolean;
32
+ overRateLimit: boolean;
33
+ estimatedCost: number;
34
+ budgetRemaining: number | null;
35
+ };
36
+ type AbuseActorFacts = {
37
+ did?: string;
38
+ peerId?: string;
39
+ firstContact: boolean;
40
+ peerScore: number;
41
+ localBlocked: boolean;
42
+ workspaceBlocked: boolean;
43
+ hubBlocked: boolean;
44
+ appViewBlocked: boolean;
45
+ };
46
+ type AbuseQualitySignals = {
47
+ duplicateScore: number;
48
+ slopScore: number;
49
+ citationCoverage: number;
50
+ provenanceScore: number;
51
+ };
52
+ type AbusePolicyFacts = {
53
+ peerScoreBlockThreshold: number;
54
+ peerScoreThrottleThreshold: number;
55
+ abuseLabelHideThreshold: number;
56
+ abuseLabelWarnThreshold: number;
57
+ qualityReviewThreshold: number;
58
+ qualityWarnThreshold: number;
59
+ quarantineFirstContact: boolean;
60
+ };
61
+ type AbuseDecisionOverrideScope = 'user' | 'workspace' | 'reviewer';
62
+ type AbuseDecisionOverride = Partial<Pick<AbuseDecision, 'visibility' | 'reach' | 'notify' | 'includeInCounters' | 'includeInSearch'>> & {
63
+ scope?: AbuseDecisionOverrideScope;
64
+ sourceDID?: string;
65
+ reason?: string;
66
+ };
67
+ type AbuseFacts = {
68
+ surface: AbuseSurface;
69
+ crypto?: Partial<AbuseCryptoFacts>;
70
+ resource?: Partial<AbuseResourceFacts>;
71
+ actor?: Partial<AbuseActorFacts>;
72
+ labels?: readonly AbuseLabel[];
73
+ quality?: Partial<AbuseQualitySignals>;
74
+ policy?: Partial<AbusePolicyFacts>;
75
+ override?: AbuseDecisionOverride;
76
+ now?: number;
77
+ };
78
+ type NormalizedAbuseFacts = {
79
+ surface: AbuseSurface;
80
+ crypto: AbuseCryptoFacts;
81
+ resource: AbuseResourceFacts;
82
+ actor: AbuseActorFacts;
83
+ labels: readonly AbuseLabel[];
84
+ quality: AbuseQualitySignals;
85
+ policy: AbusePolicyFacts;
86
+ override?: AbuseDecisionOverride;
87
+ now: number;
88
+ };
89
+ type PendingLabel = {
90
+ value: string;
91
+ confidence: number;
92
+ reason: AbuseReasonCode;
93
+ evidenceRefs: readonly string[];
94
+ };
95
+ type PendingSecurityEvent = {
96
+ eventName: string;
97
+ severity: AbuseSeverity;
98
+ reason: AbuseReasonCode;
99
+ };
100
+ type AbuseReviewDecision = {
101
+ required: false;
102
+ } | {
103
+ required: true;
104
+ queue: AbuseReviewQueue;
105
+ priority: number;
106
+ };
107
+ type AbuseDecision = {
108
+ admission: AbuseAdmission;
109
+ visibility: AbuseVisibility;
110
+ reach: AbuseReach;
111
+ resource: AbuseResource;
112
+ notify: boolean;
113
+ includeInCounters: boolean;
114
+ includeInSearch: boolean;
115
+ review: AbuseReviewDecision;
116
+ reasons: readonly AbuseReasonCode[];
117
+ evidenceRefs: readonly string[];
118
+ labelsToEmit: readonly PendingLabel[];
119
+ telemetry: readonly PendingSecurityEvent[];
120
+ };
121
+ type DecisionExplanationReason = {
122
+ code: AbuseReasonCode;
123
+ severity: AbuseSeverity;
124
+ message: string;
125
+ };
126
+ type DecisionExplanation = {
127
+ summary: string;
128
+ reasons: readonly DecisionExplanationReason[];
129
+ };
130
+
131
+ /**
132
+ * Privacy-preserving telemetry helpers for abuse decisions.
133
+ */
134
+
135
+ type AbusePeerScoreBucket = 'unknown' | '<=10' | '11-30' | '31-50' | '51-80' | '81-100' | '>100';
136
+ type AbuseTelemetryReporter = {
137
+ reportSecurityEvent(eventName: string, severity: AbuseSeverity, details?: Record<string, unknown>): unknown;
138
+ reportUsage?(metricName: string, value: number): unknown;
139
+ };
140
+ type RemoteMutationRejectionTelemetry = {
141
+ eventName: string;
142
+ severity: AbuseSeverity;
143
+ details: {
144
+ actionTaken: 'remote_mutation_rejected';
145
+ surface: AbuseSurface;
146
+ primaryReason: AbuseReasonCode;
147
+ reasons: readonly AbuseReasonCode[];
148
+ peerHash: string;
149
+ peerScoreBucket: AbusePeerScoreBucket;
150
+ resourceAction: AbuseDecision['resource'];
151
+ shouldThrottle: boolean;
152
+ };
153
+ };
154
+ type RemoteMutationRejectionTelemetryInput = {
155
+ facts: AbuseFacts;
156
+ decision: AbuseDecision;
157
+ eventName?: string;
158
+ peerHashSalt?: string;
159
+ };
160
+ declare function bucketAbusePeerScore(score: number | null | undefined): AbusePeerScoreBucket;
161
+ declare function hashAbusePeerIdentifier(peerId: string | null | undefined, salt?: string): string;
162
+ declare function createRemoteMutationRejectionTelemetry(input: RemoteMutationRejectionTelemetryInput): RemoteMutationRejectionTelemetry | null;
163
+ declare function reportRemoteMutationRejection(telemetry: AbuseTelemetryReporter | undefined, input: RemoteMutationRejectionTelemetryInput): boolean;
164
+
165
+ /**
166
+ * Adapter helpers for wiring local package events into abuse decisions.
167
+ */
168
+
169
+ type AbuseFactAdapter<TInput> = (input: TInput) => AbuseFacts;
170
+ type AbuseDecisionFunction = (facts: AbuseFacts) => AbuseDecision;
171
+ type AbuseAdapterResult = {
172
+ facts: AbuseFacts;
173
+ decision: AbuseDecision;
174
+ };
175
+ declare function createAbuseFactAdapter<TInput>(adapter: AbuseFactAdapter<TInput>): AbuseFactAdapter<TInput>;
176
+ declare function decideWithAdapter<TInput>(input: TInput, adapter: AbuseFactAdapter<TInput>, decide?: AbuseDecisionFunction): AbuseAdapterResult;
177
+ declare function createAbuseDecisionAdapter<TInput>(adapter: AbuseFactAdapter<TInput>, decide?: AbuseDecisionFunction): (input: TInput) => AbuseAdapterResult;
178
+ type RemoteAdmissionResult = AbuseAdapterResult & {
179
+ accepted: boolean;
180
+ shouldMutate: boolean;
181
+ shouldRelay: boolean;
182
+ shouldThrottle: boolean;
183
+ };
184
+ type RemoteAdmissionPipeline<TInput> = {
185
+ evaluate(input: TInput): RemoteAdmissionResult;
186
+ };
187
+ type RemoteAdmissionPipelineOptions<TInput> = {
188
+ adapt: AbuseFactAdapter<TInput>;
189
+ decide?: AbuseDecisionFunction;
190
+ telemetry?: AbuseTelemetryReporter;
191
+ telemetryEventName?: string;
192
+ telemetryPeerHashSalt?: string;
193
+ };
194
+ declare function createRemoteAdmissionPipeline<TInput>(options: RemoteAdmissionPipelineOptions<TInput>): RemoteAdmissionPipeline<TInput>;
195
+
196
+ export { type AbuseLabel as A, type RemoteMutationRejectionTelemetry as B, type RemoteMutationRejectionTelemetryInput as C, type DecisionExplanation as D, type AbuseActorFacts as E, type AbuseAdmission as F, type AbuseCryptoFacts as G, type AbuseDecisionOverrideScope as H, type AbuseResourceFacts as I, type AbuseReviewDecision as J, type AbuseSeverity as K, type PendingLabel as L, type PendingSecurityEvent as M, type NormalizedAbuseFacts as N, type PolicyScope as P, type RemoteAdmissionPipeline as R, type AbuseFacts as a, type AbuseDecision as b, type AbuseReasonCode as c, type DecisionExplanationReason as d, type AbuseSurface as e, type AbuseQualitySignals as f, type AbuseVisibility as g, type AbuseReach as h, type AbuseReviewQueue as i, type AbuseResource as j, type AbuseDecisionOverride as k, createAbuseDecisionAdapter as l, createAbuseFactAdapter as m, createRemoteAdmissionPipeline as n, decideWithAdapter as o, bucketAbusePeerScore as p, createRemoteMutationRejectionTelemetry as q, hashAbusePeerIdentifier as r, reportRemoteMutationRejection as s, type AbuseAdapterResult as t, type AbuseDecisionFunction as u, type AbuseFactAdapter as v, type RemoteAdmissionPipelineOptions as w, type RemoteAdmissionResult as x, type AbusePeerScoreBucket as y, type AbuseTelemetryReporter as z };
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @xnetjs/abuse - Shared abuse decision types.
3
+ */
4
+ type AbuseSurface = 'transport' | 'remoteMutation' | 'commentThread' | 'messageInbox' | 'searchIndex' | 'feed' | 'crawl' | 'localApi';
5
+ type PolicyScope = 'user' | 'workspace' | 'community' | 'hub' | 'appView' | 'protocol';
6
+ type AbuseAdmission = 'accept' | 'reject' | 'quarantine';
7
+ type AbuseVisibility = 'show' | 'warn' | 'blur' | 'hide';
8
+ type AbuseReach = 'normal' | 'demote' | 'exclude';
9
+ type AbuseResource = 'normal' | 'throttle' | 'block-peer' | 'require-budget';
10
+ type AbuseSeverity = 'low' | 'medium' | 'high' | 'critical';
11
+ type AbuseReviewQueue = 'safety' | 'quality' | 'appeal' | 'operator';
12
+ type AbuseReasonCode = 'accepted' | 'blocked-by-policy' | 'budget-required' | 'failed-admission' | 'first-contact' | 'invalid-doc-binding' | 'invalid-freshness' | 'invalid-hash' | 'invalid-signature' | 'low-confidence-quality-signal' | 'over-rate-limit' | 'over-size-limit' | 'peer-score-block' | 'peer-score-throttle' | 'quality-risk' | 'trusted-abuse-label' | 'trusted-warning-label' | 'unauthorized' | 'unsigned-update' | 'user-override';
13
+ type AbuseLabel = {
14
+ value: string;
15
+ sourceDID?: string;
16
+ sourceWeight: number;
17
+ confidence: number;
18
+ expiresAt?: number;
19
+ evidenceRefs?: readonly string[];
20
+ };
21
+ type AbuseCryptoFacts = {
22
+ hashValid: boolean;
23
+ signatureValid: boolean;
24
+ authorized: boolean;
25
+ freshnessValid: boolean;
26
+ docBindingValid: boolean;
27
+ };
28
+ type AbuseResourceFacts = {
29
+ overSizeLimit: boolean;
30
+ overRateLimit: boolean;
31
+ estimatedCost: number;
32
+ budgetRemaining: number | null;
33
+ };
34
+ type AbuseActorFacts = {
35
+ did?: string;
36
+ peerId?: string;
37
+ firstContact: boolean;
38
+ peerScore: number;
39
+ localBlocked: boolean;
40
+ workspaceBlocked: boolean;
41
+ hubBlocked: boolean;
42
+ };
43
+ type AbuseQualitySignals = {
44
+ duplicateScore: number;
45
+ slopScore: number;
46
+ citationCoverage: number;
47
+ provenanceScore: number;
48
+ };
49
+ type AbusePolicyFacts = {
50
+ peerScoreBlockThreshold: number;
51
+ peerScoreThrottleThreshold: number;
52
+ abuseLabelHideThreshold: number;
53
+ abuseLabelWarnThreshold: number;
54
+ qualityReviewThreshold: number;
55
+ qualityWarnThreshold: number;
56
+ quarantineFirstContact: boolean;
57
+ };
58
+ type AbuseDecisionOverride = Partial<Pick<AbuseDecision, 'visibility' | 'reach' | 'notify' | 'includeInCounters' | 'includeInSearch'>> & {
59
+ reason?: string;
60
+ };
61
+ type AbuseFacts = {
62
+ surface: AbuseSurface;
63
+ crypto?: Partial<AbuseCryptoFacts>;
64
+ resource?: Partial<AbuseResourceFacts>;
65
+ actor?: Partial<AbuseActorFacts>;
66
+ labels?: readonly AbuseLabel[];
67
+ quality?: Partial<AbuseQualitySignals>;
68
+ policy?: Partial<AbusePolicyFacts>;
69
+ override?: AbuseDecisionOverride;
70
+ now?: number;
71
+ };
72
+ type NormalizedAbuseFacts = {
73
+ surface: AbuseSurface;
74
+ crypto: AbuseCryptoFacts;
75
+ resource: AbuseResourceFacts;
76
+ actor: AbuseActorFacts;
77
+ labels: readonly AbuseLabel[];
78
+ quality: AbuseQualitySignals;
79
+ policy: AbusePolicyFacts;
80
+ override?: AbuseDecisionOverride;
81
+ now: number;
82
+ };
83
+ type PendingLabel = {
84
+ value: string;
85
+ confidence: number;
86
+ reason: AbuseReasonCode;
87
+ evidenceRefs: readonly string[];
88
+ };
89
+ type PendingSecurityEvent = {
90
+ eventName: string;
91
+ severity: AbuseSeverity;
92
+ reason: AbuseReasonCode;
93
+ };
94
+ type AbuseReviewDecision = {
95
+ required: false;
96
+ } | {
97
+ required: true;
98
+ queue: AbuseReviewQueue;
99
+ priority: number;
100
+ };
101
+ type AbuseDecision = {
102
+ admission: AbuseAdmission;
103
+ visibility: AbuseVisibility;
104
+ reach: AbuseReach;
105
+ resource: AbuseResource;
106
+ notify: boolean;
107
+ includeInCounters: boolean;
108
+ includeInSearch: boolean;
109
+ review: AbuseReviewDecision;
110
+ reasons: readonly AbuseReasonCode[];
111
+ evidenceRefs: readonly string[];
112
+ labelsToEmit: readonly PendingLabel[];
113
+ telemetry: readonly PendingSecurityEvent[];
114
+ };
115
+ type DecisionExplanationReason = {
116
+ code: AbuseReasonCode;
117
+ severity: AbuseSeverity;
118
+ message: string;
119
+ };
120
+ type DecisionExplanation = {
121
+ summary: string;
122
+ reasons: readonly DecisionExplanationReason[];
123
+ };
124
+
125
+ /**
126
+ * Privacy-preserving telemetry helpers for abuse decisions.
127
+ */
128
+
129
+ type AbusePeerScoreBucket = 'unknown' | '<=10' | '11-30' | '31-50' | '51-80' | '81-100' | '>100';
130
+ type AbuseTelemetryReporter = {
131
+ reportSecurityEvent(eventName: string, severity: AbuseSeverity, details?: Record<string, unknown>): unknown;
132
+ reportUsage?(metricName: string, value: number): unknown;
133
+ };
134
+ type RemoteMutationRejectionTelemetry = {
135
+ eventName: string;
136
+ severity: AbuseSeverity;
137
+ details: {
138
+ actionTaken: 'remote_mutation_rejected';
139
+ surface: AbuseSurface;
140
+ primaryReason: AbuseReasonCode;
141
+ reasons: readonly AbuseReasonCode[];
142
+ peerHash: string;
143
+ peerScoreBucket: AbusePeerScoreBucket;
144
+ resourceAction: AbuseDecision['resource'];
145
+ shouldThrottle: boolean;
146
+ };
147
+ };
148
+ type RemoteMutationRejectionTelemetryInput = {
149
+ facts: AbuseFacts;
150
+ decision: AbuseDecision;
151
+ eventName?: string;
152
+ peerHashSalt?: string;
153
+ };
154
+ declare function bucketAbusePeerScore(score: number | null | undefined): AbusePeerScoreBucket;
155
+ declare function hashAbusePeerIdentifier(peerId: string | null | undefined, salt?: string): string;
156
+ declare function createRemoteMutationRejectionTelemetry(input: RemoteMutationRejectionTelemetryInput): RemoteMutationRejectionTelemetry | null;
157
+ declare function reportRemoteMutationRejection(telemetry: AbuseTelemetryReporter | undefined, input: RemoteMutationRejectionTelemetryInput): boolean;
158
+
159
+ /**
160
+ * Adapter helpers for wiring local package events into abuse decisions.
161
+ */
162
+
163
+ type AbuseFactAdapter<TInput> = (input: TInput) => AbuseFacts;
164
+ type AbuseDecisionFunction = (facts: AbuseFacts) => AbuseDecision;
165
+ type AbuseAdapterResult = {
166
+ facts: AbuseFacts;
167
+ decision: AbuseDecision;
168
+ };
169
+ declare function createAbuseFactAdapter<TInput>(adapter: AbuseFactAdapter<TInput>): AbuseFactAdapter<TInput>;
170
+ declare function decideWithAdapter<TInput>(input: TInput, adapter: AbuseFactAdapter<TInput>, decide?: AbuseDecisionFunction): AbuseAdapterResult;
171
+ declare function createAbuseDecisionAdapter<TInput>(adapter: AbuseFactAdapter<TInput>, decide?: AbuseDecisionFunction): (input: TInput) => AbuseAdapterResult;
172
+ type RemoteAdmissionResult = AbuseAdapterResult & {
173
+ accepted: boolean;
174
+ shouldMutate: boolean;
175
+ shouldRelay: boolean;
176
+ shouldThrottle: boolean;
177
+ };
178
+ type RemoteAdmissionPipeline<TInput> = {
179
+ evaluate(input: TInput): RemoteAdmissionResult;
180
+ };
181
+ type RemoteAdmissionPipelineOptions<TInput> = {
182
+ adapt: AbuseFactAdapter<TInput>;
183
+ decide?: AbuseDecisionFunction;
184
+ telemetry?: AbuseTelemetryReporter;
185
+ telemetryEventName?: string;
186
+ telemetryPeerHashSalt?: string;
187
+ };
188
+ declare function createRemoteAdmissionPipeline<TInput>(options: RemoteAdmissionPipelineOptions<TInput>): RemoteAdmissionPipeline<TInput>;
189
+
190
+ export { type AbuseLabel as A, type AbuseCryptoFacts as B, type AbuseDecisionOverride as C, type DecisionExplanation as D, type AbuseResource as E, type AbuseResourceFacts as F, type AbuseReviewDecision as G, type AbuseSeverity as H, type AbuseVisibility as I, type PendingLabel as J, type PendingSecurityEvent as K, type NormalizedAbuseFacts as N, type PolicyScope as P, type RemoteAdmissionPipeline as R, type AbuseFacts as a, type AbuseDecision as b, type AbuseReasonCode as c, type DecisionExplanationReason as d, type AbuseSurface as e, type AbuseQualitySignals as f, type AbuseReviewQueue as g, createAbuseDecisionAdapter as h, createAbuseFactAdapter as i, createRemoteAdmissionPipeline as j, decideWithAdapter as k, bucketAbusePeerScore as l, createRemoteMutationRejectionTelemetry as m, hashAbusePeerIdentifier as n, type AbuseAdapterResult as o, type AbuseDecisionFunction as p, type AbuseFactAdapter as q, reportRemoteMutationRejection as r, type RemoteAdmissionPipelineOptions as s, type RemoteAdmissionResult as t, type AbusePeerScoreBucket as u, type AbuseTelemetryReporter as v, type RemoteMutationRejectionTelemetry as w, type RemoteMutationRejectionTelemetryInput as x, type AbuseActorFacts as y, type AbuseAdmission as z };