@voltro/plugin-sanctions 0.0.0-stage → 0.87.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/CHANGELOG.md +24329 -0
- package/LICENSE +61 -0
- package/README.md +198 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +7737 -0
- package/dist/admission.server.js +97 -0
- package/dist/components.js +283 -0
- package/dist/contract.js +89 -0
- package/dist/entities.js +102 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +15 -0
- package/dist/eventProducer.d.ts +8 -0
- package/dist/eventProducer.js +20 -0
- package/dist/events.d.ts +14 -0
- package/dist/events.js +11 -0
- package/dist/guard.d.ts +83 -0
- package/dist/guard.js +17 -0
- package/dist/index.d.ts +585 -0
- package/dist/index.js +180 -0
- package/dist/inspect.js +24 -0
- package/dist/port.d.ts +82 -0
- package/dist/port.js +5 -0
- package/dist/rpc.d.ts +165 -0
- package/dist/rpc.js +147 -0
- package/dist/rpcRoutes.js +50 -0
- package/dist/sanctions-app/events/sanctions.changed.event.ts +1 -0
- package/dist/sanctions-app/subscribers/sanctions.actions.subscribe.ts +3 -0
- package/dist/server.d.ts +363 -0
- package/dist/server.js +1 -0
- package/dist/service.js +709 -0
- package/dist/strategies.d.ts +154 -0
- package/dist/strategies.js +73 -0
- package/dist/testing.d.ts +211 -0
- package/dist/testing.js +121 -0
- package/dist/web.d.ts +239 -0
- package/dist/web.js +8 -0
- package/package.json +99 -4
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { DataStore } from '@voltro/database';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { PluginPrivacyTarget } from '@voltro/protocol';
|
|
4
|
+
import { PreparedSqlMutation } from '@voltro/database/sql';
|
|
5
|
+
import { RateLimitStore } from '@voltro/plugin-ratelimit';
|
|
6
|
+
import { RedisStoreOptions } from '@voltro/plugin-ratelimit';
|
|
7
|
+
import { Schema } from 'effect';
|
|
8
|
+
import { Subject } from '@voltro/protocol';
|
|
9
|
+
import { YieldableError } from 'effect/Cause';
|
|
10
|
+
|
|
11
|
+
declare const ACTION_KINDS: readonly ["warn", "mute", "chatBan", "matchmakingBan", "rankedBan", "tradeBan", "ban", "shadowBan", "nameReset", "rollback"];
|
|
12
|
+
|
|
13
|
+
declare const ACTIONS_TABLE = "_voltro_sanc_actions";
|
|
14
|
+
|
|
15
|
+
declare const ADMISSIONS_TABLE = "_voltro_sanc_admissions";
|
|
16
|
+
|
|
17
|
+
declare const APPEALS_TABLE = "_voltro_sanc_appeals";
|
|
18
|
+
|
|
19
|
+
declare const AppealSchema: Schema.Struct<{
|
|
20
|
+
readonly id: Schema.String;
|
|
21
|
+
readonly actionId: Schema.String;
|
|
22
|
+
readonly text: Schema.String;
|
|
23
|
+
readonly state: Schema.Literals<readonly ["pending", "reviewed"]>;
|
|
24
|
+
readonly outcome: Schema.NullOr<Schema.Literals<readonly ["upheld", "revoked"]>>;
|
|
25
|
+
readonly createdAt: Schema.Number;
|
|
26
|
+
readonly decidedAt: Schema.NullOr<Schema.Number>;
|
|
27
|
+
}>;
|
|
28
|
+
|
|
29
|
+
declare namespace C {
|
|
30
|
+
export {
|
|
31
|
+
REPORTS_TABLE,
|
|
32
|
+
CASES_TABLE,
|
|
33
|
+
ACTIONS_TABLE,
|
|
34
|
+
APPEALS_TABLE,
|
|
35
|
+
STRIKES_TABLE,
|
|
36
|
+
DEVICE_HINTS_TABLE,
|
|
37
|
+
ADMISSIONS_TABLE,
|
|
38
|
+
ACTION_KINDS,
|
|
39
|
+
CATEGORIES,
|
|
40
|
+
POINTS,
|
|
41
|
+
SanctionKind,
|
|
42
|
+
SanctionCategory,
|
|
43
|
+
SanctionPoint,
|
|
44
|
+
SanctionScopeSchema,
|
|
45
|
+
ReportContextSchema,
|
|
46
|
+
ReportContext,
|
|
47
|
+
SanctionSchema,
|
|
48
|
+
ReportSchema,
|
|
49
|
+
AppealSchema,
|
|
50
|
+
CaseSchema,
|
|
51
|
+
DecisionSchema,
|
|
52
|
+
Sanction,
|
|
53
|
+
SanctionReport,
|
|
54
|
+
SanctionAppeal,
|
|
55
|
+
SanctionCase,
|
|
56
|
+
SanctionDecision,
|
|
57
|
+
SanctionScope,
|
|
58
|
+
ReportInput,
|
|
59
|
+
IssueSanctionInput,
|
|
60
|
+
SanctionCheckContext,
|
|
61
|
+
TrustedSanctionSignal
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare const CASES_TABLE = "_voltro_sanc_cases";
|
|
66
|
+
|
|
67
|
+
declare const CaseSchema: Schema.Struct<{
|
|
68
|
+
readonly id: Schema.String;
|
|
69
|
+
readonly targetId: Schema.NullOr<Schema.String>;
|
|
70
|
+
readonly reports: Schema.Number;
|
|
71
|
+
readonly evidence: Schema.$Array<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
72
|
+
readonly trustScore: Schema.Number;
|
|
73
|
+
readonly state: Schema.Literals<readonly ["open", "reviewing", "actioned", "dismissed"]>;
|
|
74
|
+
readonly decidedBy: Schema.NullOr<Schema.String>;
|
|
75
|
+
readonly decidedAt: Schema.NullOr<Schema.Number>;
|
|
76
|
+
readonly version: Schema.Number;
|
|
77
|
+
}>;
|
|
78
|
+
|
|
79
|
+
declare const CATEGORIES: readonly ["cheating", "abuse", "name", "spam", "griefing", "rmt"];
|
|
80
|
+
|
|
81
|
+
export declare const dataStoreSanctions: (store: DataStore, options: ResolvedSanctionsOptions, limiter: RateLimitStore) => {
|
|
82
|
+
api: SanctionsApi;
|
|
83
|
+
port: SanctionsService;
|
|
84
|
+
admission: SanctionsSqlAdmissionService;
|
|
85
|
+
operator: SanctionsOperatorService;
|
|
86
|
+
recordDeviceHint: (subject: Subject, input: {
|
|
87
|
+
readonly tenantId: string;
|
|
88
|
+
readonly subjectId: string;
|
|
89
|
+
readonly deviceId: string;
|
|
90
|
+
}, salt: Uint8Array) => Effect.Effect<void, SanctionsError, never>;
|
|
91
|
+
exportSubject: (target: PluginPrivacyTarget) => Effect.Effect<unknown, SanctionsError, never>;
|
|
92
|
+
eraseSubject: (target: PluginPrivacyTarget) => Effect.Effect<void, SanctionsError, never>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
declare const DecisionSchema: Schema.Struct<{
|
|
96
|
+
readonly allowed: Schema.Boolean;
|
|
97
|
+
readonly actionIds: Schema.$Array<Schema.String>;
|
|
98
|
+
readonly shadow: Schema.Boolean;
|
|
99
|
+
}>;
|
|
100
|
+
|
|
101
|
+
declare const defaultSanctionsStrategies: Readonly<{
|
|
102
|
+
escalationLadder: (input: EscalationInput) => EscalationStep | null;
|
|
103
|
+
trustScore: (input: TrustScoreInput) => number;
|
|
104
|
+
evidenceCollector: (_input: EvidenceCollectorInput) => Effect.Effect<ReadonlyArray<Readonly<Record<string, unknown>>>, SanctionsError>;
|
|
105
|
+
enforcementPoints: ({ action, point, context }: EnforcementInput) => {
|
|
106
|
+
readonly denied: boolean;
|
|
107
|
+
readonly shadow: boolean;
|
|
108
|
+
};
|
|
109
|
+
}>;
|
|
110
|
+
|
|
111
|
+
declare const DEVICE_HINTS_TABLE = "_voltro_sanc_device_hints";
|
|
112
|
+
|
|
113
|
+
declare interface EnforcementInput {
|
|
114
|
+
readonly action: Sanction;
|
|
115
|
+
readonly point: SanctionPoint;
|
|
116
|
+
readonly context: SanctionCheckContext;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare interface EscalationInput {
|
|
120
|
+
readonly category: SanctionCategory;
|
|
121
|
+
readonly count: number;
|
|
122
|
+
readonly confidence: number;
|
|
123
|
+
readonly minimumConfidence: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
declare interface EscalationStep {
|
|
127
|
+
readonly kind: SanctionKind;
|
|
128
|
+
readonly durationMs: number | null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
declare interface EvidenceCollectorInput {
|
|
132
|
+
readonly subject: Subject;
|
|
133
|
+
readonly targetId: string;
|
|
134
|
+
readonly context: ReportContext;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare interface IssueSanctionInput {
|
|
138
|
+
readonly requestId: string;
|
|
139
|
+
readonly targetId: string;
|
|
140
|
+
readonly kind: SanctionKind;
|
|
141
|
+
readonly scope?: SanctionScope;
|
|
142
|
+
readonly durationMs?: number | null;
|
|
143
|
+
readonly reason: string;
|
|
144
|
+
readonly publicReason: string;
|
|
145
|
+
readonly evidenceCaseId?: string;
|
|
146
|
+
readonly appealable?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
declare const POINTS: readonly ["chat", "matchmaking", "trade", "ranked", "login"];
|
|
150
|
+
|
|
151
|
+
declare type ReportContext = typeof ReportContextSchema.Type;
|
|
152
|
+
|
|
153
|
+
declare const ReportContextSchema: Schema.Struct<{
|
|
154
|
+
readonly channelId: Schema.optional<Schema.String>;
|
|
155
|
+
readonly messageIds: Schema.optional<Schema.$Array<Schema.String>>;
|
|
156
|
+
readonly roomId: Schema.optional<Schema.String>;
|
|
157
|
+
readonly matchId: Schema.optional<Schema.String>;
|
|
158
|
+
readonly replayRef: Schema.optional<Schema.String>;
|
|
159
|
+
}>;
|
|
160
|
+
|
|
161
|
+
declare interface ReportInput {
|
|
162
|
+
readonly requestId: string;
|
|
163
|
+
readonly targetId: string;
|
|
164
|
+
readonly category: SanctionCategory;
|
|
165
|
+
readonly context?: ReportContext;
|
|
166
|
+
readonly note?: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
declare const REPORTS_TABLE = "_voltro_sanc_reports";
|
|
170
|
+
|
|
171
|
+
declare const ReportSchema: Schema.Struct<{
|
|
172
|
+
readonly id: Schema.String;
|
|
173
|
+
readonly targetId: Schema.String;
|
|
174
|
+
readonly category: Schema.Literals<readonly ["cheating", "abuse", "name", "spam", "griefing", "rmt"]>;
|
|
175
|
+
readonly state: Schema.Literals<readonly ["open", "reviewing", "actioned", "dismissed"]>;
|
|
176
|
+
readonly createdAt: Schema.Number;
|
|
177
|
+
readonly caseId: Schema.String;
|
|
178
|
+
}>;
|
|
179
|
+
|
|
180
|
+
declare type ResolvedSanctionsOptions = ReturnType<typeof resolveSanctionsOptions>;
|
|
181
|
+
|
|
182
|
+
declare const resolveSanctionsOptions: (input?: SanctionsOptions) => Readonly<{
|
|
183
|
+
maxActiveActions: number;
|
|
184
|
+
maxReportsPerCase: number;
|
|
185
|
+
maxEvidenceRefs: number;
|
|
186
|
+
maxContextMessageIds: number;
|
|
187
|
+
maxTextLength: number;
|
|
188
|
+
pageSize: number;
|
|
189
|
+
maxExportRows: number;
|
|
190
|
+
reportLimit: number;
|
|
191
|
+
reportWindowMs: number;
|
|
192
|
+
strikeDecayMs: number;
|
|
193
|
+
minimumSignalConfidence: number;
|
|
194
|
+
strategies: Readonly<{
|
|
195
|
+
escalationLadder: (input: EscalationInput) => EscalationStep | null;
|
|
196
|
+
trustScore: (input: TrustScoreInput) => number;
|
|
197
|
+
evidenceCollector: (_input: EvidenceCollectorInput) => Effect.Effect<ReadonlyArray<Readonly<Record<string, unknown>>>, SanctionsError>;
|
|
198
|
+
enforcementPoints: ({ action, point, context }: EnforcementInput) => {
|
|
199
|
+
readonly denied: boolean;
|
|
200
|
+
readonly shadow: boolean;
|
|
201
|
+
};
|
|
202
|
+
}>;
|
|
203
|
+
rateLimit: RedisStoreOptions | undefined;
|
|
204
|
+
now: () => number;
|
|
205
|
+
}>;
|
|
206
|
+
|
|
207
|
+
declare type Sanction = typeof SanctionSchema.Type;
|
|
208
|
+
|
|
209
|
+
declare type SanctionAppeal = typeof AppealSchema.Type;
|
|
210
|
+
|
|
211
|
+
declare type SanctionCase = typeof CaseSchema.Type;
|
|
212
|
+
|
|
213
|
+
declare type SanctionCategory = typeof CATEGORIES[number];
|
|
214
|
+
|
|
215
|
+
declare interface SanctionCheckContext {
|
|
216
|
+
readonly channelId?: string;
|
|
217
|
+
readonly queueId?: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
declare type SanctionDecision = typeof DecisionSchema.Type;
|
|
221
|
+
|
|
222
|
+
declare type SanctionKind = typeof ACTION_KINDS[number];
|
|
223
|
+
|
|
224
|
+
declare type SanctionPoint = typeof POINTS[number];
|
|
225
|
+
|
|
226
|
+
declare type SanctionReport = typeof ReportSchema.Type;
|
|
227
|
+
|
|
228
|
+
export declare interface SanctionsAdmissionBatch extends SanctionsAdmissionRead {
|
|
229
|
+
readonly tenantId: string;
|
|
230
|
+
readonly participants: readonly {
|
|
231
|
+
readonly ownerId: string;
|
|
232
|
+
readonly expected: SanctionsPermission;
|
|
233
|
+
}[];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export declare interface SanctionsAdmissionPrepare extends SanctionsAdmissionRead {
|
|
237
|
+
readonly expected: SanctionsPermission;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export declare interface SanctionsAdmissionRead {
|
|
241
|
+
readonly point: SanctionPoint;
|
|
242
|
+
readonly at: number;
|
|
243
|
+
readonly context?: SanctionCheckContext;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export declare interface SanctionsApi {
|
|
247
|
+
readonly report: (subject: Subject, input: C.ReportInput) => Effect.Effect<C.SanctionReport, SanctionsError>;
|
|
248
|
+
readonly reportsMine: (subject: Subject) => Effect.Effect<ReadonlyArray<C.SanctionReport>, SanctionsError>;
|
|
249
|
+
readonly mine: (subject: Subject) => Effect.Effect<ReadonlyArray<C.Sanction>, SanctionsError>;
|
|
250
|
+
readonly appeal: (subject: Subject, input: {
|
|
251
|
+
readonly actionId: string;
|
|
252
|
+
readonly text: string;
|
|
253
|
+
}) => Effect.Effect<C.SanctionAppeal, SanctionsError>;
|
|
254
|
+
readonly appealsMine: (subject: Subject) => Effect.Effect<ReadonlyArray<C.SanctionAppeal>, SanctionsError>;
|
|
255
|
+
readonly cases: (subject: Subject) => Effect.Effect<ReadonlyArray<C.SanctionCase>, SanctionsError>;
|
|
256
|
+
readonly reviewCase: (subject: Subject, input: {
|
|
257
|
+
readonly caseId: string;
|
|
258
|
+
readonly baseVersion: number;
|
|
259
|
+
readonly state: C.SanctionCase['state'];
|
|
260
|
+
}) => Effect.Effect<C.SanctionCase, SanctionsError>;
|
|
261
|
+
readonly issue: (subject: Subject, input: C.IssueSanctionInput) => Effect.Effect<C.Sanction, SanctionsError>;
|
|
262
|
+
readonly revoke: (subject: Subject, actionId: string) => Effect.Effect<void, SanctionsError>;
|
|
263
|
+
readonly appealsQueue: (subject: Subject) => Effect.Effect<ReadonlyArray<C.SanctionAppeal>, SanctionsError>;
|
|
264
|
+
readonly reviewAppeal: (subject: Subject, input: {
|
|
265
|
+
readonly appealId: string;
|
|
266
|
+
readonly outcome: 'upheld' | 'revoked';
|
|
267
|
+
}) => Effect.Effect<C.SanctionAppeal, SanctionsError>;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
declare const SanctionSchema: Schema.Struct<{
|
|
271
|
+
readonly id: Schema.String;
|
|
272
|
+
readonly kind: Schema.Literals<readonly ["warn", "mute", "chatBan", "matchmakingBan", "rankedBan", "tradeBan", "ban", "shadowBan", "nameReset", "rollback"]>;
|
|
273
|
+
readonly scope: Schema.Struct<{
|
|
274
|
+
readonly channels: Schema.$Array<Schema.String>;
|
|
275
|
+
readonly queues: Schema.$Array<Schema.String>;
|
|
276
|
+
}>;
|
|
277
|
+
readonly from: Schema.Number;
|
|
278
|
+
readonly until: Schema.NullOr<Schema.Number>;
|
|
279
|
+
readonly publicReason: Schema.String;
|
|
280
|
+
readonly appealable: Schema.Boolean;
|
|
281
|
+
}>;
|
|
282
|
+
|
|
283
|
+
declare type SanctionScope = typeof SanctionScopeSchema.Type;
|
|
284
|
+
|
|
285
|
+
declare const SanctionScopeSchema: Schema.Struct<{
|
|
286
|
+
readonly channels: Schema.$Array<Schema.String>;
|
|
287
|
+
readonly queues: Schema.$Array<Schema.String>;
|
|
288
|
+
}>;
|
|
289
|
+
|
|
290
|
+
declare class SanctionsError extends SanctionsError_base {
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
declare const SanctionsError_base: Schema.Class<SanctionsError, Schema.TaggedStruct<"SanctionsError", {
|
|
294
|
+
readonly code: Schema.Literals<readonly ["identity_required", "invalid_input", "forbidden", "not_found", "conflict", "rate_limited", "sanctioned", "unavailable", "export_limit"]>;
|
|
295
|
+
}>, YieldableError>;
|
|
296
|
+
|
|
297
|
+
/** The tenant and human operator a trusted server caller (a GM console) acts as; recorded as the issuer. */
|
|
298
|
+
declare interface SanctionsOperator {
|
|
299
|
+
readonly tenantId: string;
|
|
300
|
+
readonly operatorId: string;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Server-only issue/revoke for code that already admitted its operator. No request scope is read. */
|
|
304
|
+
declare interface SanctionsOperatorService {
|
|
305
|
+
readonly issue: (operator: SanctionsOperator, input: C.IssueSanctionInput) => Effect.Effect<C.Sanction, SanctionsError>;
|
|
306
|
+
readonly revoke: (operator: SanctionsOperator, actionId: string) => Effect.Effect<void, SanctionsError>;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
declare interface SanctionsOptions {
|
|
310
|
+
readonly maxActiveActions?: number;
|
|
311
|
+
readonly maxReportsPerCase?: number;
|
|
312
|
+
readonly maxEvidenceRefs?: number;
|
|
313
|
+
readonly maxContextMessageIds?: number;
|
|
314
|
+
readonly maxTextLength?: number;
|
|
315
|
+
readonly pageSize?: number;
|
|
316
|
+
readonly maxExportRows?: number;
|
|
317
|
+
readonly reportLimit?: number;
|
|
318
|
+
readonly reportWindowMs?: number;
|
|
319
|
+
readonly strikeDecayMs?: number;
|
|
320
|
+
readonly minimumSignalConfidence?: number;
|
|
321
|
+
readonly strategies?: Partial<typeof defaultSanctionsStrategies>;
|
|
322
|
+
readonly rateLimit?: RedisStoreOptions;
|
|
323
|
+
readonly now?: () => number;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export declare interface SanctionsPermission {
|
|
327
|
+
readonly allowed: boolean;
|
|
328
|
+
readonly trustScore: number;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
declare interface SanctionsService {
|
|
332
|
+
readonly check: (subject: Subject, point: SanctionPoint, context?: SanctionCheckContext) => Effect.Effect<SanctionDecision, SanctionsError>;
|
|
333
|
+
readonly active: (subject: Subject) => Effect.Effect<ReadonlyArray<Sanction>, SanctionsError>;
|
|
334
|
+
readonly trust: (subject: Subject) => Effect.Effect<number, SanctionsError>;
|
|
335
|
+
readonly recordSignal: (subject: Subject, input: TrustedSanctionSignal) => Effect.Effect<void, SanctionsError>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Server-only capability; the small ordinary SanctionsPort remains unchanged. */
|
|
339
|
+
export declare interface SanctionsSqlAdmissionService {
|
|
340
|
+
readonly read: (subject: Subject, input: SanctionsAdmissionRead) => Effect.Effect<SanctionsPermission, SanctionsError>;
|
|
341
|
+
readonly prepare: (subject: Subject, input: SanctionsAdmissionPrepare) => Effect.Effect<PreparedSqlMutation<void>, SanctionsError>;
|
|
342
|
+
readonly prepareMany: (input: SanctionsAdmissionBatch) => Effect.Effect<PreparedSqlMutation<void>, SanctionsError>;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
declare const STRIKES_TABLE = "_voltro_sanc_strikes";
|
|
346
|
+
|
|
347
|
+
declare interface TrustedSanctionSignal {
|
|
348
|
+
readonly tenantId: string;
|
|
349
|
+
readonly targetId: string;
|
|
350
|
+
readonly requestId: string;
|
|
351
|
+
readonly category: SanctionCategory;
|
|
352
|
+
readonly confidence: number;
|
|
353
|
+
readonly context?: ReportContext;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
declare interface TrustScoreInput {
|
|
357
|
+
readonly strikes: number;
|
|
358
|
+
readonly verifiedAccountAgeMs: number | null;
|
|
359
|
+
readonly verifiedPlatform: boolean;
|
|
360
|
+
readonly verifiedPaymentCount: number;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export { }
|
package/dist/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { dataStoreSanctions } from "./service.js";
|