@oxyhq/core 3.2.0 → 3.4.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.
Files changed (73) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/AuthManager.js +3 -1
  3. package/dist/cjs/HttpService.js +89 -0
  4. package/dist/cjs/OxyServices.js +1 -1
  5. package/dist/cjs/constants/version.js +1 -1
  6. package/dist/cjs/i18n/locales/en-US.json +44 -44
  7. package/dist/cjs/i18n/locales/es-ES.json +44 -44
  8. package/dist/cjs/i18n/locales/locales/en-US.json +44 -44
  9. package/dist/cjs/i18n/locales/locales/es-ES.json +44 -44
  10. package/dist/cjs/index.js +4 -0
  11. package/dist/cjs/mixins/OxyServices.applications.js +3 -1
  12. package/dist/cjs/mixins/OxyServices.reputation.js +244 -0
  13. package/dist/cjs/mixins/OxyServices.workspaces.js +3 -1
  14. package/dist/cjs/mixins/index.js +2 -2
  15. package/dist/cjs/utils/accountUtils.js +12 -5
  16. package/dist/cjs/utils/ssoReturn.js +80 -33
  17. package/dist/esm/.tsbuildinfo +1 -1
  18. package/dist/esm/AuthManager.js +3 -1
  19. package/dist/esm/HttpService.js +89 -0
  20. package/dist/esm/OxyServices.js +1 -1
  21. package/dist/esm/constants/version.js +1 -1
  22. package/dist/esm/i18n/locales/en-US.json +44 -44
  23. package/dist/esm/i18n/locales/es-ES.json +44 -44
  24. package/dist/esm/i18n/locales/locales/en-US.json +44 -44
  25. package/dist/esm/i18n/locales/locales/es-ES.json +44 -44
  26. package/dist/esm/index.js +4 -0
  27. package/dist/esm/mixins/OxyServices.applications.js +3 -1
  28. package/dist/esm/mixins/OxyServices.reputation.js +241 -0
  29. package/dist/esm/mixins/OxyServices.workspaces.js +3 -1
  30. package/dist/esm/mixins/index.js +2 -2
  31. package/dist/esm/utils/accountUtils.js +12 -5
  32. package/dist/esm/utils/ssoReturn.js +80 -33
  33. package/dist/types/.tsbuildinfo +1 -1
  34. package/dist/types/HttpService.d.ts +57 -0
  35. package/dist/types/OxyServices.d.ts +1 -1
  36. package/dist/types/constants/version.d.ts +2 -2
  37. package/dist/types/index.d.ts +2 -1
  38. package/dist/types/mixins/OxyServices.applications.d.ts +8 -2
  39. package/dist/types/mixins/OxyServices.features.d.ts +0 -1
  40. package/dist/types/mixins/OxyServices.reputation.d.ts +436 -0
  41. package/dist/types/mixins/OxyServices.workspaces.d.ts +8 -2
  42. package/dist/types/mixins/index.d.ts +2 -2
  43. package/dist/types/models/interfaces.d.ts +15 -26
  44. package/dist/types/utils/accountUtils.d.ts +17 -4
  45. package/dist/types/utils/ssoReturn.d.ts +30 -9
  46. package/package.json +2 -1
  47. package/src/AuthManager.ts +3 -1
  48. package/src/HttpService.ts +91 -0
  49. package/src/OxyServices.ts +1 -1
  50. package/src/__tests__/httpServiceCache.test.ts +198 -0
  51. package/src/constants/version.ts +1 -1
  52. package/src/i18n/locales/en-US.json +44 -44
  53. package/src/i18n/locales/es-ES.json +44 -44
  54. package/src/index.ts +32 -4
  55. package/src/mixins/OxyServices.applications.ts +8 -2
  56. package/src/mixins/OxyServices.auth.ts +2 -1
  57. package/src/mixins/OxyServices.features.ts +0 -1
  58. package/src/mixins/OxyServices.reputation.ts +674 -0
  59. package/src/mixins/OxyServices.workspaces.ts +8 -2
  60. package/src/mixins/__tests__/reputation.test.ts +408 -0
  61. package/src/mixins/index.ts +3 -3
  62. package/src/models/interfaces.ts +16 -32
  63. package/src/utils/__tests__/accountUtils.test.ts +142 -0
  64. package/src/utils/__tests__/consumeSsoReturn.test.ts +229 -37
  65. package/src/utils/accountUtils.ts +20 -5
  66. package/src/utils/ssoReturn.ts +98 -37
  67. package/dist/cjs/mixins/OxyServices.developer.js +0 -97
  68. package/dist/cjs/mixins/OxyServices.karma.js +0 -108
  69. package/dist/esm/mixins/OxyServices.developer.js +0 -94
  70. package/dist/esm/mixins/OxyServices.karma.js +0 -105
  71. package/dist/types/mixins/OxyServices.developer.d.ts +0 -106
  72. package/dist/types/mixins/OxyServices.karma.d.ts +0 -92
  73. package/src/mixins/OxyServices.karma.ts +0 -111
@@ -104,14 +104,60 @@ export declare class HttpService {
104
104
  * into a `Headers`-compatible object.
105
105
  */
106
106
  private static parseXHRHeaders;
107
+ /**
108
+ * Delimiter that separates the logical `method:url[:data]` portion of a
109
+ * cache key from its identity suffix. Always APPENDED, never used to parse
110
+ * a key apart, so the `method:url` prefix stays intact for
111
+ * `clearCacheByPrefix` sweeps and `clearCacheEntry` base-key matching.
112
+ * The `clearCacheEntry` callsites all pass fixed, dataless logical keys
113
+ * (`GET:/users/<id>`, `GET:/session/user/<sessionId>`,
114
+ * `GET:/fedcm/me/authorized-apps`), so this readable suffix can never be
115
+ * ambiguous with a serialized request body.
116
+ */
117
+ private static readonly CACHE_IDENTITY_DELIM;
118
+ /**
119
+ * Derive a stable, non-sensitive identity discriminator for cache scoping.
120
+ *
121
+ * The GET-response cache MUST be partitioned by caller identity: endpoints
122
+ * with optional auth (e.g. `GET /profiles/recommendations`) return different
123
+ * content for an anonymous vs an authenticated caller, and per-user content
124
+ * for different authenticated users. Keying solely on `method:url:data`
125
+ * (the previous behavior) let an anonymous response be served to an
126
+ * authenticated caller — surfacing as "Who to follow" recommending accounts
127
+ * the user already follows after a cold-boot session restore.
128
+ *
129
+ * We use the access token's decoded user id (`userId || id`) rather than the
130
+ * raw JWT so the token never lands in a cache key (no token leakage through
131
+ * any cache-key logging, no key bloat). The acting-as id is folded in because
132
+ * managed-account responses differ per acting identity — and `X-Acting-As`
133
+ * already changes the server response for the same bearer token. Falls back
134
+ * to `'anon'` when there is no token, and to a short FNV-1a hash of the token
135
+ * only if it is present but cannot be decoded (degraded but still partitioned,
136
+ * never colliding anon with authed).
137
+ */
138
+ private computeIdentityTag;
107
139
  /**
108
140
  * Generate cache key efficiently
109
141
  * Uses a content-addressed hash for large payloads so two requests with
110
142
  * the same shape but different values never collide on the same key
111
143
  * (which would silently serve stale data — e.g. paginated search results,
112
144
  * large object updates).
145
+ *
146
+ * The key is identity-scoped: the logical `method:url[:data]` portion is
147
+ * suffixed with ` id=<identityTag>` so two callers with different
148
+ * identities (anon vs authed, or two different users) never share an entry.
149
+ * The identity tag is placed at the END so the key still STARTS with
150
+ * `method:url`, preserving the prefix-based invalidation in
151
+ * `clearCacheByPrefix` (e.g. `GET:/session/user/`) and the base-key matching
152
+ * in `clearCacheEntry`.
113
153
  */
114
154
  private generateCacheKey;
155
+ /**
156
+ * Build the identity-agnostic portion of a cache key (`method:url[:data]`).
157
+ * Kept separate so identity scoping is applied in exactly one place
158
+ * (`generateCacheKey`) and cannot drift between the cache and dedupe paths.
159
+ */
160
+ private generateBaseCacheKey;
115
161
  /**
116
162
  * Build full URL with query params
117
163
  */
@@ -169,6 +215,17 @@ export declare class HttpService {
169
215
  hasAccessToken(): boolean;
170
216
  getBaseURL(): string;
171
217
  clearCache(): void;
218
+ /**
219
+ * Delete a cache entry by its LOGICAL key (`method:url[:data]`).
220
+ *
221
+ * Because the response cache is identity-scoped — stored keys carry an
222
+ * ` id=<identityTag>` suffix — a caller passing the logical key
223
+ * `GET:/users/<id>` must invalidate that resource for EVERY identity that
224
+ * cached it (e.g. `updateProfile` busting a user representation that may be
225
+ * cached under both the owner's id and a viewer's id). We therefore delete
226
+ * the exact key (for any pre-existing un-suffixed entries) AND every
227
+ * identity-scoped variant `<key> id=*`.
228
+ */
172
229
  clearCacheEntry(key: string): void;
173
230
  /**
174
231
  * Delete every cache entry whose key starts with `prefix`.
@@ -80,7 +80,7 @@ import { composeOxyServices } from './mixins';
80
80
  * - **Privacy**: Blocked and restricted users
81
81
  * - **Language**: Language detection and metadata
82
82
  * - **Payment**: Payment processing
83
- * - **Karma**: Karma system
83
+ * - **Reputation**: Reputation system (Oxy Trust)
84
84
  * - **Assets**: File upload and asset management
85
85
  * - **Applications**: Application, membership, and credential management
86
86
  * - **Workspaces**: Workspace and membership management
@@ -5,9 +5,9 @@
5
5
  export declare const packageInfo: {
6
6
  readonly name: "@oxyhq/services";
7
7
  readonly version: "5.2.1";
8
- readonly description: "Reusable OxyHQ module to handle authentication, user management, karma system and more 🚀";
8
+ readonly description: "Reusable OxyHQ module to handle authentication, user management, reputation system (Oxy Trust) and more 🚀";
9
9
  readonly main: "lib/commonjs/node/index.js";
10
10
  readonly module: "lib/module/node/index.js";
11
11
  readonly types: "lib/typescript/node/index.d.ts";
12
12
  };
13
- export declare const name: "@oxyhq/services", version: "5.2.1", description: "Reusable OxyHQ module to handle authentication, user management, karma system and more 🚀";
13
+ export declare const name: "@oxyhq/services", version: "5.2.1", description: "Reusable OxyHQ module to handle authentication, user management, reputation system (Oxy Trust) and more 🚀";
@@ -35,6 +35,7 @@ export type { ContactDiscoveryMatch, ContactDiscoveryResponse, } from './mixins/
35
35
  export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
36
36
  export type { Application, PublicApplication, ApplicationMember, ApplicationCredential, ApplicationRole, ApplicationType, ApplicationStatus, ApplicationMemberStatus, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, InviteApplicationMemberInput, UpdateApplicationMemberInput, TransferApplicationOwnershipInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, ApplicationSuccessResult, } from './mixins/OxyServices.applications';
37
37
  export type { Workspace, WorkspaceMember, WorkspaceRole, WorkspaceType, WorkspaceStatus, WorkspaceMemberStatus, CreateWorkspaceInput, UpdateWorkspaceInput, InviteWorkspaceMemberInput, UpdateWorkspaceMemberInput, TransferWorkspaceOwnershipInput, WorkspaceSuccessResult, } from './mixins/OxyServices.workspaces';
38
+ export type { ReputationCategory, TrustTier, ReputationTransactionStatus, ReputationTargetEntityType, ReputationDisputeStatus, ReputationInfluenceContext, ReputationTransaction, ReputationBalanceBreakdown, ReputationInfluence, ReputationReliability, ReputationBalance, ReputationDispute, ReputationRule, ReputationLeaderboardEntry, ReputationInfluenceResult, ReverseReputationTransactionResult, AwardReputationInput, CreateReputationDisputeInput, ResolveReputationDisputeInput, UpsertReputationRuleInput, ReverseReputationTransactionInput, } from './mixins/OxyServices.reputation';
38
39
  export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers';
39
40
  export type { HandleApiErrorOptions } from './utils/authHelpers';
40
41
  export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual, } from './utils/sessionUtils';
@@ -48,7 +49,7 @@ export { RecoveryPhraseService } from './crypto/recoveryPhrase';
48
49
  export type { RecoveryPhraseResult } from './crypto/recoveryPhrase';
49
50
  export { DeviceManager } from './utils/deviceManager';
50
51
  export type { DeviceFingerprint, StoredDeviceInfo } from './utils/deviceManager';
51
- export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, KarmaRule, KarmaHistory, KarmaLeaderboardEntry, KarmaAwardRequest, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceSession, DeviceSessionsResponse, DeviceSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
52
+ export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceSession, DeviceSessionsResponse, DeviceSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
52
53
  export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
53
54
  export { TopicType, TopicSource } from './models/Topic';
54
55
  export type { TopicData, TopicTranslation } from './models/Topic';
@@ -179,7 +179,11 @@ export interface UpdateApplicationInput {
179
179
  }
180
180
  /** Input accepted by `inviteApplicationMember`. The owner role cannot be invited. */
181
181
  export interface InviteApplicationMemberInput {
182
- userId: string;
182
+ /**
183
+ * The username or email of the user to invite. Resolved to a user server-side;
184
+ * an unknown value yields a 404 "User not found".
185
+ */
186
+ usernameOrEmail: string;
183
187
  role: Exclude<ApplicationRole, 'owner'>;
184
188
  }
185
189
  /** Input accepted by `updateApplicationMember`. */
@@ -300,7 +304,9 @@ export declare function OxyServicesApplicationsMixin<T extends typeof OxyService
300
304
  /**
301
305
  * Add a member to an application.
302
306
  * @param applicationId - The application's Mongo `_id`.
303
- * @param data - Target user id and role (never `owner`).
307
+ * @param data - Target user's username or email and role (never `owner`).
308
+ * The server resolves `usernameOrEmail` to a user; an unknown value yields
309
+ * a 404 "User not found".
304
310
  */
305
311
  inviteApplicationMember(applicationId: string, data: InviteApplicationMemberInput): Promise<ApplicationMember>;
306
312
  /**
@@ -65,7 +65,6 @@ export interface UserStats {
65
65
  commentCount: number;
66
66
  followerCount: number;
67
67
  followingCount: number;
68
- karmaScore?: number;
69
68
  }
70
69
  export interface HistoryItem {
71
70
  id: string;
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Reputation Methods Mixin (Oxy Trust)
3
+ *
4
+ * Provides typed access to the reputation ledger (#217) and the derived
5
+ * trust-tier / capped-influence model (#219) via the `/reputation` API.
6
+ *
7
+ * The reputation ledger is append-only: transactions are NEVER deleted.
8
+ * A correction is expressed either as a compensating REVERSAL (the original is
9
+ * marked `reversed` and a new `active` transaction with negated points is
10
+ * appended) or a VOID (the original is marked `voided` and excluded from the
11
+ * balance). A user's `ReputationBalance` is a recomputable cache of the sum of
12
+ * their `active` transactions, augmented with a trust tier, capped influence
13
+ * weights, and reliability signals.
14
+ *
15
+ * Reference users by their Mongo `_id` (or publicKey, which the API resolves),
16
+ * transactions by their `id`, and disputes by their `id`.
17
+ */
18
+ import type { OxyServicesBase } from '../OxyServices.base';
19
+ import type { User } from '../models/interfaces';
20
+ /**
21
+ * Category bucket a reputation transaction falls into. Drives the per-category
22
+ * balance breakdown.
23
+ */
24
+ export type ReputationCategory = 'content' | 'social' | 'trust' | 'moderation' | 'physical' | 'penalty' | 'other';
25
+ /** Trust tiers, lowest → highest (plus the punitive `restricted`). */
26
+ export type TrustTier = 'new' | 'trusted' | 'high_trust' | 'verified' | 'restricted';
27
+ /**
28
+ * Transaction lifecycle status. Only `active` transactions count toward the
29
+ * balance; `disputed` still counts until the dispute resolves; `reversed` and
30
+ * `voided` are excluded.
31
+ */
32
+ export type ReputationTransactionStatus = 'active' | 'disputed' | 'reversed' | 'voided';
33
+ /** Kind of entity a transaction may target. */
34
+ export type ReputationTargetEntityType = 'post' | 'comment' | 'report' | 'purchase' | 'event' | 'check_in' | 'manual_review' | 'user' | 'other';
35
+ /** Dispute lifecycle status. */
36
+ export type ReputationDisputeStatus = 'open' | 'accepted' | 'rejected' | 'needs_review';
37
+ /** Influence context selecting which capped weight axis to return. */
38
+ export type ReputationInfluenceContext = 'default' | 'report' | 'moderation' | 'ranking';
39
+ /**
40
+ * A single immutable entry in the reputation ledger. Ids are emitted as strings
41
+ * and dates as ISO strings by the API.
42
+ */
43
+ export interface ReputationTransaction {
44
+ /** The transaction's Mongo `_id` as a string. */
45
+ id: string;
46
+ /** Subject of the reputation change — the user whose balance moves. */
47
+ userId: string;
48
+ /** Signed point delta. Positive awards, negative penalties/reversals. */
49
+ points: number;
50
+ /** The rule/action key that produced this transaction (e.g. `post_created`). */
51
+ actionType: string;
52
+ /** Category bucket the points fall into. */
53
+ category: ReputationCategory;
54
+ /** Canonical source application that reported the action, if any. */
55
+ applicationId?: string;
56
+ /** The specific credential used by the source application, if any. */
57
+ credentialId?: string;
58
+ /** Opaque id of the originating action in the source system (idempotency key). */
59
+ sourceActionId?: string;
60
+ /** Source-system action type (e.g. `report_confirmed`, `event_check_in`). */
61
+ sourceActionType?: string;
62
+ /** Id of the entity the action targeted (post id, report id, etc.). */
63
+ targetEntityId?: string;
64
+ /** Kind of the targeted entity. */
65
+ targetEntityType?: ReputationTargetEntityType;
66
+ /** Lifecycle status — only `active` transactions count toward the balance. */
67
+ status: ReputationTransactionStatus;
68
+ /**
69
+ * Set ONLY on a compensating reversal transaction; references the original
70
+ * transaction it reverses. The original carries `status: 'reversed'`.
71
+ */
72
+ reversedTransactionId?: string;
73
+ /** Human-readable reason / note. */
74
+ reason?: string;
75
+ /** Free-form structured metadata from the source system. */
76
+ metadata?: Record<string, unknown>;
77
+ /** The user who caused this change (the liker, the reporting user, staff). */
78
+ createdByUserId?: string;
79
+ /** Staff/service principal who reviewed (reversed/voided) this transaction. */
80
+ reviewedByUserId?: string;
81
+ /** ISO timestamp the transaction was reviewed at, if reviewed. */
82
+ reviewedAt?: string;
83
+ /** ISO creation timestamp. */
84
+ createdAt: string;
85
+ /** ISO last-update timestamp. */
86
+ updatedAt: string;
87
+ }
88
+ /**
89
+ * Per-category sums of a user's ACTIVE transactions. `penalties` is the
90
+ * absolute sum of every negative-point transaction; the named buckets carry the
91
+ * signed sum of transactions in that category.
92
+ */
93
+ export interface ReputationBalanceBreakdown {
94
+ content: number;
95
+ social: number;
96
+ trust: number;
97
+ moderation: number;
98
+ physical: number;
99
+ penalties: number;
100
+ }
101
+ /**
102
+ * Capped influence weights (#219). Every weight is clamped to a configured
103
+ * range; restricted users are floored on every axis. Downstream systems
104
+ * (ranking, moderation, reporting) consume these to weight a user's
105
+ * contributions without letting any single user dominate.
106
+ */
107
+ export interface ReputationInfluence {
108
+ /** General-purpose trust weight derived from the lifetime total. */
109
+ defaultWeight: number;
110
+ /** Weight applied to this user's reports (scales with report accuracy). */
111
+ reportWeight: number;
112
+ /** Weight applied to this user's moderation actions (scales with tier). */
113
+ moderationWeight: number;
114
+ /** Damped weight applied to this user's ranking feedback. */
115
+ rankingFeedbackWeight: number;
116
+ }
117
+ /**
118
+ * Reliability signals (#219) derived from the user's moderation track record in
119
+ * the ledger.
120
+ */
121
+ export interface ReputationReliability {
122
+ /** Count of active transactions stamped `report_confirmed`. */
123
+ accurateReports: number;
124
+ /** Count of active transactions stamped `report_rejected`. */
125
+ rejectedReports: number;
126
+ /** accurate / (accurate + rejected), or the neutral 0.5 when no history. */
127
+ reportAccuracyScore: number;
128
+ /** Smoothed 0..1 abuse signal; high values force the `restricted` tier. */
129
+ abuseScore: number;
130
+ }
131
+ /**
132
+ * Cached, recomputable snapshot of a user's reputation. Shape mirrors the
133
+ * `/reputation/:userId/balance` response (which omits internal `lastTransactionId`
134
+ * and `createdAt`).
135
+ */
136
+ export interface ReputationBalance {
137
+ userId: string;
138
+ /** Net lifetime total across all active transactions. */
139
+ total: number;
140
+ /** Sum of positive points only. */
141
+ positive: number;
142
+ /** Sum of negative points only (a negative number). */
143
+ negative: number;
144
+ breakdown: ReputationBalanceBreakdown;
145
+ trustTier: TrustTier;
146
+ influence: ReputationInfluence;
147
+ reliability: ReputationReliability;
148
+ /** ISO timestamp the snapshot was last recomputed at. */
149
+ recalculatedAt: string;
150
+ /** ISO last-update timestamp. */
151
+ updatedAt: string;
152
+ }
153
+ /**
154
+ * A user-initiated dispute against a specific reputation transaction. Ids are
155
+ * strings and dates ISO strings.
156
+ */
157
+ export interface ReputationDispute {
158
+ /** The dispute's Mongo `_id` as a string. */
159
+ id: string;
160
+ /** The transaction being disputed. */
161
+ transactionId: string;
162
+ /** The user raising the dispute. */
163
+ userId: string;
164
+ /** Why the user believes the transaction is wrong. */
165
+ reason: string;
166
+ status: ReputationDisputeStatus;
167
+ /** Optional supporting evidence (URLs / references). */
168
+ evidence?: string[];
169
+ /** ISO timestamp the dispute was resolved at, if resolved. */
170
+ resolvedAt?: string;
171
+ /** Staff principal who resolved the dispute, if resolved. */
172
+ resolvedByUserId?: string;
173
+ /** ISO creation timestamp. */
174
+ createdAt: string;
175
+ /** ISO last-update timestamp. */
176
+ updatedAt: string;
177
+ }
178
+ /**
179
+ * A configurable reputation award/penalty rule. The `/reputation/rules`
180
+ * response shape: `id` is the rule's `_id`; no timestamps are emitted.
181
+ */
182
+ export interface ReputationRule {
183
+ /** The rule's Mongo `_id` as a string. */
184
+ id: string;
185
+ /** Unique action key (e.g. `post_created`). */
186
+ actionType: string;
187
+ /** Signed points the rule awards (may be negative for penalties). */
188
+ points: number;
189
+ /** Category the resulting transaction is filed under. */
190
+ category: ReputationCategory;
191
+ description: string;
192
+ /** Per (user, actionType) cooldown in minutes; 0 disables the cooldown. */
193
+ cooldownInMinutes: number;
194
+ isEnabled: boolean;
195
+ }
196
+ /**
197
+ * A single leaderboard entry. `user` is the populated user document the API
198
+ * returns alongside the lifetime total, derived trust tier, and 1-based rank.
199
+ */
200
+ export interface ReputationLeaderboardEntry {
201
+ /** The populated user (id, username, name, avatar, publicKey). */
202
+ user: Pick<User, 'id' | 'username' | 'name' | 'avatar' | 'publicKey'> & Partial<User>;
203
+ /** Net lifetime total. */
204
+ total: number;
205
+ /** Derived trust tier. */
206
+ trustTier: TrustTier;
207
+ /** 1-based rank within the leaderboard (`offset + index + 1`). */
208
+ rank: number;
209
+ }
210
+ /**
211
+ * Result of `getReputationInfluence` — the requested context, the single capped
212
+ * weight for that context, and the full influence block.
213
+ */
214
+ export interface ReputationInfluenceResult {
215
+ context: ReputationInfluenceContext;
216
+ weight: number;
217
+ influence: ReputationInfluence;
218
+ }
219
+ /**
220
+ * Result of `reverseReputationTransaction` — the now-`reversed` original plus
221
+ * the compensating `active` reversal entry.
222
+ */
223
+ export interface ReverseReputationTransactionResult {
224
+ original: ReputationTransaction;
225
+ reversal: ReputationTransaction;
226
+ }
227
+ /**
228
+ * Input for `awardReputation`. Awarding is restricted to service tokens (the
229
+ * canonical path) and platform staff; regular users may NOT award reputation.
230
+ * When called with a service token, `applicationId` / `credentialId` are
231
+ * resolved from the token and any client-supplied values are ignored.
232
+ */
233
+ export interface AwardReputationInput {
234
+ /** The subject whose reputation changes (`_id` or publicKey). */
235
+ userId: string;
236
+ /** The enabled rule's action key (e.g. `post_created`). */
237
+ actionType: string;
238
+ /** Source application id (ignored for service tokens). */
239
+ applicationId?: string;
240
+ /** Source credential id (ignored for service tokens). */
241
+ credentialId?: string;
242
+ /** Opaque originating-action id used as the idempotency key. */
243
+ sourceActionId?: string;
244
+ /** Source-system action type. */
245
+ sourceActionType?: string;
246
+ /** Id of the targeted entity. */
247
+ targetEntityId?: string;
248
+ /** Kind of the targeted entity. */
249
+ targetEntityType?: ReputationTargetEntityType;
250
+ /** Optional human-readable reason (max 500 chars). */
251
+ reason?: string;
252
+ /** Free-form structured metadata from the source system. */
253
+ metadata?: Record<string, unknown>;
254
+ }
255
+ /** Input for `createReputationDispute`. The disputer is the authenticated user. */
256
+ export interface CreateReputationDisputeInput {
257
+ /** The transaction being disputed. */
258
+ transactionId: string;
259
+ /** Why the transaction is believed to be wrong (1..1000 chars). */
260
+ reason: string;
261
+ /** Optional supporting evidence (URLs / references; max 20). */
262
+ evidence?: string[];
263
+ }
264
+ /** Input for `resolveReputationDispute` (staff). */
265
+ export interface ResolveReputationDisputeInput {
266
+ /** Accepting reverses the disputed transaction; rejecting restores it. */
267
+ status: 'accepted' | 'rejected';
268
+ }
269
+ /** Input for `upsertReputationRule` (staff). Keyed by `actionType`. */
270
+ export interface UpsertReputationRuleInput {
271
+ /** Unique action key (e.g. `post_created`). */
272
+ actionType: string;
273
+ /** Signed points the rule awards (may be negative). */
274
+ points: number;
275
+ /** Category the resulting transaction is filed under. */
276
+ category: ReputationCategory;
277
+ /** Human-readable description (1..500 chars). */
278
+ description: string;
279
+ /** Per (user, actionType) cooldown in minutes; defaults to 0. */
280
+ cooldownInMinutes?: number;
281
+ /** Whether the rule is active; defaults to true. */
282
+ isEnabled?: boolean;
283
+ }
284
+ /**
285
+ * Input for `reverseReputationTransaction` / `voidReputationTransaction`
286
+ * (staff). The reviewing principal is the authenticated user.
287
+ */
288
+ export interface ReverseReputationTransactionInput {
289
+ /** Optional human-readable reason (max 500 chars). */
290
+ reason?: string;
291
+ }
292
+ export declare function OxyServicesReputationMixin<T extends typeof OxyServicesBase>(Base: T): {
293
+ new (...args: any[]): {
294
+ /**
295
+ * Get a user's cached reputation balance — derived totals, per-category
296
+ * breakdown, trust tier, capped influence weights, and reliability signals.
297
+ * @param userId - The subject user's `_id` or publicKey.
298
+ */
299
+ getReputationBalance(userId: string): Promise<ReputationBalance>;
300
+ /**
301
+ * Get the reputation leaderboard, ordered by lifetime total descending.
302
+ * @param limit - Page size (server-capped).
303
+ * @param offset - Page offset.
304
+ */
305
+ getReputationLeaderboard(limit?: number, offset?: number): Promise<ReputationLeaderboardEntry[]>;
306
+ /**
307
+ * List the enabled reputation rules (for client display).
308
+ */
309
+ getReputationRules(): Promise<ReputationRule[]>;
310
+ /**
311
+ * Get a user's paginated reputation ledger, newest first (auth required).
312
+ * @param userId - The subject user's `_id` or publicKey.
313
+ * @param limit - Page size (server-capped).
314
+ * @param offset - Page offset.
315
+ */
316
+ getReputationTransactions(userId: string, limit?: number, offset?: number): Promise<ReputationTransaction[]>;
317
+ /**
318
+ * Get a user's capped influence weight for a given context (auth required).
319
+ * @param userId - The subject user's `_id` or publicKey.
320
+ * @param context - The weight axis to read (defaults server-side to `default`).
321
+ */
322
+ getReputationInfluence(userId: string, context?: ReputationInfluenceContext): Promise<ReputationInfluenceResult>;
323
+ /**
324
+ * Award (or penalise) reputation to a user by `actionType`. Restricted to
325
+ * service tokens and platform staff. Invalidates cached reputation reads.
326
+ * @param input - The award payload (subject, action, source, target, etc.).
327
+ */
328
+ awardReputation(input: AwardReputationInput): Promise<ReputationTransaction>;
329
+ /**
330
+ * Open a dispute against a transaction (auth required; the disputer is the
331
+ * authenticated user and must own the transaction).
332
+ * @param input - The transaction id, reason, and optional evidence.
333
+ */
334
+ createReputationDispute(input: CreateReputationDisputeInput): Promise<ReputationDispute>;
335
+ /**
336
+ * List a user's own reputation disputes (auth required; the caller must be
337
+ * the subject or platform staff).
338
+ * @param userId - The subject user's `_id` or publicKey.
339
+ * @param limit - Page size (server-capped).
340
+ * @param offset - Page offset.
341
+ */
342
+ getUserReputationDisputes(userId: string, limit?: number, offset?: number): Promise<ReputationDispute[]>;
343
+ /**
344
+ * Create or update a reputation rule, keyed by `actionType` (staff only).
345
+ * Invalidates the cached rule list.
346
+ * @param input - The rule definition.
347
+ */
348
+ upsertReputationRule(input: UpsertReputationRuleInput): Promise<ReputationRule>;
349
+ /**
350
+ * Reverse a transaction (staff only): mark the original `reversed` and append
351
+ * a compensating `active` reversal with negated points. Invalidates cached
352
+ * reputation reads.
353
+ * @param transactionId - The transaction's id.
354
+ * @param input - Optional reason for the reversal.
355
+ */
356
+ reverseReputationTransaction(transactionId: string, input?: ReverseReputationTransactionInput): Promise<ReverseReputationTransactionResult>;
357
+ /**
358
+ * Void a transaction (staff only): mark it `voided` so it is excluded from
359
+ * the balance, with NO compensating entry. Invalidates cached reputation
360
+ * reads.
361
+ * @param transactionId - The transaction's id.
362
+ * @param input - Optional reason for the void.
363
+ */
364
+ voidReputationTransaction(transactionId: string, input?: ReverseReputationTransactionInput): Promise<ReputationTransaction>;
365
+ /**
366
+ * Force a recompute of a user's balance snapshot from their active ledger
367
+ * (staff only). Invalidates cached reputation reads.
368
+ * @param userId - The subject user's `_id` or publicKey.
369
+ */
370
+ recalculateReputation(userId: string): Promise<ReputationBalance>;
371
+ /**
372
+ * Get the open dispute queue across all users (staff only).
373
+ * @param limit - Page size (server-capped).
374
+ * @param offset - Page offset.
375
+ */
376
+ getReputationDisputeQueue(limit?: number, offset?: number): Promise<ReputationDispute[]>;
377
+ /**
378
+ * Resolve a dispute (staff only). Accepting reverses the disputed
379
+ * transaction; rejecting restores it to `active`. Invalidates cached
380
+ * reputation reads.
381
+ * @param disputeId - The dispute's id.
382
+ * @param input - The resolution (`accepted` or `rejected`).
383
+ */
384
+ resolveReputationDispute(disputeId: string, input: ResolveReputationDisputeInput): Promise<ReputationDispute>;
385
+ httpService: import("../HttpService").HttpService;
386
+ cloudURL: string;
387
+ config: import("../OxyServices.base").OxyConfig;
388
+ __resetTokensForTests(): void;
389
+ makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
390
+ getBaseURL(): string;
391
+ getSessionBaseUrl(): string;
392
+ getClient(): import("../HttpService").HttpService;
393
+ getMetrics(): {
394
+ totalRequests: number;
395
+ successfulRequests: number;
396
+ failedRequests: number;
397
+ cacheHits: number;
398
+ cacheMisses: number;
399
+ averageResponseTime: number;
400
+ };
401
+ clearCache(): void;
402
+ clearCacheEntry(key: string): void;
403
+ clearCacheByPrefix(prefix: string): number;
404
+ getCacheStats(): {
405
+ size: number;
406
+ hits: number;
407
+ misses: number;
408
+ hitRate: number;
409
+ };
410
+ getCloudURL(): string;
411
+ setTokens(accessToken: string, refreshToken?: string): void;
412
+ clearTokens(): void;
413
+ onTokensChanged(listener: (accessToken: string | null) => void): () => void;
414
+ _cachedUserId: string | null | undefined;
415
+ _cachedAccessToken: string | null;
416
+ getCurrentUserId(): string | null;
417
+ hasValidToken(): boolean;
418
+ getAccessToken(): string | null;
419
+ setActingAs(userId: string | null): void;
420
+ getActingAs(): string | null;
421
+ waitForAuth(timeoutMs?: number): Promise<boolean>;
422
+ withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
423
+ maxRetries?: number;
424
+ retryDelay?: number;
425
+ authTimeoutMs?: number;
426
+ }): Promise<T_1>;
427
+ validate(): Promise<boolean>;
428
+ handleError(error: unknown): Error;
429
+ healthCheck(): Promise<{
430
+ status: string;
431
+ users?: number;
432
+ timestamp?: string;
433
+ [key: string]: any;
434
+ }>;
435
+ };
436
+ } & T;
@@ -72,7 +72,11 @@ export interface UpdateWorkspaceInput {
72
72
  }
73
73
  /** Input accepted by `inviteWorkspaceMember`. The owner role cannot be invited. */
74
74
  export interface InviteWorkspaceMemberInput {
75
- userId: string;
75
+ /**
76
+ * The username or email of the user to invite. Resolved to a user server-side;
77
+ * an unknown value yields a 404 "User not found".
78
+ */
79
+ usernameOrEmail: string;
76
80
  role: Exclude<WorkspaceRole, 'owner'>;
77
81
  }
78
82
  /** Input accepted by `updateWorkspaceMember`. The owner role cannot be assigned. */
@@ -122,7 +126,9 @@ export declare function OxyServicesWorkspacesMixin<T extends typeof OxyServicesB
122
126
  /**
123
127
  * Add a member to a workspace.
124
128
  * @param workspaceId - The workspace's Mongo `_id`.
125
- * @param data - Target user id and role (never `owner`).
129
+ * @param data - Target user's username or email and role (never `owner`).
130
+ * The server resolves `usernameOrEmail` to a user; an unknown value yields
131
+ * a 404 "User not found".
126
132
  */
127
133
  inviteWorkspaceMember(workspaceId: string, data: InviteWorkspaceMemberInput): Promise<WorkspaceMember>;
128
134
  /**
@@ -14,7 +14,7 @@ import { OxyServicesUserMixin } from './OxyServices.user';
14
14
  import { OxyServicesPrivacyMixin } from './OxyServices.privacy';
15
15
  import { OxyServicesLanguageMixin } from './OxyServices.language';
16
16
  import { OxyServicesPaymentMixin } from './OxyServices.payment';
17
- import { OxyServicesKarmaMixin } from './OxyServices.karma';
17
+ import { OxyServicesReputationMixin } from './OxyServices.reputation';
18
18
  import { OxyServicesAssetsMixin } from './OxyServices.assets';
19
19
  import { OxyServicesApplicationsMixin } from './OxyServices.applications';
20
20
  import { OxyServicesWorkspacesMixin } from './OxyServices.workspaces';
@@ -37,7 +37,7 @@ import { OxyServicesAppDataMixin } from './OxyServices.appData';
37
37
  * If you add a new mixin to `MIXIN_PIPELINE`, add it here too so its methods
38
38
  * are visible without a cast.
39
39
  */
40
- type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFedCMMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPopupAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesRedirectAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSsoMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesKarmaMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesWorkspacesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesManagedAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
40
+ type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFedCMMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPopupAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesRedirectAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSsoMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesWorkspacesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesManagedAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
41
41
  /**
42
42
  * Constructor type for the fully composed mixin pipeline. Each mixin returns
43
43
  * a new constructor that augments its input; reducing across the pipeline