@recursiv/sdk 0.7.89 → 0.7.96
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/README.md +2 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.js +6 -1
- package/dist/resources/moderation.d.ts +26 -0
- package/dist/resources/signals.d.ts +129 -6
- package/dist/resources/signals.js +58 -2
- package/dist/resources/trust.d.ts +85 -0
- package/dist/resources/trust.js +26 -0
- package/dist/types.d.ts +8 -0
- package/llm.md +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
100
100
|
| `r.communities` | `create`, `join`, `search`, `members` |
|
|
101
101
|
| `r.chat` | `send`, `dm`, `createGroup` (`encrypted: true` for an end-to-end encrypted group, 0.7.59), `messages` (cursor paging), `acceptRequest`, `declineRequest`, `leave`, `deleteConversation`; a private group's admin: `addMembers`, `removeMember`, `updateGroup` (0.7.59); photos and voice notes: `uploadAttachment`, `signAttachments` (private chat storage, 0.7.82) |
|
|
102
102
|
| `r.tags` | `create`, `list` |
|
|
103
|
-
| `r.signals` | `batch` (up to 100 engagement signals, such as views, dwell and hides, for the member's own ranking; `keepalive` for a page-leave flush), `record` (one signal) (0.7.71) |
|
|
103
|
+
| `r.signals` | `batch` (up to 100 engagement signals, such as views, dwell and hides, for the member's own ranking; `keepalive` for a page-leave flush), `record` (one signal) (0.7.71); `clearViewingHistory` ("Clear my viewing history"); typed `SignalMetadata`, an id per event so a retry is stored once, and `duplicates`/`limited`/`rejected` in the answer (0.7.93) |
|
|
104
104
|
| `r.privacy` | `capabilities` (the platform privacy facts the deployed API's build enforces, and its commit); pair with `@recursiv/sdk/privacy` (`PRIVACY_FACTS`, `isFactLive`, `liveFacts`) (unreleased) |
|
|
105
105
|
| `r.curator` | `today` (World Today; a world story can carry `story_key` and `discussion`, its count across networks), `storyPosts` / `memberStoryPosts` (one page of a story's posts, keyless or under the member's key), `storyPostPages` (walk every page), `forYou`, `run`, `refreshIfStale`, `seedDeck` |
|
|
106
106
|
| `r.uploads` | `uploadMedia` — upload post/chat bytes end to end |
|
|
@@ -120,6 +120,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
120
120
|
| `r.integrations` | External service connections |
|
|
121
121
|
| `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
|
|
122
122
|
| `r.moderation` | Moderation actions: `publicLog`, `mine`, `appeal`; staff `apply`, `resolveAppeal`, `list({ target_type?, target_id? })` (one target's recorded actions, newest first, at most 100; none named = the network's latest; 0.7.74) and `reverse(actionId)` (sent once: a repeat answers 404 `action_not_found`; 0.7.74); agent grants `listAgentGrants`, `grantAgent`, `revokeAgent` |
|
|
123
|
+
| `r.trust` | The signed trust log (0.7.96; 404 until the operator turns on `TRUST_LOG_PUBLIC_READS`): keyless `keys()` (every key that ever signed it), `mine({ before?, limit? })` (`users:read`: the caller's own signed events with the server's check `verified`). `r.moderation.publicLog` then carries `trust_log` on each action (`status`, and where the signed decision and reversal sit: `chain`, `seq`, `key_id`, `occurred_at`, `verified`). No read carries a hash or a signature |
|
|
123
124
|
| `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
|
|
124
125
|
| `r.goals` | Org goals with task rollups (group dispatcher tasks) |
|
|
125
126
|
| `r.linkPreview` | `get(url)` — a public link's Open Graph card (`data: null` when it has none). For a chat link pass `{ messageId }` or `{ conversationId }` on a signed-in key with `chat:read`: the link is fetched only when it is in the named message and a person wrote that message, or the caller wrote it in that conversation, else `refused: 'ai_link'`. A source with no id rejects (`ValidationError`). Not in encrypted chats: the server cannot read them, so leave `source` out there and apply the app's own rule |
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ import { JobsResource } from './resources/jobs.js';
|
|
|
60
60
|
import { CuratorResource } from './resources/curator.js';
|
|
61
61
|
import { ReportsResource } from './resources/reports.js';
|
|
62
62
|
import { ModerationResource } from './resources/moderation.js';
|
|
63
|
+
import { TrustResource } from './resources/trust.js';
|
|
63
64
|
import { JudgmentsResource } from './resources/judgments.js';
|
|
64
65
|
import { LinkPreviewResource } from './resources/link-preview.js';
|
|
65
66
|
import { SearchResource } from './resources/search.js';
|
|
@@ -134,6 +135,8 @@ export declare class Recursiv {
|
|
|
134
135
|
readonly boosts: BoostsResource;
|
|
135
136
|
readonly reports: ReportsResource;
|
|
136
137
|
readonly moderation: ModerationResource;
|
|
138
|
+
/** The signed trust log: its public keys, and the member's own signed events (0.7.96). */
|
|
139
|
+
readonly trust: TrustResource;
|
|
137
140
|
/** Jev's labels of a post: read them, dispute or appeal one, and (staff) review a dispute. */
|
|
138
141
|
readonly judgments: JudgmentsResource;
|
|
139
142
|
readonly linkPreview: LinkPreviewResource;
|
|
@@ -216,7 +219,9 @@ export type { ReportTargetType, ReportStatus, CreateReportInput, ReportRecord, R
|
|
|
216
219
|
export { JudgmentsResource, isJudgmentSharedWordsError } from './resources/judgments.js';
|
|
217
220
|
export type { JudgmentSubjectKind, JudgmentSource, JudgmentOverrideMethod, PostLabelsStatus, PostLabelsQuery, PostLabelsSubject, PostLabelsCategory, PostLabelsPolitics, PostJudgmentRecord, PostJudgment, PostSafetyAttribution, PostLabelsCategoryView, PostLabelsSummaryView, PostLabelsFullView, PostLabels, DisputeJudgmentInput, JudgmentDispute, ReviewJudgmentInput, JudgmentReviewUpheld, JudgmentReviewAwaitingReviewer, JudgmentReviewOverridden, JudgmentReview, JudgmentSharedWordsDetails, JudgmentSharedWordsError, } from './resources/judgments.js';
|
|
218
221
|
export { ModerationResource } from './resources/moderation.js';
|
|
219
|
-
export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, ModerationAction, ListModerationActionsParams, RecordedModerationTargetType, AgentModerationMode, AgentModerationGrant, } from './resources/moderation.js';
|
|
222
|
+
export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, ModerationAction, ListModerationActionsParams, RecordedModerationTargetType, AgentModerationMode, AgentModerationGrant, ModerationTrustStatus, ModerationTrustLog, ModerationTrustWindow, } from './resources/moderation.js';
|
|
223
|
+
export { TrustResource } from './resources/trust.js';
|
|
224
|
+
export type { TrustEnvelope, OwnTrustEvent, TrustPublicKey, OwnTrustEvents } from './resources/trust.js';
|
|
220
225
|
export { LinkPreviewResource } from './resources/link-preview.js';
|
|
221
226
|
export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
|
|
222
227
|
export type { PublicSearchPage, PublicSearchTab, SearchAnswer, SearchGroup, SearchLadder, SearchOmitReason, SearchParams, SearchPerson, SearchPost, SearchRelated, SearchResponse, SearchSection, SearchStage, SearchStory, SearchTopic, SearchVia, } from './resources/search.js';
|
|
@@ -225,8 +230,8 @@ export { FEED_TOP_NETWORKS, FeedsResource } from './resources/feeds.js';
|
|
|
225
230
|
export type { FeedStoriesParams, FeedStoriesResponse, FeedStory, FeedTopExplain, FeedTopItem, FeedTopMeta, FeedTopNetwork, FeedTopNetworkCard, FeedTopNetworkEntry, FeedTopNetworkMap, FeedTopParams, FeedTopResponse, FeedTopTarget, PublicFeedTopMeta, PublicFeedTopParams, PublicFeedTopResponse, } from './resources/feeds.js';
|
|
226
231
|
export type { BookmarkCollection, BookmarkCollectionInput, BookmarkCollectionKind, BookmarkCollectionPatch, BookmarkCollectionsList, BookmarkCollectionTarget, BookmarkCollectionVisibility, BookmarkedPost, BookmarkInput, BookmarkItem, BookmarkKey, BookmarkKeyRow, BookmarkKeysPage, BookmarkKind, BookmarkMergeItem, BookmarkMergeResult, BookmarkProtocol, BookmarkRequestOptions, BookmarksPage, FiledBookmark, RemovedBookmarkCollection, SavedBookmark, UnfiledBookmark, UnsavedBookmark, } from './resources/bookmarks.js';
|
|
227
232
|
export { AccountsResource } from './resources/accounts.js';
|
|
228
|
-
export { SIGNAL_BATCH_MAX, SignalsResource } from './resources/signals.js';
|
|
229
|
-
export type { SignalBatchResult, SignalEventInput, SignalEventType, SignalNotRecordedReason, SignalRecordResult, SignalSendOptions, } from './resources/signals.js';
|
|
233
|
+
export { SIGNAL_BATCH_MAX, SignalsResource, newClientEventId } from './resources/signals.js';
|
|
234
|
+
export type { ClearViewingHistoryResult, SignalBatchResult, SignalEventInput, SignalEventType, SignalExit, SignalMetadata, SignalNotRecordedReason, SignalRecordResult, SignalRejection, SignalSendOptions, SignalSurface, } from './resources/signals.js';
|
|
230
235
|
export { SEALED_ALLOWANCE_REMAINING_HEADER, SEALED_REFUSAL_HEADER, SealedResource, SealedTransportError, } from './resources/sealed.js';
|
|
231
236
|
export type { SealedAllowance, SealedAttestationInput, SealedConfig, SealedModel, SealedPlan, SealedPurpose, SealedQuotaDetails, SealedSendInput, SealedTarget, SealedTargetKind, SealedTransportErrorCode, } from './resources/sealed.js';
|
|
232
237
|
export type { SiblingAccount, SiblingAccountsResponse, SwitchAccountResult, } from './resources/accounts.js';
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ import { JobsResource } from './resources/jobs.js';
|
|
|
61
61
|
import { CuratorResource } from './resources/curator.js';
|
|
62
62
|
import { ReportsResource } from './resources/reports.js';
|
|
63
63
|
import { ModerationResource } from './resources/moderation.js';
|
|
64
|
+
import { TrustResource } from './resources/trust.js';
|
|
64
65
|
import { JudgmentsResource } from './resources/judgments.js';
|
|
65
66
|
import { LinkPreviewResource } from './resources/link-preview.js';
|
|
66
67
|
import { SearchResource } from './resources/search.js';
|
|
@@ -135,6 +136,8 @@ export class Recursiv {
|
|
|
135
136
|
boosts;
|
|
136
137
|
reports;
|
|
137
138
|
moderation;
|
|
139
|
+
/** The signed trust log: its public keys, and the member's own signed events (0.7.96). */
|
|
140
|
+
trust;
|
|
138
141
|
/** Jev's labels of a post: read them, dispute or appeal one, and (staff) review a dispute. */
|
|
139
142
|
judgments;
|
|
140
143
|
linkPreview;
|
|
@@ -211,6 +214,7 @@ export class Recursiv {
|
|
|
211
214
|
this.boosts = new BoostsResource(client);
|
|
212
215
|
this.reports = new ReportsResource(client);
|
|
213
216
|
this.moderation = new ModerationResource(client);
|
|
217
|
+
this.trust = new TrustResource(client);
|
|
214
218
|
this.judgments = new JudgmentsResource(client);
|
|
215
219
|
this.linkPreview = new LinkPreviewResource(client);
|
|
216
220
|
this.search = new SearchResource(client);
|
|
@@ -274,12 +278,13 @@ export { STORY_POOL_MAX_POSTS, STORY_POST_NETWORKS, STORY_POSTS_PAGE_MAX } from
|
|
|
274
278
|
export { ReportsResource } from './resources/reports.js';
|
|
275
279
|
export { JudgmentsResource, isJudgmentSharedWordsError } from './resources/judgments.js';
|
|
276
280
|
export { ModerationResource } from './resources/moderation.js';
|
|
281
|
+
export { TrustResource } from './resources/trust.js';
|
|
277
282
|
export { LinkPreviewResource } from './resources/link-preview.js';
|
|
278
283
|
export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
|
|
279
284
|
export { BOOKMARK_MERGE_MAX_ITEMS, BookmarkCollectionsResource, BookmarksResource } from './resources/bookmarks.js';
|
|
280
285
|
export { FEED_TOP_NETWORKS, FeedsResource } from './resources/feeds.js';
|
|
281
286
|
export { AccountsResource } from './resources/accounts.js';
|
|
282
|
-
export { SIGNAL_BATCH_MAX, SignalsResource } from './resources/signals.js';
|
|
287
|
+
export { SIGNAL_BATCH_MAX, SignalsResource, newClientEventId } from './resources/signals.js';
|
|
283
288
|
export { SEALED_ALLOWANCE_REMAINING_HEADER, SEALED_REFUSAL_HEADER, SealedResource, SealedTransportError, } from './resources/sealed.js';
|
|
284
289
|
export { OrganizationSettingsResource } from './resources/organization-settings.js';
|
|
285
290
|
export { ProjectSettingsResource } from './resources/project-settings.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
2
|
import type { SingleResponse } from '../types.js';
|
|
3
|
+
import type { TrustEnvelope } from './trust.js';
|
|
3
4
|
export type ModerationTargetType = 'post' | 'user' | 'message' | 'community' | 'agent' | 'source_actor' | 'source_post';
|
|
4
5
|
export type ModerationActionType = 'down_rank' | 'label' | 'remove' | 'suspend' | 'ban';
|
|
5
6
|
export type ModerationAppealStatus = 'none' | 'pending' | 'upheld' | 'overturned';
|
|
@@ -8,9 +9,30 @@ export interface ModerationPrinciple {
|
|
|
8
9
|
title: string;
|
|
9
10
|
summary: string;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Whether a decision is in the signed trust log, by the server's check (0.7.96): `verified` (signed, and its signature,
|
|
14
|
+
* hash and link to the event before it hold), `unverified` (signed, and one of those does not), `unsigned` (decided
|
|
15
|
+
* before the log was written, or not signed yet), `unavailable` (the log could not be read just now).
|
|
16
|
+
*/
|
|
17
|
+
export type ModerationTrustStatus = 'verified' | 'unverified' | 'unsigned' | 'unavailable';
|
|
18
|
+
/** One decision's signed envelopes: where they sit in the chain and what signed them, never the moderator, the subject or a hash. */
|
|
19
|
+
export interface ModerationTrustLog {
|
|
20
|
+
status: ModerationTrustStatus;
|
|
21
|
+
decision: TrustEnvelope | null;
|
|
22
|
+
reversal: TrustEnvelope | null;
|
|
23
|
+
}
|
|
24
|
+
/** The page as a whole: unverified if any action is, verified if any is signed, else unsigned. */
|
|
25
|
+
export interface ModerationTrustWindow {
|
|
26
|
+
status: ModerationTrustStatus;
|
|
27
|
+
verified: number;
|
|
28
|
+
unverified: number;
|
|
29
|
+
unsigned: number;
|
|
30
|
+
}
|
|
11
31
|
export interface PublicModerationAction {
|
|
12
32
|
id: string;
|
|
13
33
|
target_type: ModerationTargetType;
|
|
34
|
+
/** One sentence: what kind of thing was acted on and how, never which one. */
|
|
35
|
+
description?: string;
|
|
14
36
|
action: ModerationActionType;
|
|
15
37
|
policy: string | null;
|
|
16
38
|
actor_type: 'human' | 'ai' | 'system';
|
|
@@ -18,6 +40,8 @@ export interface PublicModerationAction {
|
|
|
18
40
|
reversed_at: string | null;
|
|
19
41
|
created_at: string;
|
|
20
42
|
appeal_status: ModerationAppealStatus;
|
|
43
|
+
/** Present once the operator turns on the signed log's reads (TRUST_LOG_PUBLIC_READS); absent before. */
|
|
44
|
+
trust_log?: ModerationTrustLog;
|
|
21
45
|
}
|
|
22
46
|
export interface PublicModerationLog {
|
|
23
47
|
principles: ModerationPrinciple[];
|
|
@@ -27,6 +51,8 @@ export interface PublicModerationLog {
|
|
|
27
51
|
offset: number;
|
|
28
52
|
has_more: boolean;
|
|
29
53
|
};
|
|
54
|
+
/** Present once the operator turns on the signed log's reads; absent before. */
|
|
55
|
+
trust_log?: ModerationTrustWindow;
|
|
30
56
|
}
|
|
31
57
|
export interface OwnModerationAppeal {
|
|
32
58
|
id: string;
|
|
@@ -6,24 +6,117 @@ import type { SingleResponse } from '../types.js';
|
|
|
6
6
|
* one, so send them in a request of their own if the API may be older.
|
|
7
7
|
*/
|
|
8
8
|
export type SignalEventType = 'view' | 'click' | 'dwell' | 'react_up' | 'react_down' | 'save' | 'hide' | 'share' | 'mute_source' | 'open_chat' | 'ask_agent' | 'more_like' | 'unhide';
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Where a signal happened: each one a public place. In the Minds app the server lists these (and Discover's older
|
|
11
|
+
* `discover_featured`, `discover_hot`, `discover_rediscover`) and refuses any other name, `other` included, with a 400
|
|
12
|
+
* (`private_surface`). Another app's well-formed name the server does not list is stored as `other`, so a new surface
|
|
13
|
+
* still lands there; add it here (and to the server's SIGNAL_SURFACES) to keep it apart. There is no `group`: a group
|
|
14
|
+
* can be an encrypted chat, which never sends a signal.
|
|
15
|
+
*/
|
|
16
|
+
export type SignalSurface = 'home_feed' | 'discovery' | 'discovery_story' | 'discovery_module' | 'discover_category' | 'discover_for_you' | 'postcard' | 'story' | 'swipe' | 'watch' | 'clip_strip' | 'discover_videos' | 'story_cluster' | 'headline_gallery' | 'post_clip' | 'search_videos' | 'profile' | 'search' | 'post' | 'other';
|
|
17
|
+
/** How a watch, or a card's time on screen, ended. */
|
|
18
|
+
export type SignalExit = 'swipe' | 'leave' | 'hide' | 'tap';
|
|
19
|
+
/**
|
|
20
|
+
* A signal's metadata: the only keys the server keeps (0.7.93, the ingest contract). A key not listed here is dropped.
|
|
21
|
+
* A listed key with a value of the wrong type or past its limit refuses that event (named in `rejections`, never
|
|
22
|
+
* stored); `null` or `''` reads as absent. Times are milliseconds.
|
|
23
|
+
*/
|
|
24
|
+
export interface SignalMetadata {
|
|
25
|
+
/**
|
|
26
|
+
* Where it happened (up to 64 characters). In the Minds app one of SignalSurface's names other than `other`;
|
|
27
|
+
* elsewhere an unlisted name is stored as `other`.
|
|
28
|
+
*/
|
|
29
|
+
surface?: SignalSurface | (string & {});
|
|
30
|
+
/** Which feed of the surface, e.g. `all`, `category`, `for-you:world` (up to 64). */
|
|
31
|
+
feed?: string;
|
|
32
|
+
/** The block of the feed, e.g. `top` (up to 32). */
|
|
33
|
+
block?: string;
|
|
34
|
+
/** The 0-based row in the feed (0 to 100,000). */
|
|
35
|
+
position?: number;
|
|
36
|
+
/** The place within a module that holds several (0 to 10,000). */
|
|
37
|
+
slot?: number;
|
|
38
|
+
/** A category feed's category (up to 64). */
|
|
39
|
+
category?: string;
|
|
40
|
+
/**
|
|
41
|
+
* An item with no Minds post id: an open-network post `<protocol>/<native id>` (native id up to 2,048 characters, no
|
|
42
|
+
* whitespace), a story lead `cluster:<64 hex>` or a World Today story `today:<uuid>`. Stored in an indexed column.
|
|
43
|
+
*/
|
|
44
|
+
ref?: string;
|
|
45
|
+
/** How long the clip played (0 to one hour). Held to `duration_ms` x (`loops` + 1) and to the wall clock. */
|
|
46
|
+
watch_ms?: number;
|
|
47
|
+
/** The clip's length (0 to six hours). */
|
|
48
|
+
duration_ms?: number;
|
|
49
|
+
completed?: boolean;
|
|
50
|
+
/** Times the clip looped (0 to 1,000). */
|
|
51
|
+
loops?: number;
|
|
52
|
+
sound_on?: boolean;
|
|
53
|
+
/** How it ended. A watch under a second that ended in a `swipe` is stored as a skip. */
|
|
54
|
+
exit?: SignalExit;
|
|
55
|
+
/** Cards on screen at once in a grid (1 to 200). Readers divide a grid card's dwell by it; it never divides watch time. */
|
|
56
|
+
visible_count?: number;
|
|
57
|
+
autoplay?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The client's id for this event (a letter or digit, then up to 63 of `A-Za-z0-9_.:-`): an event sent again with the
|
|
60
|
+
* same id is stored once. `batch` and `record` add one to an event that has none.
|
|
61
|
+
*/
|
|
62
|
+
client_event_id?: string;
|
|
63
|
+
/** The event's place in the client's session (0 to 2^31 - 1). */
|
|
64
|
+
session_seq?: number;
|
|
65
|
+
/** How the action was taken, e.g. `less_like_this`, `module_hide` (up to 64). */
|
|
66
|
+
via?: string;
|
|
67
|
+
/** What the action came from, e.g. `postcard_bookmark` (up to 64). */
|
|
68
|
+
source?: string;
|
|
69
|
+
/** How the item was shown, e.g. `video` (up to 32). */
|
|
70
|
+
presentation?: string;
|
|
71
|
+
}
|
|
72
|
+
/** One engagement signal. An open-network post has no Minds id: name it by `metadata.ref` instead. */
|
|
10
73
|
export interface SignalEventInput {
|
|
11
74
|
event_type: SignalEventType;
|
|
12
75
|
/** The Minds post's id (a UUID). */
|
|
13
76
|
post_id?: string;
|
|
14
|
-
/** Milliseconds on screen, for `dwell` (0 to one hour). */
|
|
77
|
+
/** Milliseconds on screen, for `dwell` (0 to one hour; a card's counts up to five minutes). */
|
|
15
78
|
dwell_ms?: number;
|
|
16
|
-
metadata?:
|
|
79
|
+
metadata?: SignalMetadata;
|
|
17
80
|
}
|
|
18
81
|
/**
|
|
19
82
|
* Why nothing was stored, on a 200: `no_project_scope` (the key is not bound to an app, so there is no app database
|
|
20
|
-
* for the signal)
|
|
83
|
+
* for the signal), `project_db_unavailable` (the app database did not take it; signals are best-effort),
|
|
84
|
+
* `viewing_off` (the member turned off "Use what I watch and read to personalize", so a dwell or open is not kept),
|
|
85
|
+
* `rate_limited` (past the member's events a minute) or `duplicate` (`record` only: an event with this
|
|
86
|
+
* `client_event_id` is already stored).
|
|
21
87
|
*/
|
|
22
|
-
export type SignalNotRecordedReason = 'no_project_scope' | 'project_db_unavailable';
|
|
88
|
+
export type SignalNotRecordedReason = 'no_project_scope' | 'project_db_unavailable' | 'viewing_off' | 'rate_limited' | 'duplicate';
|
|
89
|
+
/** Why the server refused one event of a batch: a metadata value of the wrong type or past its limit. */
|
|
90
|
+
export interface SignalRejection {
|
|
91
|
+
/** The event's index in the batch. */
|
|
92
|
+
index: number;
|
|
93
|
+
reason: 'invalid_metadata' | 'invalid_ref' | 'invalid_client_event_id';
|
|
94
|
+
/** The metadata key. */
|
|
95
|
+
key: string;
|
|
96
|
+
}
|
|
23
97
|
export interface SignalBatchResult {
|
|
24
|
-
/** Events stored. */
|
|
98
|
+
/** Events stored by this call. */
|
|
25
99
|
recorded: number;
|
|
100
|
+
/**
|
|
101
|
+
* Events the server did not keep because the member's viewing personalization is off: dwell and opens. Views and
|
|
102
|
+
* explicit choices are always kept. Absent when none were left out.
|
|
103
|
+
*/
|
|
104
|
+
withheld?: number;
|
|
26
105
|
reason?: SignalNotRecordedReason;
|
|
106
|
+
/** Events already stored (the same `client_event_id`, in this batch or an earlier one): stored once. */
|
|
107
|
+
duplicates?: number;
|
|
108
|
+
/** Events past the member's rate (600 a minute by default): not stored. */
|
|
109
|
+
limited?: number;
|
|
110
|
+
/** Events refused by the contract: not stored. The rest of the batch is. */
|
|
111
|
+
rejected?: number;
|
|
112
|
+
/** The first ten refused events, and why. */
|
|
113
|
+
rejections?: SignalRejection[];
|
|
114
|
+
}
|
|
115
|
+
/** What "Clear my viewing history" did: the member's views, dwell, Watch time and opens in this app are gone. */
|
|
116
|
+
export interface ClearViewingHistoryResult {
|
|
117
|
+
cleared: true;
|
|
118
|
+
/** Rows deleted. */
|
|
119
|
+
deleted: number;
|
|
27
120
|
}
|
|
28
121
|
export interface SignalRecordResult {
|
|
29
122
|
recorded: boolean;
|
|
@@ -44,15 +137,45 @@ export declare const SIGNAL_BATCH_MAX = 100;
|
|
|
44
137
|
* Engagement signals (views, dwell, hides, "More like this") for the member's own ranking, under the member's
|
|
45
138
|
* app-bound key (`agents:write`). They land in the app's database and feed For You. Best-effort: a signal the server
|
|
46
139
|
* cannot store still answers 200 with `recorded` 0 or false and a `reason`.
|
|
140
|
+
*
|
|
141
|
+
* Only from a public place, never a private message: a `metadata` key or placement (`surface`, `feed`, `source`,
|
|
142
|
+
* `context`, `route`...) that names a chat, DM, inbox, encrypted or Sealed conversation, by any spelling (`chatList`,
|
|
143
|
+
* `dmThread`, `/messages/1`), is refused with a 400 (`private_surface`), and nothing in that request is stored. An app
|
|
144
|
+
* names its own public places (`{ source: 'discover' }`). In the Minds app the placements (`surface`, `feed`, `source`,
|
|
145
|
+
* `via`, `layoutSource`) must be ones the server lists (its feeds, Discover, Watch, stories, the pager and post cards),
|
|
146
|
+
* and any other key that names a place (`context`) is refused there too.
|
|
147
|
+
*
|
|
148
|
+
* That check reads the metadata as sent. The server then keeps only SignalMetadata's keys, each within its limit; a
|
|
149
|
+
* batch names any event it refused and stores the rest. `record` answers 400 (`invalid_signal`) for an event it refuses.
|
|
47
150
|
*/
|
|
48
151
|
export declare class SignalsResource {
|
|
49
152
|
private client;
|
|
153
|
+
/**
|
|
154
|
+
* The id each event object was sent with. An event sent again (a retry of the same request, or the app sending the
|
|
155
|
+
* same object after a failed flush) carries the same id, so the server stores it once. Weak: an event the app has let
|
|
156
|
+
* go is not held here.
|
|
157
|
+
*/
|
|
158
|
+
private readonly eventIds;
|
|
50
159
|
constructor(client: HttpClient);
|
|
51
160
|
/**
|
|
52
161
|
* Store up to SIGNAL_BATCH_MAX events in one request (POST /signals/batch). The server reads a post's newest hide or
|
|
53
162
|
* unhide, so send a batch only after the one before it has landed when order matters. An empty array stores nothing.
|
|
163
|
+
* Each event without a `metadata.client_event_id` is sent with one (the same one each time that object is sent).
|
|
54
164
|
*/
|
|
55
165
|
batch(events: SignalEventInput[], options?: SignalSendOptions): Promise<SingleResponse<SignalBatchResult>>;
|
|
56
166
|
/** Store one event (POST /signals/post). Prefer `batch`; this is for an API that predates it (404 there). */
|
|
57
167
|
record(event: SignalEventInput, options?: SignalSendOptions): Promise<SingleResponse<SignalRecordResult>>;
|
|
168
|
+
/**
|
|
169
|
+
* "Clear my viewing history" (DELETE /signals/history): the member's views, dwell, Watch time and opens in this app
|
|
170
|
+
* are deleted, and what was built from them is rebuilt. Their explicit choices (hides, "More like this", saves,
|
|
171
|
+
* shares, votes) stay. A delete that could not be done is an error (503), never a success.
|
|
172
|
+
*/
|
|
173
|
+
clearViewingHistory(): Promise<SingleResponse<ClearViewingHistoryResult>>;
|
|
174
|
+
/** The event as sent: the caller's object is never changed. */
|
|
175
|
+
private withEventId;
|
|
58
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* A fresh event id the server's grammar takes: a random UUID where the platform has one, otherwise the time and about
|
|
179
|
+
* 104 random bits (React Native's Hermes has no crypto.randomUUID). Unique per member is all it needs to be.
|
|
180
|
+
*/
|
|
181
|
+
export declare function newClientEventId(): string;
|
|
@@ -4,23 +4,79 @@ export const SIGNAL_BATCH_MAX = 100;
|
|
|
4
4
|
* Engagement signals (views, dwell, hides, "More like this") for the member's own ranking, under the member's
|
|
5
5
|
* app-bound key (`agents:write`). They land in the app's database and feed For You. Best-effort: a signal the server
|
|
6
6
|
* cannot store still answers 200 with `recorded` 0 or false and a `reason`.
|
|
7
|
+
*
|
|
8
|
+
* Only from a public place, never a private message: a `metadata` key or placement (`surface`, `feed`, `source`,
|
|
9
|
+
* `context`, `route`...) that names a chat, DM, inbox, encrypted or Sealed conversation, by any spelling (`chatList`,
|
|
10
|
+
* `dmThread`, `/messages/1`), is refused with a 400 (`private_surface`), and nothing in that request is stored. An app
|
|
11
|
+
* names its own public places (`{ source: 'discover' }`). In the Minds app the placements (`surface`, `feed`, `source`,
|
|
12
|
+
* `via`, `layoutSource`) must be ones the server lists (its feeds, Discover, Watch, stories, the pager and post cards),
|
|
13
|
+
* and any other key that names a place (`context`) is refused there too.
|
|
14
|
+
*
|
|
15
|
+
* That check reads the metadata as sent. The server then keeps only SignalMetadata's keys, each within its limit; a
|
|
16
|
+
* batch names any event it refused and stores the rest. `record` answers 400 (`invalid_signal`) for an event it refuses.
|
|
7
17
|
*/
|
|
8
18
|
export class SignalsResource {
|
|
9
19
|
client;
|
|
20
|
+
/**
|
|
21
|
+
* The id each event object was sent with. An event sent again (a retry of the same request, or the app sending the
|
|
22
|
+
* same object after a failed flush) carries the same id, so the server stores it once. Weak: an event the app has let
|
|
23
|
+
* go is not held here.
|
|
24
|
+
*/
|
|
25
|
+
eventIds = new WeakMap();
|
|
10
26
|
constructor(client) {
|
|
11
27
|
this.client = client;
|
|
12
28
|
}
|
|
13
29
|
/**
|
|
14
30
|
* Store up to SIGNAL_BATCH_MAX events in one request (POST /signals/batch). The server reads a post's newest hide or
|
|
15
31
|
* unhide, so send a batch only after the one before it has landed when order matters. An empty array stores nothing.
|
|
32
|
+
* Each event without a `metadata.client_event_id` is sent with one (the same one each time that object is sent).
|
|
16
33
|
*/
|
|
17
34
|
batch(events, options) {
|
|
18
|
-
return this.client.post('/signals/batch', { events }, sendOptions(options));
|
|
35
|
+
return this.client.post('/signals/batch', { events: events.map((event) => this.withEventId(event)) }, sendOptions(options));
|
|
19
36
|
}
|
|
20
37
|
/** Store one event (POST /signals/post). Prefer `batch`; this is for an API that predates it (404 there). */
|
|
21
38
|
record(event, options) {
|
|
22
|
-
return this.client.post('/signals/post', event, sendOptions(options));
|
|
39
|
+
return this.client.post('/signals/post', this.withEventId(event), sendOptions(options));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* "Clear my viewing history" (DELETE /signals/history): the member's views, dwell, Watch time and opens in this app
|
|
43
|
+
* are deleted, and what was built from them is rebuilt. Their explicit choices (hides, "More like this", saves,
|
|
44
|
+
* shares, votes) stay. A delete that could not be done is an error (503), never a success.
|
|
45
|
+
*/
|
|
46
|
+
clearViewingHistory() {
|
|
47
|
+
return this.client.delete('/signals/history');
|
|
48
|
+
}
|
|
49
|
+
/** The event as sent: the caller's object is never changed. */
|
|
50
|
+
withEventId(event) {
|
|
51
|
+
if (!event || typeof event !== 'object')
|
|
52
|
+
return event;
|
|
53
|
+
const given = event.metadata?.client_event_id;
|
|
54
|
+
if (typeof given === 'string' && given)
|
|
55
|
+
return event;
|
|
56
|
+
let id = this.eventIds.get(event);
|
|
57
|
+
if (!id) {
|
|
58
|
+
id = newClientEventId();
|
|
59
|
+
this.eventIds.set(event, id);
|
|
60
|
+
}
|
|
61
|
+
return { ...event, metadata: { ...event.metadata, client_event_id: id } };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A fresh event id the server's grammar takes: a random UUID where the platform has one, otherwise the time and about
|
|
66
|
+
* 104 random bits (React Native's Hermes has no crypto.randomUUID). Unique per member is all it needs to be.
|
|
67
|
+
*/
|
|
68
|
+
export function newClientEventId() {
|
|
69
|
+
const cryptoApi = globalThis.crypto;
|
|
70
|
+
if (typeof cryptoApi?.randomUUID === 'function') {
|
|
71
|
+
try {
|
|
72
|
+
return cryptoApi.randomUUID();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// A non-secure context can refuse it: fall through.
|
|
76
|
+
}
|
|
23
77
|
}
|
|
78
|
+
const random = () => Math.floor(Math.random() * 2 ** 52).toString(36).padStart(11, '0');
|
|
79
|
+
return `${Date.now().toString(36)}-${random()}${random()}`;
|
|
24
80
|
}
|
|
25
81
|
function sendOptions(options) {
|
|
26
82
|
if (!options)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { HttpClient } from '../client.js';
|
|
2
|
+
import type { SingleResponse } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Where one event sits in the signed trust log, which key signed it, and whether the server's check of it holds
|
|
5
|
+
* (0.7.96). The public moderation log carries one per signed decision and reversal (`PublicModerationAction.trust_log`):
|
|
6
|
+
* never the moderator, the subject or the payload, and never a hash or a signature. The log's v1 encoding signs the
|
|
7
|
+
* moderator and the subject unsalted, so a published hash or signature would let anyone test guesses of who acted on
|
|
8
|
+
* what; `verified` is the server's verdict, not something a client re-checks.
|
|
9
|
+
*/
|
|
10
|
+
export interface TrustEnvelope {
|
|
11
|
+
/** The chain the event is on: the network's id. */
|
|
12
|
+
chain: string;
|
|
13
|
+
seq: number;
|
|
14
|
+
key_id: string;
|
|
15
|
+
algorithm: 'ed25519';
|
|
16
|
+
occurred_at: string;
|
|
17
|
+
/** The server's own check: the signature, the hash, and the link to the event before it. */
|
|
18
|
+
verified: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** One of the caller's own events: what they did, where it sits in the log, and the server's check of it. */
|
|
21
|
+
export interface OwnTrustEvent extends TrustEnvelope {
|
|
22
|
+
id: number;
|
|
23
|
+
network_id: string;
|
|
24
|
+
project_id: string | null;
|
|
25
|
+
/** e.g. `follow`, `unfollow`, `block`, `mute`, `hide`, `appeal`, `moderation_action` (a moderator's own decision). */
|
|
26
|
+
kind: string;
|
|
27
|
+
actor: {
|
|
28
|
+
kind: 'user' | 'agent' | 'system';
|
|
29
|
+
user_id: string | null;
|
|
30
|
+
principal_id: string | null;
|
|
31
|
+
};
|
|
32
|
+
subject: {
|
|
33
|
+
kind: string;
|
|
34
|
+
id: string | null;
|
|
35
|
+
key: string | null;
|
|
36
|
+
principal_id: string | null;
|
|
37
|
+
};
|
|
38
|
+
payload: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/** A public key that signed the log. A retired or compromised key still verifies what it signed before that instant. */
|
|
41
|
+
export interface TrustPublicKey {
|
|
42
|
+
key_id: string;
|
|
43
|
+
algorithm: 'ed25519';
|
|
44
|
+
/** base64 SPKI DER. */
|
|
45
|
+
public_key: string;
|
|
46
|
+
activated_at: string | null;
|
|
47
|
+
retired_at: string | null;
|
|
48
|
+
compromised_at: string | null;
|
|
49
|
+
}
|
|
50
|
+
export interface OwnTrustEvents {
|
|
51
|
+
events: OwnTrustEvent[];
|
|
52
|
+
/** The keys that signed this page's events. */
|
|
53
|
+
keys: TrustPublicKey[];
|
|
54
|
+
pagination: {
|
|
55
|
+
limit: number;
|
|
56
|
+
before: number | null;
|
|
57
|
+
has_more: boolean;
|
|
58
|
+
next_before: number | null;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The signed trust log (0.7.96). Both reads answer 404 (`NotFoundError`) until the operator turns on
|
|
63
|
+
* TRUST_LOG_PUBLIC_READS. Signed moderation decisions are on the public moderation log itself
|
|
64
|
+
* (`r.moderation.publicLog`, `trust_log` on each action).
|
|
65
|
+
*/
|
|
66
|
+
export declare class TrustResource {
|
|
67
|
+
private client;
|
|
68
|
+
constructor(client: HttpClient);
|
|
69
|
+
/**
|
|
70
|
+
* Keyless: every public key that ever signed the log, retired and compromised ones included. Public material: pin
|
|
71
|
+
* them to see a rotation, or check a full row an auditor holds.
|
|
72
|
+
*/
|
|
73
|
+
keys(): Promise<SingleResponse<{
|
|
74
|
+
keys: TrustPublicKey[];
|
|
75
|
+
}>>;
|
|
76
|
+
/**
|
|
77
|
+
* The caller's own events, newest first: the follows, blocks, mutes, hides and appeals they made (a moderator's own
|
|
78
|
+
* decisions too), each with where it sits in the log and the server's check. Never a hash or a signature. Page with
|
|
79
|
+
* `before: pagination.next_before`. Needs `users:read`.
|
|
80
|
+
*/
|
|
81
|
+
mine(params?: {
|
|
82
|
+
before?: number;
|
|
83
|
+
limit?: number;
|
|
84
|
+
}): Promise<SingleResponse<OwnTrustEvents>>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed trust log (0.7.96). Both reads answer 404 (`NotFoundError`) until the operator turns on
|
|
3
|
+
* TRUST_LOG_PUBLIC_READS. Signed moderation decisions are on the public moderation log itself
|
|
4
|
+
* (`r.moderation.publicLog`, `trust_log` on each action).
|
|
5
|
+
*/
|
|
6
|
+
export class TrustResource {
|
|
7
|
+
client;
|
|
8
|
+
constructor(client) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Keyless: every public key that ever signed the log, retired and compromised ones included. Public material: pin
|
|
13
|
+
* them to see a rotation, or check a full row an auditor holds.
|
|
14
|
+
*/
|
|
15
|
+
keys() {
|
|
16
|
+
return this.client.get('/transparency/trust/keys');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The caller's own events, newest first: the follows, blocks, mutes, hides and appeals they made (a moderator's own
|
|
20
|
+
* decisions too), each with where it sits in the log and the server's check. Never a hash or a signature. Page with
|
|
21
|
+
* `before: pagination.next_before`. Needs `users:read`.
|
|
22
|
+
*/
|
|
23
|
+
mine(params = {}) {
|
|
24
|
+
return this.client.get('/trust/me', { before: params.before, limit: params.limit });
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1651,6 +1651,12 @@ export interface ContentPreferences {
|
|
|
1651
1651
|
* that predate it: treat absent as off.
|
|
1652
1652
|
*/
|
|
1653
1653
|
sealed_adult?: boolean;
|
|
1654
|
+
/**
|
|
1655
|
+
* "Use what I watch and read to personalize": on by default. Off, the server stores only views (so a post the member
|
|
1656
|
+
* saw is not served again) and their explicit choices, and no ranking reads what they watched or read. Absent on
|
|
1657
|
+
* servers that predate it: treat absent as on.
|
|
1658
|
+
*/
|
|
1659
|
+
personalize_from_viewing?: boolean;
|
|
1654
1660
|
}
|
|
1655
1661
|
export interface UpdateNotificationChannelsInput {
|
|
1656
1662
|
/** Master switch: sets in-app on every kind. */
|
|
@@ -1698,6 +1704,8 @@ export interface UpdateContentPreferencesInput {
|
|
|
1698
1704
|
* accepted.
|
|
1699
1705
|
*/
|
|
1700
1706
|
sealed_adult?: boolean;
|
|
1707
|
+
/** "Use what I watch and read to personalize". `false` is the member's opt-out; `true` turns it back on. */
|
|
1708
|
+
personalize_from_viewing?: boolean;
|
|
1701
1709
|
}
|
|
1702
1710
|
export interface Session {
|
|
1703
1711
|
id: string;
|
package/llm.md
CHANGED
|
@@ -826,7 +826,7 @@ Methods: `list`, `get`, `create`, `delete`
|
|
|
826
826
|
## Platform Resources
|
|
827
827
|
|
|
828
828
|
- `r.settings` — User preferences, sessions, password changes, account deletion. `revokeOtherSessions({ keep_session_token? })` signs out every other device and keeps this key (and the proven session); `revokeAllSessions()` also revokes this key. Accounts with no password confirm `requestEmailChange({ new_email, email_code })` with a code from `requestConfirmationCode('email_change')`. With a project-bound key, `getPreferences()` includes app-scoped `content`; persist changes with `updateContent({ show_nsfw?, autoplay_video?, ai_enabled?, default_feed?, sealed_adult? })`; `content.default_feed_chosen` is true once the member set `default_feed` themselves (absent on older APIs). `privacy.age_is_adult` is true only when the member answered the age question 18 or older (false for 13-17 and for no answer; only their own preferences carry it). `content.sealed_adult` is the Sealed adult-content setting: it reads true only while it is on and the member is a known adult, and `updateContent({ sealed_adult: true })` from anyone else throws `AuthorizationError` with code `sealed_adult_needs_adult` and saves nothing in that request; `sealed_adult: false` is always accepted. Both are absent on older APIs (treat as false). Notifications (0.7.72): `getPreferences()` returns `notifications.channels`, each kind (`post_mention`, `post_reply`, `post_repost`, `post_reaction`, `follow`, `message`, and the operator kinds) with `{ inApp, push, email }`; `updateNotifications({ channels: { post_reaction: { push: false } } })` changes that kind only, while `in_app`/`push`/`email` stay master switches over every kind. `disableAccount({ password })` (0.7.71) deactivates the account (hidden, nothing deleted; signing back in restores it), asked once; an API that does not serve deactivation answers 404 `NotFoundError`, so keep the control hidden until yours does.
|
|
829
|
-
- `r.signals` (0.7.71) — The member's engagement signals for their own ranking (For You), under their app-bound key (`agents:write`): `batch(events, { keepalive?, retry? })` sends up to `SIGNAL_BATCH_MAX` (100) `{ event_type, post_id?, dwell_ms?, metadata? }` to POST /signals/batch; `record(event)` sends one to POST /signals/post (for an API before the batch route, which answers that one 404). `event_type`: `view`, `click`, `dwell`, `react_up`, `react_down`, `save`, `hide`, `share`, `mute_source`, `open_chat`, `ask_agent`, `more_like`, `unhide`. Best-effort: a signal the server cannot store answers 200 with `recorded` 0 (or false) and a `reason` (`no_project_scope`, `project_db_unavailable`). `keepalive: true` is for a flush as the page is hidden or unloads: the request is on the wire before the call returns and is asked once.
|
|
829
|
+
- `r.signals` (0.7.71) — The member's engagement signals for their own ranking (For You), under their app-bound key (`agents:write`): `batch(events, { keepalive?, retry? })` sends up to `SIGNAL_BATCH_MAX` (100) `{ event_type, post_id?, dwell_ms?, metadata? }` to POST /signals/batch; `record(event)` sends one to POST /signals/post (for an API before the batch route, which answers that one 404). `event_type`: `view`, `click`, `dwell`, `react_up`, `react_down`, `save`, `hide`, `share`, `mute_source`, `open_chat`, `ask_agent`, `more_like`, `unhide`. Best-effort: a signal the server cannot store answers 200 with `recorded` 0 (or false) and a `reason` (`no_project_scope`, `project_db_unavailable`, `viewing_off`, `rate_limited`, or for `record` `duplicate`). `keepalive: true` is for a flush as the page is hidden or unloads: the request is on the wire before the call returns and is asked once. Only from a public place, never a private message: a `metadata` key or placement (`surface`, `feed`, `source`, `context`, `route`...) naming a chat, DM, inbox, encrypted or Sealed conversation by any spelling (`chatList`, `dmThread`, `/messages/1`) answers 400 (`private_surface`) and nothing in that request is stored; this is read on the metadata as sent, before any key is dropped. An app names its own public places (`{ source: 'discover' }`). In the Minds app a `surface` must be one of `SignalSurface`'s names other than `other` (each a public place the server lists), a `feed`, `source`, `via` or `layoutSource` one the server lists (its feeds, Discover, Watch, stories, the pager and post cards), and any other key naming a place (`context`) is refused there too. With the member's "Use what I watch and read to personalize" off (`settings.updateContent({ personalize_from_viewing: false })`, on by default), only views and explicit choices are stored and `withheld` counts the dwell and opens left out. `clearViewingHistory()` (DELETE /signals/history) is "Clear my viewing history": the member's views, dwell, Watch time and opens in this app are deleted; their hides, "More like this", saves, shares and votes stay. Ingest contract (0.7.93): `metadata` is `SignalMetadata`; the server keeps only its keys (`surface`, `feed`, `block`, `position`, `slot`, `category`, `ref`, `watch_ms`, `duration_ms`, `completed`, `loops`, `sound_on`, `exit` (`swipe`/`leave`/`hide`/`tap`), `visible_count`, `autoplay`, `client_event_id`, `session_seq`, `via`, `source`, `presentation`) and drops the rest; a listed key of the wrong type or past its limit refuses that event (batch: `rejected` + the first ten `rejections` `{ index, reason, key }`, the rest stored; record: 400 `invalid_signal`). `batch`/`record` add a `client_event_id` to each event without one (the same id each time that object is sent), so a retry is stored once (`duplicates`). An open-network item is named by `metadata.ref` (`<protocol>/<native id>`, `cluster:<id>`, `today:<uuid>`). Watch time is held to `duration_ms` x (`loops` + 1) and to the wall clock, a card's dwell to 5 min; a watch under 1 s ending in `exit: 'swipe'` is stored as a skip. Past 600 events a minute per member the rest are `limited` (record: `reason: 'rate_limited'`).
|
|
830
830
|
|
|
831
831
|
```typescript
|
|
832
832
|
await r.signals.batch([{ event_type: 'view', post_id: post.id }, { event_type: 'dwell', post_id: post.id, dwell_ms: 4200 }])
|
|
@@ -866,6 +866,7 @@ await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorati
|
|
|
866
866
|
```
|
|
867
867
|
- `r.admin` — Admin operations (user management, stats, network settings); content reports: `listReports({ status? })`, `resolveReport(id)`, `dismissReport(id)` (project-bound keys see only their app's reports)
|
|
868
868
|
- `r.moderation` — Moderation actions. Staff (admin key), 0.7.74: `const { data: actions } = await r.moderation.list({ target_type: 'user', target_id: userId })` reads one target's recorded actions (`ModerationAction`: `id`, `target_type` incl. `source_item`, `action`, `label`, `reason`, `actor_id`, `actor_type`, `source_report_id`, `expires_at`, `reversed_at`, `created_at`), newest first, at most 100, inside the caller's network and a project-bound key's app; `list()` with no target reads the network's latest. Unban = reverse each ban still in force: `for (const a of actions.filter(a => a.action === 'ban' && !a.reversed_at)) await r.moderation.reverse(a.id)`; the target's state is derived again from what is still in force. `reverse` is sent once, never retried: 404 `action_not_found` (`NotFoundError`) means already reversed or not in the caller's scope; 409 `use_judgment_review` means a reviewed label override (change it with `r.judgments.review`). An older SDK has neither method. Also `apply`, `publicLog`, `mine`, `appeal`, `resolveAppeal`, and agent grants `listAgentGrants`, `grantAgent(agentId, 'shadow' | 'act')`, `revokeAgent`.
|
|
869
|
+
- `r.trust` — The signed trust log (0.7.96; both reads answer 404, `NotFoundError`, until the operator turns on `TRUST_LOG_PUBLIC_READS`). `r.trust.keys()` (keyless) lists every public key that ever signed it (`key_id`, `algorithm` `ed25519`, `public_key` base64 SPKI DER, `activated_at`, `retired_at`, `compromised_at`). `r.trust.mine({ before?, limit? })` (`users:read`) reads the caller's own signed events, newest first (their follows, blocks, mutes, hides, appeals, and a moderator's own decisions: `kind`, `actor`, `subject`, `payload`, `chain`, `seq`, `key_id`, `occurred_at`, and the server's check `verified`) with the `keys` that signed the page; page with `before: pagination.next_before`. On the public log, `r.moderation.publicLog(networkId)` then carries `trust_log` on each action (`status`: `verified` | `unverified` | `unsigned` | `unavailable`, and the signed `decision` and `reversal`: `chain`, `seq`, `key_id`, `algorithm`, `occurred_at`, `verified`) and on the page (`status` and the count of each). No read carries a hash or a signature (the v1 encoding signs the moderator and the subject unsalted, so one would let anyone test guesses of who acted on what): `verified` is the server's verdict.
|
|
869
870
|
- `r.judgments` (0.7.71) — Jev's labels of one post. `get({ kind: 'post' | 'network_post', id })`: switch on `data.view` — `'full'` for the post's author and staff (`judgments` with answers, `safety` verdict, `viral.withheld`; staff also `reviewer_ids`, `review_note`), `'summary'` for other members (`category`, `politics` band in words with its `judgment_id`; never a probability or cell). `data.status === 'unavailable'` (returned, not thrown): hide label actions. `getPublic(projectId, { kind, id })` on an anonymous client: `view: 'category'` only. `dispute(judgmentId, { question_key: 'placement', proposed: 'cm' | … | 'not_political', note? })` (safety: `question_key: 'safety'`, author only): the author's dispute is an appeal; a repeat answers `existing: true`. Staff: `review(judgmentId, { outcome: 'uphold' } | { outcome: 'override', proposed, note?, apply_to_shared? })`, sent once (never retried): a compass override answers 202 `{ status: 'awaiting_reviewer', agreeing, needed }` until a second reviewer (or an agreeing Jev re-judge) proposes the same; a safety override to `'none'` that would free other posts throws 409 `shared_words` (`isJudgmentSharedWordsError(e)`, `e.details.shared_posts`) until resent with `apply_to_shared: true`. Budgets (60 reads/hour, 25 of one author's posts, 20 disputes/day) throw `RateLimitError` with `scope: 'action'` at once. Example: `const { data } = await r.judgments.get({ kind: 'post', id }); if (data.status === 'ok' && data.view === 'summary' && data.politics) await r.judgments.dispute(data.politics.judgment_id, { question_key: 'placement', proposed: 'not_political' })`
|
|
870
871
|
- `r.inviteCodes` / `r.inviteCodesAdmin` — Invite code management
|
|
871
872
|
- `r.organizationSettings` / `r.organizationSecurity` — Org configuration
|