@rightkit/social-ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +40 -2
  5. package/dist/appName.d.ts +2 -0
  6. package/dist/appName.js +8 -0
  7. package/dist/components/ScopeSelect.d.ts +7 -0
  8. package/dist/components/ScopeSelect.js +6 -0
  9. package/dist/components/Shared.d.ts +34 -0
  10. package/dist/components/Shared.js +55 -0
  11. package/dist/data/derive.d.ts +21 -0
  12. package/dist/data/derive.js +47 -0
  13. package/dist/data/draftRules.d.ts +10 -0
  14. package/dist/data/draftRules.js +20 -0
  15. package/dist/data/engine/engineDataSource.d.ts +194 -0
  16. package/dist/data/engine/engineDataSource.js +836 -0
  17. package/dist/data/engine/errors.d.ts +13 -0
  18. package/dist/data/engine/errors.js +21 -0
  19. package/dist/data/engine/mapping.d.ts +172 -0
  20. package/dist/data/engine/mapping.js +1487 -0
  21. package/dist/data/engine/protocol.d.ts +910 -0
  22. package/dist/data/engine/protocol.js +67 -0
  23. package/dist/data/index.d.ts +18 -0
  24. package/dist/data/index.js +23 -0
  25. package/dist/data/mock/devSource.d.ts +7 -0
  26. package/dist/data/mock/devSource.js +10 -0
  27. package/dist/data/mock/fixtures-analytics.d.ts +7 -0
  28. package/dist/data/mock/fixtures-analytics.js +123 -0
  29. package/dist/data/mock/fixtures-briefs.d.ts +4 -0
  30. package/dist/data/mock/fixtures-briefs.js +230 -0
  31. package/dist/data/mock/fixtures-content.d.ts +4 -0
  32. package/dist/data/mock/fixtures-content.js +89 -0
  33. package/dist/data/mock/fixtures-digests.d.ts +3 -0
  34. package/dist/data/mock/fixtures-digests.js +40 -0
  35. package/dist/data/mock/fixtures-drafts.d.ts +2 -0
  36. package/dist/data/mock/fixtures-drafts.js +74 -0
  37. package/dist/data/mock/fixtures-gate.d.ts +3 -0
  38. package/dist/data/mock/fixtures-gate.js +49 -0
  39. package/dist/data/mock/fixtures-hooks.d.ts +2 -0
  40. package/dist/data/mock/fixtures-hooks.js +103 -0
  41. package/dist/data/mock/fixtures-inbox.d.ts +11 -0
  42. package/dist/data/mock/fixtures-inbox.js +213 -0
  43. package/dist/data/mock/fixtures-links.d.ts +5 -0
  44. package/dist/data/mock/fixtures-links.js +89 -0
  45. package/dist/data/mock/fixtures-memory.d.ts +3 -0
  46. package/dist/data/mock/fixtures-memory.js +148 -0
  47. package/dist/data/mock/fixtures-ops.d.ts +15 -0
  48. package/dist/data/mock/fixtures-ops.js +403 -0
  49. package/dist/data/mock/fixtures-optimise.d.ts +18 -0
  50. package/dist/data/mock/fixtures-optimise.js +221 -0
  51. package/dist/data/mock/fixtures-pacing.d.ts +5 -0
  52. package/dist/data/mock/fixtures-pacing.js +285 -0
  53. package/dist/data/mock/fixtures-publish.d.ts +2 -0
  54. package/dist/data/mock/fixtures-publish.js +129 -0
  55. package/dist/data/mock/fixtures-store.d.ts +10 -0
  56. package/dist/data/mock/fixtures-store.js +13 -0
  57. package/dist/data/mock/fixtures-variants.d.ts +5 -0
  58. package/dist/data/mock/fixtures-variants.js +138 -0
  59. package/dist/data/mock/fixtures.d.ts +6 -0
  60. package/dist/data/mock/fixtures.js +420 -0
  61. package/dist/data/mock/mockDataSource.d.ts +98 -0
  62. package/dist/data/mock/mockDataSource.js +903 -0
  63. package/dist/data/money.d.ts +14 -0
  64. package/dist/data/money.js +36 -0
  65. package/dist/data/platforms.d.ts +12 -0
  66. package/dist/data/platforms.js +26 -0
  67. package/dist/data/source.d.ts +196 -0
  68. package/dist/data/source.js +1 -0
  69. package/dist/data/time.d.ts +23 -0
  70. package/dist/data/time.js +86 -0
  71. package/dist/data/types.d.ts +1092 -0
  72. package/dist/data/types.js +3 -0
  73. package/dist/env.d.ts +59 -0
  74. package/dist/hooks/useAsync.d.ts +19 -0
  75. package/dist/hooks/useAsync.js +25 -0
  76. package/dist/index.d.ts +56 -0
  77. package/dist/index.js +58 -0
  78. package/dist/lib/host.d.ts +21 -0
  79. package/dist/lib/host.js +32 -0
  80. package/dist/lib/shell.d.ts +156 -0
  81. package/dist/lib/shell.js +16 -0
  82. package/dist/screens/AccountsScreen.d.ts +8 -0
  83. package/dist/screens/AccountsScreen.js +100 -0
  84. package/dist/screens/AnalyticsScreen.d.ts +7 -0
  85. package/dist/screens/AnalyticsScreen.js +64 -0
  86. package/dist/screens/ApprovalsScreen.d.ts +10 -0
  87. package/dist/screens/ApprovalsScreen.js +144 -0
  88. package/dist/screens/BriefsScreen.d.ts +7 -0
  89. package/dist/screens/BriefsScreen.js +83 -0
  90. package/dist/screens/CalendarScreen.d.ts +10 -0
  91. package/dist/screens/CalendarScreen.js +143 -0
  92. package/dist/screens/CampaignsScreen.d.ts +7 -0
  93. package/dist/screens/CampaignsScreen.js +52 -0
  94. package/dist/screens/ChannelMemoryScreen.d.ts +7 -0
  95. package/dist/screens/ChannelMemoryScreen.js +109 -0
  96. package/dist/screens/ContentScreen.d.ts +7 -0
  97. package/dist/screens/ContentScreen.js +64 -0
  98. package/dist/screens/DigestsScreen.d.ts +7 -0
  99. package/dist/screens/DigestsScreen.js +53 -0
  100. package/dist/screens/DraftsScreen.d.ts +10 -0
  101. package/dist/screens/DraftsScreen.js +108 -0
  102. package/dist/screens/FailuresScreen.d.ts +8 -0
  103. package/dist/screens/FailuresScreen.js +111 -0
  104. package/dist/screens/HookTestsScreen.d.ts +7 -0
  105. package/dist/screens/HookTestsScreen.js +54 -0
  106. package/dist/screens/InboxScreen.d.ts +12 -0
  107. package/dist/screens/InboxScreen.js +76 -0
  108. package/dist/screens/LinksScreen.d.ts +7 -0
  109. package/dist/screens/LinksScreen.js +52 -0
  110. package/dist/screens/PacingScreen.d.ts +7 -0
  111. package/dist/screens/PacingScreen.js +88 -0
  112. package/dist/screens/PublishJobsScreen.d.ts +7 -0
  113. package/dist/screens/PublishJobsScreen.js +66 -0
  114. package/dist/screens/SuggestionsScreen.d.ts +8 -0
  115. package/dist/screens/SuggestionsScreen.js +94 -0
  116. package/dist/screens/TextGateScreen.d.ts +7 -0
  117. package/dist/screens/TextGateScreen.js +111 -0
  118. package/dist/screens/ThreadsScreen.d.ts +8 -0
  119. package/dist/screens/ThreadsScreen.js +113 -0
  120. package/dist/screens/TypedBriefsScreen.d.ts +7 -0
  121. package/dist/screens/TypedBriefsScreen.js +75 -0
  122. package/dist/screens/labels.d.ts +121 -0
  123. package/dist/screens/labels.js +272 -0
  124. package/package.json +56 -3
@@ -0,0 +1,1092 @@
1
+ /**
2
+ * The account platforms the desktop shows: exactly the engine's PLATFORMS (crates/protocol/src/accounts.rs:22-33).
3
+ * The list and its order live in SOCIAL_PLATFORMS (platforms.ts). Draft-only ids are not in this union.
4
+ */
5
+ export type SocialPlatform = "instagram" | "facebook" | "threads" | "youtube" | "tiktok" | "x" | "linkedin" | "pinterest" | "bluesky" | "mastodon";
6
+ export type TokenHealth = {
7
+ status: "valid";
8
+ expiresOn: string;
9
+ } | {
10
+ status: "expiring";
11
+ expiresOn: string;
12
+ } | {
13
+ status: "refresh_failed";
14
+ } | {
15
+ status: "revoked";
16
+ };
17
+ /** Three states that are never merged into one "live" flag (plan section 4, P1). */
18
+ export type Entitlement = "sandbox_mechanics" | "review_submitted" | "audience_visible";
19
+ export interface PauseRecord {
20
+ by: "desktop" | "phone";
21
+ atUtc: string;
22
+ reason: string;
23
+ }
24
+ export interface AccountReadiness {
25
+ token: TokenHealth;
26
+ /** ISO date by which the operator must re-authorise. Null when the provider refreshes tokens. */
27
+ reauthNeededBy: string | null;
28
+ entitlement: Entitlement;
29
+ /** UTC instant the quota cooldown ends, or null when none is active. */
30
+ quotaCooldownUntil: string | null;
31
+ quotaCooldownSource: "numeric_retry_after" | "dated_retry_after" | null;
32
+ paused: PauseRecord | null;
33
+ }
34
+ export interface Venture {
35
+ id: string;
36
+ name: string;
37
+ }
38
+ export interface Account {
39
+ id: string;
40
+ venture: Venture;
41
+ platform: SocialPlatform;
42
+ handle: string;
43
+ displayName: string;
44
+ /** Last four characters of the platform account id, shown so same-handle look-alikes differ. */
45
+ idSuffix: string;
46
+ /** IANA zone the account publishes in. */
47
+ timeZone: string;
48
+ /** Stable colour band index (0 to 5). Always shown next to a text label. */
49
+ band: number;
50
+ readiness: AccountReadiness;
51
+ }
52
+ export type MediaKind = "video" | "image";
53
+ export interface MediaItem {
54
+ /** Full digest, for example "blake3:9c1e4a7f...". The UI shows a short form. */
55
+ hash: string;
56
+ kind: MediaKind;
57
+ durationSeconds: number | null;
58
+ /** A whole number of milliseconds, or null when the engine holds none. Absent on mock records. */
59
+ durationMs?: number | null;
60
+ dimensions: string | null;
61
+ status: "ready" | "uploading" | "missing";
62
+ uploadPercent: number | null;
63
+ }
64
+ /** "remake" is the format of a calendar entry the optimise module creates when a suggestion is accepted. */
65
+ export type JobFormat = "reel" | "carousel" | "photo" | "short" | "video" | "text_post" | "remake";
66
+ export type JobState = "draft" | "media_pending" | "ready" | "scheduled" | "publishing" | "published" | "outcome_unknown" | "failed" | "late" | "paused";
67
+ export type RejectReason = "wrong_content" | "wrong_account" | "timing" | "media_issue" | "other";
68
+ export type ApprovalInvalidation = "caption_changed" | "media_changed" | "policy_changed";
69
+ export type Approval = {
70
+ status: "none";
71
+ } | {
72
+ status: "pending";
73
+ } | {
74
+ status: "approved";
75
+ approvedRevision: number;
76
+ policyRevision: number;
77
+ approvedAtUtc: string;
78
+ } | {
79
+ status: "rejected";
80
+ reason: RejectReason;
81
+ note: string | null;
82
+ } | {
83
+ status: "invalidated";
84
+ approvedRevision: number;
85
+ policyRevision: number;
86
+ cause: ApprovalInvalidation;
87
+ };
88
+ export type DstStatus = {
89
+ kind: "none";
90
+ } | {
91
+ kind: "ambiguous_fires_once";
92
+ note: string;
93
+ } | {
94
+ kind: "nonexistent_moved";
95
+ note: string;
96
+ };
97
+ export interface Lateness {
98
+ /** UTC instant by which media had to be ready. */
99
+ mediaNotReadyBy: string;
100
+ policy: "publish_next_slot" | "held_for_review";
101
+ }
102
+ export interface ScheduleConflict {
103
+ withJobId: string;
104
+ minimumSpacingMinutes: number;
105
+ }
106
+ export interface PublishJob {
107
+ id: string;
108
+ accountId: string;
109
+ briefId: string;
110
+ format: JobFormat;
111
+ /** Job compare-and-swap revision. Every content, schedule or decision change bumps it. */
112
+ revision: number;
113
+ caption: string;
114
+ /** Short digest of the exact caption text, so the preview can be checked against the daemon. */
115
+ captionDigest: string;
116
+ firstComment: string | null;
117
+ settings: string[];
118
+ mediaItems: MediaItem[];
119
+ scheduledAtUtc: string;
120
+ /** Account zone at schedule time. Copied onto the job so the calendar reads one field. */
121
+ timeZone: string;
122
+ dst: DstStatus;
123
+ state: JobState;
124
+ approval: Approval;
125
+ /** Current platform-rules and policy revision the daemon applies to this job. Null when the engine does not state one. */
126
+ policyRevision: number | null;
127
+ lateness: Lateness | null;
128
+ conflict: ScheduleConflict | null;
129
+ /**
130
+ * The engine's publish job for this entry at its current revision. Absent when the engine does
131
+ * not report one (the desktop role cannot read publish jobs; see "Publish jobs" in the design doc).
132
+ * Null when there is no job for this revision.
133
+ */
134
+ publish?: PublishProgress | null;
135
+ }
136
+ export type PublishProgressState = "queued" | "running" | "waiting" | "published" | "failed" | "outcome_unknown" | "cancelled";
137
+ export interface PublishProgress {
138
+ state: PublishProgressState;
139
+ /** The platform's link to the post. Shown as text with a Copy link button; never opened. */
140
+ permalink: string | null;
141
+ /** Set only while waiting: the UTC instant the daemon next calls the platform. */
142
+ resumeAtUtc: string | null;
143
+ /** The engine's typed error code for the latest failure, or null. */
144
+ errorCode: string | null;
145
+ errorMessage: string | null;
146
+ }
147
+ /** The two decisions a person can record for an outcome_unknown job. */
148
+ export type PublishResolutionDecision = "posted" | "not_posted";
149
+ /** What a person recorded for an outcome_unknown job. Null on every other job. */
150
+ export interface PublishJobResolution {
151
+ decision: PublishResolutionDecision;
152
+ /** The human author as stored, for example "adrian". */
153
+ resolvedBy: string;
154
+ resolvedAtUtc: string;
155
+ /** What the person checked on the platform. */
156
+ note: string;
157
+ /** True when the remote post id was typed by a person, not returned by the platform. */
158
+ remoteIdEnteredByHand: boolean;
159
+ }
160
+ /** One publish job of a channel, as publish.list returns it, with the scope it belongs to. */
161
+ export interface PublishJobRecord {
162
+ scopeId: string;
163
+ jobId: string;
164
+ entryId: string;
165
+ entryRevision: number;
166
+ platform: string;
167
+ state: PublishProgressState;
168
+ /** The engine's phase name, as stored (for example create, publish or done). */
169
+ phase: string;
170
+ remotePostId: string | null;
171
+ /** The platform's link to the post, as text. Never opened. */
172
+ permalink: string | null;
173
+ attempts: number;
174
+ /** UTC instant the daemon next calls the platform. Set only while waiting. */
175
+ nextAttemptAtUtc: string | null;
176
+ errorCode: string | null;
177
+ errorMessage: string | null;
178
+ resolution: PublishJobResolution | null;
179
+ createdAtUtc: string;
180
+ updatedAtUtc: string;
181
+ }
182
+ /** A row the desktop could not read. It is shown with its reason and its raw JSON. */
183
+ export interface UnreadableRow {
184
+ readable: false;
185
+ /** The desktop's plain reason, for example a value it does not show yet. */
186
+ reason: string;
187
+ /** The row as the engine sent it, pretty printed. May contain untrusted event text. */
188
+ rawJson: string;
189
+ }
190
+ export type InboxKind = "comment" | "message" | "mention" | "other";
191
+ export type InboxState = "open" | "handled" | "ignored";
192
+ /** One inbox event, as inbox.list returns it. Every text field is untrusted and shown as received. */
193
+ export interface InboxEvent {
194
+ readable: true;
195
+ eventId: string;
196
+ /** The channel account the event is bound to, or null when no account matched. */
197
+ scopeId: string | null;
198
+ /** True when no stored account matched the recipient id, or more than one did. */
199
+ unmatched: boolean;
200
+ /** `no_account` or `ambiguous_account` when unmatched; null otherwise. */
201
+ unmatchedReason: string | null;
202
+ /** The account platform as stored. Shown through the platform name helper. */
203
+ platform: string;
204
+ kind: InboxKind;
205
+ /** The recipient account id the platform named. Untrusted. */
206
+ recipientId: string | null;
207
+ /** The platform's own id for the event. Untrusted. */
208
+ platformEventId: string | null;
209
+ /** The sender's platform id, as given. Untrusted. */
210
+ senderId: string | null;
211
+ /** The text, as received. Untrusted. */
212
+ text: string | null;
213
+ /** The event time the platform gave, as received and not parsed. Untrusted. */
214
+ eventTimeAsGiven: string | null;
215
+ /** True when a field was cut to its bound. The raw JSON keeps the whole item. */
216
+ oversize: boolean;
217
+ /** The instant the daemon stored the event (UTC). */
218
+ receivedAtUtc: string;
219
+ state: InboxState;
220
+ /** Who set the state (a person's name), or null while open. */
221
+ stateBy: string | null;
222
+ stateAtUtc: string | null;
223
+ /** The note recorded with the state. Empty when none. */
224
+ stateNote: string;
225
+ }
226
+ export type InboxRow = InboxEvent | UnreadableRow;
227
+ /** One page of events, newest first. */
228
+ export interface InboxListing {
229
+ rows: InboxRow[];
230
+ /** True when older events exist beyond this page. This screen loads the newest page only. */
231
+ moreAvailable: boolean;
232
+ }
233
+ /** The raw item JSON of one event, as received, pretty printed. Untrusted text. */
234
+ export interface InboxRaw {
235
+ eventId: string;
236
+ rawJson: string;
237
+ }
238
+ /** One count of the stored events: a scope (null for unmatched), a kind and a state, as stored. */
239
+ export interface InboxStatsGroup {
240
+ scopeId: string | null;
241
+ kind: string;
242
+ state: string;
243
+ count: number;
244
+ }
245
+ export type InboxStatsRow = InboxStatsGroup | UnreadableRow;
246
+ /** The inbox counts. Whole-store totals and refusals are not kept per channel by the daemon. */
247
+ export interface InboxStats {
248
+ totalEvents: number;
249
+ unmatchedEvents: number;
250
+ deliveriesParsed: number;
251
+ deliveriesUnparseable: number;
252
+ /** Deliveries refused for a bad signature, whole store. */
253
+ refusedTotal: number;
254
+ groups: InboxStatsRow[];
255
+ }
256
+ /** Why a job may not run now. The words are in the Pacing screen's labels. */
257
+ export type PacingReason = "not_due" | "outcome_unknown_on_account" | "account_not_ready" | "account_paused" | "rate_limited_until" | "daily_cap" | "account_gap";
258
+ /** A publish job named by publish.next. */
259
+ export interface PacingJob {
260
+ readable: true;
261
+ jobId: string;
262
+ /** The channel account of the job (its scope id). */
263
+ scopeId: string;
264
+ platform: string;
265
+ /** The platform's own account id for this job. */
266
+ remoteAccount: string;
267
+ entryId: string;
268
+ state: PublishProgressState;
269
+ phase: string;
270
+ /** When the job is due: the next attempt for a waiting job, else its enqueue instant. */
271
+ scheduledAtUtc: string;
272
+ }
273
+ /** A job that may not run now, with the first reason, every reason, and the earliest instant it could run. */
274
+ export interface PacingBlockedJob extends PacingJob {
275
+ reason: PacingReason;
276
+ reasons: PacingReason[];
277
+ /** The earliest instant every reason has passed. Null when none is given. */
278
+ untilUtc: string | null;
279
+ /** The daemon's plain detail for the job. */
280
+ detail: string;
281
+ /** The job selected first on this account, when this job waits behind it. */
282
+ behindJobId: string | null;
283
+ }
284
+ /** What publish.next says at one instant. */
285
+ export interface PublishNextView {
286
+ /** The daemon's instant the decision was made at (UTC). */
287
+ nowUtc: string;
288
+ /** Jobs that may run now, at most one per account. */
289
+ ready: (PacingJob | UnreadableRow)[];
290
+ /** How many jobs could run now, as the daemon counts them. */
291
+ readyTotal: number;
292
+ /** Jobs that may not run now. */
293
+ blocked: (PacingBlockedJob | UnreadableRow)[];
294
+ /** Jobs already past their create. Pacing does not hold these back. */
295
+ inProgress: (PacingJob | UnreadableRow)[];
296
+ }
297
+ /** One account's pacing, as publish.quota_get returns it. */
298
+ export interface PublishQuota {
299
+ /** The channel account's scope id. */
300
+ scopeId: string;
301
+ platform: string;
302
+ remoteAccount: string;
303
+ /** True when a person stored the limits. False means the host app defaults apply. */
304
+ optedIn: boolean;
305
+ minGapSecs: number;
306
+ maxPerDay: number;
307
+ /** the host app own default gap. Not a platform limit. */
308
+ defaultMinGapSecs: number;
309
+ /** the host app own default count per UTC day. Not a platform limit. */
310
+ defaultMaxPerDay: number;
311
+ pausedUntilUtc: string | null;
312
+ lastCreateAtUtc: string | null;
313
+ /** Creates counted in the UTC day of the read. */
314
+ createsToday: number;
315
+ /** The next UTC midnight: when the count restarts. */
316
+ dayResetsAtUtc: string;
317
+ rateLimitedAtUtc: string | null;
318
+ rateLimitedUntilUtc: string | null;
319
+ }
320
+ export interface DateRange {
321
+ /** Inclusive UTC instant. */
322
+ fromUtc: string;
323
+ /** Exclusive UTC instant. */
324
+ toUtc: string;
325
+ }
326
+ export type DataErrorCode = "revision_conflict" | "account_mismatch" | "not_approvable" | "reason_required" | "invalid_input" | "not_found";
327
+ export interface DataError {
328
+ code: DataErrorCode;
329
+ message: string;
330
+ /** Set on revision_conflict: the revision the daemon holds now. */
331
+ currentRevision?: number;
332
+ }
333
+ export type Result<T> = {
334
+ ok: true;
335
+ value: T;
336
+ } | {
337
+ ok: false;
338
+ error: DataError;
339
+ };
340
+ /** Every approve, reject and move carries the revision and account the operator saw. */
341
+ export interface RejectDecision {
342
+ revision: number;
343
+ accountId: string;
344
+ code: RejectReason;
345
+ note: string;
346
+ }
347
+ export type FailureKind = "dead_letter" | "outcome_unknown" | "alert";
348
+ export type AlertKind = "disk_full" | "credentials_expired" | "quota_cooldown";
349
+ export type FailureResolution = "published_confirmed" | "cancelled_not_published" | "accepted_as_lost" | "acknowledged";
350
+ export type ReconcileResult = {
351
+ outcome: "found_on_platform";
352
+ remoteId: string;
353
+ permalink: string;
354
+ checkedAtUtc: string;
355
+ } | {
356
+ outcome: "not_found";
357
+ checkedAtUtc: string;
358
+ };
359
+ export interface FailureItem {
360
+ id: string;
361
+ kind: FailureKind;
362
+ /** Only for kind "alert". */
363
+ alertKind: AlertKind | null;
364
+ /** Null for daemon-wide alerts such as disk space. */
365
+ accountId: string | null;
366
+ jobId: string | null;
367
+ /** One plain sentence: what happened. */
368
+ summary: string;
369
+ /** One plain sentence: the single next action. */
370
+ nextAction: string;
371
+ atUtc: string;
372
+ attempts: number | null;
373
+ status: "open" | "resolved";
374
+ reconcile: ReconcileResult | null;
375
+ resolution: FailureResolution | null;
376
+ resolutionNote: string | null;
377
+ }
378
+ export interface Digest {
379
+ windowStartUtc: string;
380
+ windowEndUtc: string;
381
+ /** Plain sentences, built from the same counts the screen shows. */
382
+ sentences: string[];
383
+ counts: {
384
+ published: number;
385
+ failedDeadLetter: number;
386
+ outcomeUnknown: number;
387
+ openAlerts: number;
388
+ pausedAccounts: number;
389
+ quotaCooldowns: number;
390
+ pendingApprovals: number;
391
+ openThreads: number;
392
+ };
393
+ }
394
+ export type ThreadKind = "comment" | "dm";
395
+ export type ThreadStatus = "open" | "snoozed" | "resolved";
396
+ export interface ThreadMessage {
397
+ id: string;
398
+ /** Invented handle. Never a real account. */
399
+ author: string;
400
+ fromOperator: boolean;
401
+ sentAtUtc: string;
402
+ text: string;
403
+ }
404
+ export interface AutoReplyPlan {
405
+ ruleName: string;
406
+ /** The keyword found in the latest inbound message. */
407
+ matchedKeyword: string;
408
+ /** The reply that would be sent. Shown only; never sent by this screen. */
409
+ draftReply: string;
410
+ /** "matched_not_sent" is a preview. "unavailable" carries the reason. */
411
+ state: "matched_not_sent" | "unavailable";
412
+ unavailableReason: string | null;
413
+ }
414
+ export interface ReplyWindow {
415
+ closesUtc: string;
416
+ }
417
+ export interface ThreadItem {
418
+ id: string;
419
+ accountId: string;
420
+ postTitle: string;
421
+ postPublishedUtc: string;
422
+ kind: ThreadKind;
423
+ messages: ThreadMessage[];
424
+ /** Null when the platform gives no reply window for this kind of thread. */
425
+ replyWindow: ReplyWindow | null;
426
+ autoReply: AutoReplyPlan | null;
427
+ status: ThreadStatus;
428
+ snoozedUntilUtc: string | null;
429
+ }
430
+ export type ModerationAction = "hide_comment" | "delete_comment" | "block_author";
431
+ export interface ModerationCapability {
432
+ platform: SocialPlatform;
433
+ action: ModerationAction;
434
+ supported: boolean;
435
+ /** Shown whenever supported is false. Says why, in words. */
436
+ reason: string;
437
+ }
438
+ /** Draft kinds. A comment or a reply needs a target URL before it can be approved. */
439
+ export type DraftKind = "post" | "comment" | "reply";
440
+ /** Draft states. Only the operator's recorded link moves a draft to posted_by_operator. */
441
+ export type DraftState = "drafted" | "approved" | "posted_by_operator" | "skipped" | "stale";
442
+ export interface ManualDraft {
443
+ id: string;
444
+ /** Venture, channel and account the draft belongs to, as text. */
445
+ channelLabel: string;
446
+ /** Platform name as stored: a platform on the account list, or "reddit". */
447
+ platform: string;
448
+ kind: DraftKind;
449
+ /** Community or thread URL. Shown as text and never opened by the screen. Null for a new post. */
450
+ target: string | null;
451
+ title: string | null;
452
+ body: string;
453
+ /** Unicode scalar values in the body. */
454
+ bodyChars: number;
455
+ disclosureRequired: boolean;
456
+ state: DraftState;
457
+ /** Compare-and-swap revision. Every action sends the revision the operator saw. */
458
+ revision: number;
459
+ /** The link the operator recorded. Set only when posted. */
460
+ postedUrl: string | null;
461
+ postedAtUtc: string | null;
462
+ skipReason: string | null;
463
+ sourceNote: string;
464
+ updatedAtUtc: string;
465
+ }
466
+ /** One stored channel the desktop can select for suggestions and spend. */
467
+ export interface OperatorScope {
468
+ id: string;
469
+ /** Venture, channel and account as text. */
470
+ label: string;
471
+ }
472
+ export type OptimiseState = "proposed" | "accepted" | "dismissed";
473
+ /** A calendar change the optimise module proposes from stored analytics. The desktop only decides it. */
474
+ export interface OptimiseProposal {
475
+ id: string;
476
+ scopeId: string;
477
+ /** The rule that produced it. "remake" is the only rule. */
478
+ kind: "remake";
479
+ state: OptimiseState;
480
+ /** Compare-and-swap revision. Accept and dismiss send the revision the operator saw. */
481
+ revision: number;
482
+ /** The published post the suggestion is about. */
483
+ sourceJobId: string;
484
+ platform: string;
485
+ metric: string;
486
+ window: string;
487
+ observedAtUtc: string;
488
+ /** A whole number as exact text. Never converted to a float, so it is shown as stored. */
489
+ value: string;
490
+ /** The top-decile cut. A post wins when its value is at least this. Exact text, like value. */
491
+ threshold: string;
492
+ sampleSize: number;
493
+ decileSize: number;
494
+ dueAtUtc: string;
495
+ /** The published posts compared. */
496
+ lineageJobIds: string[];
497
+ /** The analytics pulls whose observations were compared. */
498
+ lineagePullIds: string[];
499
+ decisionNote: string | null;
500
+ decidedAtUtc: string | null;
501
+ /** The draft calendar entry an accepted suggestion created. Null otherwise. */
502
+ calendarEntryId: string | null;
503
+ createdAtUtc: string;
504
+ updatedAtUtc: string;
505
+ }
506
+ /** The month's allowance for one channel. remaining = limit - reserved - committed. */
507
+ export interface SpendAllowance {
508
+ /** "YYYY-MM". */
509
+ month: string;
510
+ currency: string;
511
+ limitMicro: number;
512
+ reservedMicro: number;
513
+ committedMicro: number;
514
+ remainingMicro: number;
515
+ revision: number;
516
+ updatedAtUtc: string;
517
+ }
518
+ export interface SpendRouteTotal {
519
+ routeId: string;
520
+ countReserved: number;
521
+ countCommitted: number;
522
+ countReleased: number;
523
+ countExpired: number;
524
+ openMicro: number;
525
+ forecastMicro: number;
526
+ measuredMicro: number;
527
+ varianceMicro: number;
528
+ overReservations: number;
529
+ }
530
+ export interface SpendReport {
531
+ month: string;
532
+ currency: string;
533
+ limitMicro: number;
534
+ reservedMicro: number;
535
+ committedMicro: number;
536
+ remainingMicro: number;
537
+ /** True when the allowance totals equal the reservation and commit totals. */
538
+ reconciles: boolean;
539
+ routes: SpendRouteTotal[];
540
+ }
541
+ /** The latest version of one price route, with its qualification at the data source's clock. */
542
+ export interface SpendPrice {
543
+ routeId: string;
544
+ version: number;
545
+ provider: string;
546
+ modelSlug: string;
547
+ unit: string;
548
+ amountMicro: number;
549
+ currency: string;
550
+ sourceUrl: string | null;
551
+ sourcePathLine: string | null;
552
+ fetchedAtUtc: string;
553
+ verifiedBy: string | null;
554
+ qualified: boolean;
555
+ /** Set only when not qualified: the engine's reason, as text. */
556
+ reason: string | null;
557
+ }
558
+ /** One campaign in a channel. `utmCampaign` is the slug written into utm_campaign on links cut for it. */
559
+ export interface Campaign {
560
+ id: string;
561
+ scopeId: string;
562
+ name: string;
563
+ utmCampaign: string;
564
+ /** Compare-and-swap revision. The desktop never writes it. */
565
+ revision: number;
566
+ createdAtUtc: string;
567
+ updatedAtUtc: string;
568
+ }
569
+ /** One tracked link. Its final URL, with the UTM parts, is fixed when the link is created. */
570
+ export interface TrackedLink {
571
+ id: string;
572
+ scopeId: string;
573
+ campaignId: string;
574
+ variantId: string;
575
+ platform: string;
576
+ /** The https destination as given. Shown as text and never opened by the screen. */
577
+ destination: string;
578
+ /** The calendar entry the link was cut for, or null when it was not attached to one. */
579
+ calendarEntryId: string | null;
580
+ utmSource: string;
581
+ utmMedium: string;
582
+ utmCampaign: string;
583
+ utmContent: string;
584
+ finalUrl: string;
585
+ createdAtUtc: string;
586
+ }
587
+ /** One link's visit count in an attribution summary. */
588
+ export interface LinkAttributionRow {
589
+ linkId: string;
590
+ variantId: string;
591
+ platform: string;
592
+ calendarEntryId: string | null;
593
+ /** A whole, non-negative count. Zero is a normal state. */
594
+ visits: number;
595
+ }
596
+ /** The visit counts of one campaign's links. `totalVisits` is the sum of the rows. */
597
+ export interface CampaignAttribution {
598
+ campaignId: string;
599
+ totalVisits: number;
600
+ links: LinkAttributionRow[];
601
+ }
602
+ /** One tracked link of a channel, with the campaign it was cut for and its plain visit count. */
603
+ export interface ChannelLink {
604
+ link: TrackedLink;
605
+ /** The name of the campaign the link belongs to. */
606
+ campaignName: string;
607
+ /** Visits recorded against the link, a whole count. Null when the count was not read with the link. */
608
+ visits: number | null;
609
+ }
610
+ /** A set is open until one read decides it. A decided set never changes. */
611
+ export type HookTestState = "open" | "decided";
612
+ /** The winner is promoted; every other opening is archived. Null on every opening of an open set. */
613
+ export type HookTestRole = "promoted" | "archived";
614
+ /** One opening of a hook test. The number fields are set only once a read has compared the opening. */
615
+ export interface HookTestOpening {
616
+ /** 1-based position of the opening in the body. */
617
+ index: number;
618
+ text: string;
619
+ /** The publish job that carries this opening, or null when not bound yet. */
620
+ jobId: string | null;
621
+ boundAtUtc: string | null;
622
+ role: HookTestRole | null;
623
+ /** The metric value as exact integer text. Null when the opening has not been read. */
624
+ value: string | null;
625
+ /** The instant of the observation compared. */
626
+ observedAtUtc: string | null;
627
+ /** The analytics pull that stored the observation compared. */
628
+ pullId: string | null;
629
+ /** The job's publish instant the 24-hour window counted from. */
630
+ publishedAtUtc: string | null;
631
+ }
632
+ /** A variant set: one body with 2 to 5 openings, compared on one metric. */
633
+ export interface HookTestSet {
634
+ id: string;
635
+ scopeId: string;
636
+ briefRef: string;
637
+ body: string;
638
+ metric: string;
639
+ window: string;
640
+ state: HookTestState;
641
+ /** Compare-and-swap revision. The desktop never writes it. */
642
+ revision: number;
643
+ /** The 1-based index of the promoted opening once decided, otherwise null. */
644
+ winnerIndex: number | null;
645
+ decidedAtUtc: string | null;
646
+ createdAtUtc: string;
647
+ updatedAtUtc: string;
648
+ openings: HookTestOpening[];
649
+ }
650
+ /** One finding in a digest. `source` is an http(s) url or a path:line reference: text, never opened. */
651
+ export interface DigestEntry {
652
+ id: string;
653
+ topic: string;
654
+ finding: string;
655
+ source: string;
656
+ /** YYYY-MM-DD: the day the finding was read. */
657
+ fetchedOn: string;
658
+ /** The platform the finding suggests, as stored. */
659
+ platform: string;
660
+ /** The format the finding suggests, as stored. */
661
+ format: string;
662
+ }
663
+ /** One research digest in a channel. A stored digest is immutable. */
664
+ export interface ResearchDigest {
665
+ id: string;
666
+ scopeId: string;
667
+ /** YYYY-MM-DD. */
668
+ digestDate: string;
669
+ entries: DigestEntry[];
670
+ createdAtUtc: string;
671
+ }
672
+ /** One digest entry that became a draft calendar entry. */
673
+ export interface DigestApplication {
674
+ digestId: string;
675
+ digestEntryId: string;
676
+ briefId: string;
677
+ /** The draft calendar entry the application created. */
678
+ calendarEntryId: string;
679
+ appliedAtUtc: string;
680
+ }
681
+ export type ContentState = "active" | "withdrawn" | "expired";
682
+ export type ContentMediaKind = "image" | "video";
683
+ export type ContentVariantKind = "caption" | "cta";
684
+ export type ContentVerdict = "insufficient_evidence" | "evidence_sufficient";
685
+ export interface ContentMediaItem {
686
+ hash: string;
687
+ kind: ContentMediaKind;
688
+ /** A whole number of milliseconds, or null when the engine holds none. */
689
+ durationMs: number | null;
690
+ }
691
+ /** One reusable media record. `state` is derived from the stored state and the clock; expired is never stored. */
692
+ export interface ContentRecord {
693
+ id: string;
694
+ scopeId: string;
695
+ title: string;
696
+ media: ContentMediaItem[];
697
+ baseCaption: string;
698
+ /** Recorded for the operator. Nothing acts on it yet. */
699
+ evergreen: boolean;
700
+ /** Whole days between two occurrences on one platform. */
701
+ cooldownDays: number;
702
+ /** Last UTC instant the record may run, or null when it never expires. */
703
+ expiresAtUtc: string | null;
704
+ state: ContentState;
705
+ /** Set only when withdrawn. */
706
+ withdrawalReason: string | null;
707
+ revision: number;
708
+ createdAtUtc: string;
709
+ updatedAtUtc: string;
710
+ }
711
+ /** One caption or call-to-action variant of a record. */
712
+ export interface ContentVariant {
713
+ id: string;
714
+ scopeId: string;
715
+ contentId: string;
716
+ kind: ContentVariantKind;
717
+ text: string;
718
+ createdAtUtc: string;
719
+ }
720
+ /** A record with its variants, as the engine returns them for one record. */
721
+ export interface ContentDetail {
722
+ record: ContentRecord;
723
+ variants: ContentVariant[];
724
+ }
725
+ /** Plain occurrence counts for one variant. Only published occurrences count toward the minimum. */
726
+ export interface ContentVariantCount {
727
+ variantId: string;
728
+ text: string;
729
+ planned: number;
730
+ published: number;
731
+ cancelled: number;
732
+ meetsMinimum: boolean;
733
+ /** Published occurrences whose primary metric value was compared. Zero unless a metric is declared. */
734
+ compared?: number;
735
+ /** Exact sum of the compared values, as decimal text. "0" unless a metric is declared. */
736
+ total?: string;
737
+ }
738
+ /** The engine's comparison of one record's variants. The engine names no winner in this slice. */
739
+ export interface ContentExperimentSummary {
740
+ scopeId: string;
741
+ contentId: string;
742
+ minOccurrences: number;
743
+ verdict: ContentVerdict;
744
+ /** The engine's own sentence listing why the verdict holds. Shown as stored. */
745
+ reason: string;
746
+ /** The primary metric declared for the record, or null. */
747
+ primaryMetric?: string | null;
748
+ captionWinner?: string | null;
749
+ ctaWinner?: string | null;
750
+ caption: ContentVariantCount[];
751
+ cta: ContentVariantCount[];
752
+ }
753
+ /** Which style rules treat an inconclusive or unverified finding as a block. Mirrors the engine's flags. */
754
+ export interface GateRequiredRules {
755
+ bannedPhrase: boolean;
756
+ dashDensity: boolean;
757
+ semicolonDensity: boolean;
758
+ sentenceLengthVariance: boolean;
759
+ tricolonFrequency: boolean;
760
+ stockOpener: boolean;
761
+ }
762
+ /** The latest revision of a channel's gate policy. Thresholds are exact: 1500 is written 1.5 on screen. */
763
+ export interface GatePolicy {
764
+ scopeId: string;
765
+ revision: number;
766
+ createdAtUtc: string;
767
+ bannedPhrases: string[];
768
+ stockOpeners: string[];
769
+ minWords: number;
770
+ /** Dashes per 100 words, at most, in thousandths. */
771
+ dashMaxPer100WordsMilli: number;
772
+ /** Semicolons per 100 words, at most, in thousandths. */
773
+ semicolonMaxPer100WordsMilli: number;
774
+ /** Population variance of words per sentence, at least, in thousandths. */
775
+ varianceMinMilli: number;
776
+ varianceMinSentences: number;
777
+ /** Tricolon sentences per 100 sentences, at most, in thousandths. */
778
+ tricolonMaxPer100SentencesMilli: number;
779
+ tricolonMinSentences: number;
780
+ required: GateRequiredRules;
781
+ }
782
+ /** The overall result of a check: pass, hold (a person decides) or block. */
783
+ export type GateDisposition = "pass" | "hold" | "block";
784
+ /** One rule's verdict. The engine's reason text is kept exactly as written. */
785
+ export type GateVerdict = "pass" | "fail" | "inconclusive" | "unverified" | "hold" | "not_applicable";
786
+ export interface GateRuleFinding {
787
+ ruleId: string;
788
+ required: boolean;
789
+ verdict: GateVerdict;
790
+ /** True when this finding alone blocks the script. */
791
+ blocks: boolean;
792
+ reason: string;
793
+ }
794
+ /** A dry-run check. Nothing is stored. */
795
+ export interface GateCheck {
796
+ subjectId: string;
797
+ disposition: GateDisposition;
798
+ policyRevision: number;
799
+ /** fnv1a128 over the text bytes. For identification only. */
800
+ textHash: string;
801
+ rules: GateRuleFinding[];
802
+ blockingRules: string[];
803
+ proofIdsUsed: string[];
804
+ }
805
+ /** One stored verdict record of a subject, newest first in a list. The record's own JSON is not read here. */
806
+ export interface GateRecord {
807
+ scopeId: string;
808
+ recordSeq: number;
809
+ subjectId: string;
810
+ textHash: string;
811
+ policyRevision: number;
812
+ disposition: GateDisposition;
813
+ checkedAtUtc: string;
814
+ }
815
+ /** Any JSON value, as the engine stores a document. */
816
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
817
+ [key: string]: JsonValue;
818
+ };
819
+ /**
820
+ * `issued` until an envelope is accepted for the brief; `partial` while some envelopes are stored but the engine's
821
+ * latest result was partial (part of the request was skipped or refused); `delivered` once everything requested
822
+ * was produced.
823
+ */
824
+ export type BriefState = "issued" | "partial" | "delivered";
825
+ /** One outgoing content brief in a channel. */
826
+ export interface OutgoingBrief {
827
+ scopeId: string;
828
+ requestId: string;
829
+ briefId: string;
830
+ revision: number;
831
+ state: BriefState;
832
+ createdAtUtc: string;
833
+ updatedAtUtc: string;
834
+ }
835
+ /** The fields of a brief document the desktop shows. A field the document lacks is null or empty, never invented. */
836
+ export interface BriefSummary {
837
+ briefType: string | null;
838
+ platforms: string[];
839
+ deliverables: string[];
840
+ hook: string | null;
841
+ reviewMode: string | null;
842
+ policyRevision: number | null;
843
+ stageCount: number;
844
+ }
845
+ export interface OutgoingBriefDetail extends OutgoingBrief {
846
+ summary: BriefSummary;
847
+ /** The brief exactly as the engine stored it. */
848
+ document: JsonValue;
849
+ }
850
+ /** One accepted delivery envelope and the request it answers. A brief holds at most one envelope per deliverable. */
851
+ export interface DeliveryEnvelope {
852
+ scopeId: string;
853
+ envelopeId: string;
854
+ requestId: string;
855
+ /** The engine's key for the deliverable this envelope is for. Empty when the envelope was stored without one. */
856
+ deliverable: string;
857
+ briefId: string;
858
+ briefRevision: number;
859
+ acceptedAtUtc: string;
860
+ document: JsonValue;
861
+ }
862
+ /** The outcomes an attempt can end with. `running` is shown while the engine child is still alive. */
863
+ export type DispatchOutcome = "running" | "delivered" | "partial" | "awaiting_approval" | "engine_failed" | "timed_out" | "delivery_invalid" | "engine_missing" | "interrupted";
864
+ /** One entry of the engine's `skipped` list, as the engine wrote it. A field the entry lacks is null. */
865
+ export interface DispatchSkipped {
866
+ deliverable: string | null;
867
+ /** For example `spend_authorisation_required`: a paid route the engine refused. */
868
+ code: string | null;
869
+ message: string | null;
870
+ }
871
+ /** One run of an editor engine against an outgoing brief. */
872
+ export interface DispatchAttempt {
873
+ scopeId: string;
874
+ attemptId: number;
875
+ requestId: string;
876
+ engine: string;
877
+ startedAtUtc: string;
878
+ /** Null while the run is still going. */
879
+ finishedAtUtc: string | null;
880
+ /** Null when the child was killed or never ran. */
881
+ exitCode: number | null;
882
+ outcome: DispatchOutcome;
883
+ /** The capped tail of the engine's stderr. Null when none was kept. */
884
+ stderrTail: string | null;
885
+ /** The message the attempt recorded, or null. */
886
+ message: string | null;
887
+ /** The deliverable keys that produced an envelope in this attempt. Empty when none did. */
888
+ producedDeliverables: string[];
889
+ /** What the engine skipped or refused, verbatim. Empty when nothing was. */
890
+ skipped: DispatchSkipped[];
891
+ }
892
+ /** One stored revision of a channel's memory document, without the document. */
893
+ export interface ChannelMemoryRevision {
894
+ scopeId: string;
895
+ revision: number;
896
+ /** The revision this one replaces. Null for the first revision. */
897
+ supersedes: number | null;
898
+ author: string;
899
+ createdAtUtc: string;
900
+ /** Identifies the revision's content. It is not a security digest. */
901
+ contentHash: string;
902
+ }
903
+ /** The latest revision with its document as the engine stores it. A section the document lacks is absent. */
904
+ export interface ChannelMemoryDetail extends ChannelMemoryRevision {
905
+ document: JsonValue;
906
+ }
907
+ /** The proof statuses the engine stores. */
908
+ export type ProofStatus = "collected" | "approved" | "withdrawn" | "superseded";
909
+ /** One proof in a channel's proof bank. `value` is an exact decimal string and is shown verbatim, never parsed. */
910
+ export interface ProofRecord {
911
+ scopeId: string;
912
+ answerId: string;
913
+ proofId: string;
914
+ status: ProofStatus;
915
+ subject: string;
916
+ value: string;
917
+ units: string;
918
+ origin: string;
919
+ observedFrom: string;
920
+ /** Empty text when the answer named no end date. */
921
+ observedTo: string;
922
+ /** Set on a superseded proof: the proof id that replaces it. */
923
+ supersededBy: string | null;
924
+ /** Set on a withdrawn proof when the engine holds a note. */
925
+ withdrawalNote: string | null;
926
+ }
927
+ /** The seven brief types the engine stores. */
928
+ export type BriefType = "talking_head" | "faceless_cartoon_short" | "explainer" | "carousel" | "ugc_character_review" | "product_pitch" | "product_demo";
929
+ /** Where a platform rule places a link. "none" means the platform carries no link. */
930
+ export type LinkPlacement = "caption" | "bio" | "first_comment" | "none";
931
+ /** One scene of an explainer beat. Every field is text, as the engine stores it. */
932
+ export interface SceneSpec {
933
+ teaches: string;
934
+ frame: string;
935
+ motion: string;
936
+ words: string;
937
+ sound: string;
938
+ }
939
+ /** The five beats of an explainer, in story order. */
940
+ export interface ExplainerBeats {
941
+ question: SceneSpec;
942
+ model: SceneSpec;
943
+ proof: SceneSpec;
944
+ turn: SceneSpec;
945
+ payoff: SceneSpec;
946
+ }
947
+ /** One product-demo step: the text said while the step is recorded. */
948
+ export interface DemoStep {
949
+ say: string;
950
+ }
951
+ /** One typed brief in a channel. Its type-specific parts are set exactly for the types that carry them. */
952
+ export interface TypedBrief {
953
+ scopeId: string;
954
+ briefId: string;
955
+ briefType: BriefType;
956
+ hook: string;
957
+ body: string;
958
+ /** Set exactly when the brief is an explainer. */
959
+ explainer: ExplainerBeats | null;
960
+ /** Non-empty exactly when the brief is a product demo. */
961
+ demoSteps: DemoStep[];
962
+ /** Compare-and-swap revision. The desktop never writes it. */
963
+ revision: number;
964
+ createdAtUtc: string;
965
+ updatedAtUtc: string;
966
+ }
967
+ /** One platform-native variant of one brief at one brief revision, under one platform rule version. */
968
+ export interface BriefVariant {
969
+ scopeId: string;
970
+ variantId: string;
971
+ briefId: string;
972
+ briefRevision: number;
973
+ platform: string;
974
+ ruleRevision: string;
975
+ /** The placement the rule applied. */
976
+ linkPlacement: LinkPlacement;
977
+ /** The final caption as the engine stored it. Hashtags are part of this text; the engine keeps no separate list. */
978
+ caption: string;
979
+ /** The https link as given, or null when none was given. Shown as text and never opened. */
980
+ link: string | null;
981
+ createdAtUtc: string;
982
+ }
983
+ /** Where a stored number came from: the platform's API, or a file the owner exported by hand. */
984
+ export type AnalyticsSource = "api" | "manual_export";
985
+ /** A metric is stored with a value, or as unavailable when the platform does not provide it. Never a stored zero. */
986
+ export type AnalyticsMetricState = "value" | "unavailable";
987
+ /** The latest stored observation of one metric of one post, with the post's age at that observation. */
988
+ export interface AnalyticsSummaryMetric {
989
+ metric: string;
990
+ state: AnalyticsMetricState;
991
+ /** An exact decimal string, shown verbatim. Null when unavailable. */
992
+ value: string | null;
993
+ observedAtUtc: string;
994
+ /** "lifetime" for an API row. Null for a manual row, which names no window. */
995
+ window: string | null;
996
+ source: AnalyticsSource;
997
+ /** The pull that stored an API row. Null for a manual row. */
998
+ pullId: string | null;
999
+ /** The import that stored a manual row. Null for an API row. */
1000
+ importId: string | null;
1001
+ /** The observation instant minus the publish instant, in seconds. */
1002
+ postAgeSeconds: number;
1003
+ }
1004
+ /** One published post with its latest stored metrics. */
1005
+ export interface AnalyticsPost {
1006
+ scopeId: string;
1007
+ jobId: string;
1008
+ platform: string;
1009
+ remotePostId: string;
1010
+ publishedAtUtc: string;
1011
+ metrics: AnalyticsSummaryMetric[];
1012
+ }
1013
+ /** One stored observation of one metric of one post, as analytics.list returns it. */
1014
+ export interface AnalyticsObservation {
1015
+ scopeId: string;
1016
+ jobId: string;
1017
+ metric: string;
1018
+ state: AnalyticsMetricState;
1019
+ /** An exact decimal string, shown verbatim. Null when unavailable. */
1020
+ value: string | null;
1021
+ observedAtUtc: string;
1022
+ /** "lifetime" for an API row. Null for a manual row. */
1023
+ window: string | null;
1024
+ source: AnalyticsSource;
1025
+ /** The pull that stored an API row. Null for a manual row. */
1026
+ pullId: string | null;
1027
+ /** The import that stored a manual row. Null for an API row. */
1028
+ importId: string | null;
1029
+ /** Lineage of the publish job, when recorded. Absent otherwise. */
1030
+ variantId?: string | null;
1031
+ renderHash?: string | null;
1032
+ timeMap?: string | null;
1033
+ }
1034
+ /** One committed manual import in a channel. Its values appear with the posts they belong to. */
1035
+ export interface MetricImport {
1036
+ scopeId: string;
1037
+ importId: string;
1038
+ platform: string;
1039
+ sourceNote: string;
1040
+ source: "manual_export";
1041
+ createdAtUtc: string;
1042
+ rowsWritten: number;
1043
+ observationsWritten: number;
1044
+ }
1045
+ /** alive: the age is at most the threshold. stale: older than it. missing: no record. unreadable: the file does not parse. */
1046
+ export type HeartbeatState = "alive" | "stale" | "missing" | "unreadable";
1047
+ /** The daemon's one heartbeat record. Null fields mean there is no readable record. */
1048
+ export interface HeartbeatStatus {
1049
+ /** The instant the age was measured at, UTC. */
1050
+ nowUtc: string;
1051
+ staleAfterSeconds: number;
1052
+ /** True only when the state is alive: no heartbeat is not health. */
1053
+ allAlive: boolean;
1054
+ state: HeartbeatState;
1055
+ pid: number | null;
1056
+ /** The record's update time, milliseconds since the Unix epoch. */
1057
+ updatedMs: number | null;
1058
+ /** The engine's whole-second age, shown as given. */
1059
+ ageSeconds: number | null;
1060
+ /** The record's own state text (the beat note), or null. */
1061
+ recordState: string | null;
1062
+ /** Why the file could not be read. Null otherwise. */
1063
+ error: string | null;
1064
+ }
1065
+ /** One restore of the channel store from a verified backup. */
1066
+ export interface RestoreReceipt {
1067
+ receiptId: string;
1068
+ restoredAtUtc: string;
1069
+ /** The digest of the backup's manifest file, as the engine stored it. */
1070
+ manifestDigest: string;
1071
+ /** Null when there was no readable store to replace. */
1072
+ schemaBefore: number | null;
1073
+ schemaAfter: number;
1074
+ /** Where the replaced store was set aside. Null when there was no store to set aside. */
1075
+ previousStore: string | null;
1076
+ }
1077
+ export interface RestoreReceiptList {
1078
+ /** Newest first, as the engine returns them. */
1079
+ receipts: RestoreReceipt[];
1080
+ /** Names of receipt files that did not parse. */
1081
+ unreadable: string[];
1082
+ }
1083
+ /**
1084
+ * The outcome of disconnecting an account. The account reads back as not connected. `cancelledJobs` is the number of
1085
+ * queued or waiting publish jobs the engine cancelled; `keptJobs` is the number it left because a post may already
1086
+ * exist on the platform (the daemon reconciles those).
1087
+ */
1088
+ export interface AccountDisconnect {
1089
+ account: Account;
1090
+ cancelledJobs: number;
1091
+ keptJobs: number;
1092
+ }