@bunizao/contracts 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/admin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ClientFingerprint, CommentStatus, Interaction } from './comments';
1
+ import type { ClientFingerprint, CommentAuthAtWrite, CommentClaimMethod, CommentStatus, Interaction } from './comments';
2
2
  import type { DeliveryMode, NotifyAuditEventType, NotifyChannel, SubscriberRecord, SubscriberStatus } from './notify';
3
3
  export interface SubscriberChannelCount {
4
4
  total: number;
@@ -211,6 +211,10 @@ export interface AdminClusterCount {
211
211
  and no body hash. */
212
212
  export interface AdminCommentActor {
213
213
  readerId: string | null;
214
+ /** Missing on older responses means unknown, not authenticated. */
215
+ authAtWrite?: CommentAuthAtWrite;
216
+ claimedAt?: string | null;
217
+ claimMethod?: CommentClaimMethod | null;
214
218
  /** Plaintext while the row still holds it (verified: with the row;
215
219
  unverified: seven days). */
216
220
  email: string | null;
@@ -297,10 +301,60 @@ export interface AdminBanResult {
297
301
  comments: number;
298
302
  reactions: number;
299
303
  };
304
+ operation?: AdminBanOperation | null;
305
+ }
306
+ export interface AdminBanPreview {
307
+ accounts: number;
308
+ sessions: number;
309
+ comments: Record<AdminCommentStatus | 'total', number>;
310
+ reactions: number;
311
+ purge: {
312
+ comments: number;
313
+ reactions: number;
314
+ };
315
+ windowDays: 90;
316
+ purgeLimit: number;
317
+ purgeAllowed: boolean;
318
+ }
319
+ export interface AdminBanOperation {
320
+ id: string;
321
+ createdAt: string;
322
+ source: AdminBanSource;
323
+ keys: AdminBanInput['keys'];
324
+ note: string | null;
325
+ purged: {
326
+ comments: number;
327
+ reactions: number;
328
+ };
329
+ restoredAt: string | null;
330
+ restorableUntil: string;
331
+ restored: {
332
+ comments: number;
333
+ reactions: number;
334
+ };
335
+ skipped: {
336
+ comments: number;
337
+ reactions: number;
338
+ };
339
+ }
340
+ export interface AdminBanOperationListResult {
341
+ operations: AdminBanOperation[];
342
+ }
343
+ export interface AdminBanRestoreResult {
344
+ operation: AdminBanOperation;
300
345
  }
301
346
  export interface AdminBanListResult {
302
347
  bans: AdminBan[];
303
348
  }
349
+ export interface AdminBannedReader {
350
+ readerId: string;
351
+ email: string;
352
+ displayName: string | null;
353
+ updatedAt: string;
354
+ }
355
+ export interface AdminBannedReaderListResult {
356
+ readers: AdminBannedReader[];
357
+ }
304
358
  /** Everything about one key in one response --
305
359
  `GET /admin/sources/:type/:value`. */
306
360
  export interface AdminSourceProfile {
@@ -308,6 +362,13 @@ export interface AdminSourceProfile {
308
362
  type: AdminSourceKeyType;
309
363
  value: string;
310
364
  };
365
+ identitySummary?: {
366
+ verifiedAccounts: number;
367
+ anonymousSessions: number;
368
+ claimedComments: number;
369
+ sharedStorage: boolean;
370
+ sharedFingerprint: boolean;
371
+ };
311
372
  firstSeenAt: string | null;
312
373
  lastSeenAt: string | null;
313
374
  comments: {
@@ -327,9 +388,7 @@ export interface AdminSourceProfile {
327
388
  /** Newest 50. */
328
389
  rows: AdminReactionRecord[];
329
390
  };
330
- /** Distinct values of every other key this source has used. The spread is
331
- the churn: one fingerprint over 40 sessions and 12 IPs is a bot; one
332
- session over 3 IPs is a phone that changed networks. */
391
+ /** Distinct observed values. Shared environments do not establish a person. */
333
392
  spread: Record<'session' | 'ip' | 'ip24' | 'fp' | 'clientFp' | 'clientFpStable' | 'storageId' | 'email' | 'asn' | 'ua', number>;
334
393
  /** Every bot and vpn hint this source has tripped, with how often. */
335
394
  hints: Array<{
@@ -337,9 +396,8 @@ export interface AdminSourceProfile {
337
396
  kind: 'bot' | 'vpn';
338
397
  count: number;
339
398
  }>;
340
- /** Two hops over strong keys only (session, email, clientFpStable,
341
- storageId): the sessions this source links to, and what those sessions
342
- carried. Never `ip24`, `asn` or `ua` as a hop. */
399
+ /** Links describe verified-at-write account evidence or shared storage.
400
+ clientFpStable remains zero for compatibility; it never creates links. */
343
401
  linked: {
344
402
  sessions: number;
345
403
  via: Record<'email' | 'clientFpStable' | 'storageId', number>;
@@ -374,12 +432,52 @@ export interface AdminInsightRow {
374
432
  }
375
433
  export type AdminCommentInsightsWindow = '7d' | '30d' | '90d';
376
434
  export type AdminReactionInsightsWindow = '48h' | '7d';
435
+ export interface AdminCommentQuality {
436
+ since: string;
437
+ collectedSince: string | null;
438
+ available: {
439
+ moderation: boolean;
440
+ requests: boolean;
441
+ clientReports: boolean;
442
+ };
443
+ /** A cohort of automatically held comments and the first later owner decision. */
444
+ moderation: {
445
+ held: number;
446
+ reviewed: number;
447
+ released: number;
448
+ releasedShare: number | null;
449
+ };
450
+ /** Request outcomes include retries. Authenticated does not mean known benign. */
451
+ requests: {
452
+ attempts: number;
453
+ failures: number;
454
+ failureShare: number | null;
455
+ authenticatedAttempts: number;
456
+ authenticatedFailures: number;
457
+ authenticatedFailureShare: number | null;
458
+ outcomes: Array<{
459
+ kind: 'comment' | 'reaction';
460
+ outcome: string;
461
+ count: number;
462
+ }>;
463
+ };
464
+ /** Self-reported and incomplete when the browser cannot deliver telemetry. */
465
+ clientReports: {
466
+ reports: number;
467
+ failures: number;
468
+ networkFailures: number;
469
+ challengedAttempts: number;
470
+ repeatedChallenges: number;
471
+ untrusted: true;
472
+ };
473
+ }
377
474
  /** `GET /admin/comments/insights?window=`. Every table carries a held rate,
378
475
  because a source is only interesting relative to how the automatic pass
379
476
  treats it. NULL groups show as their own row, never dropped. */
380
477
  export interface AdminCommentInsights {
381
478
  window: AdminCommentInsightsWindow;
382
479
  since: string;
480
+ quality?: AdminCommentQuality;
383
481
  networks: Array<AdminInsightRow & {
384
482
  asn: number | null;
385
483
  asOrg: string | null;
@@ -42,7 +42,7 @@ export declare function commentAnchorToken(commentId: string): Promise<string>;
42
42
  export declare const COMMENT_ANCHOR_PATTERN: RegExp;
43
43
  export declare const MODERATION_ACTIONS: readonly ['publish', 'hold', 'reject', 'unsure'];
44
44
  export type ModerationAction = (typeof MODERATION_ACTIONS)[number];
45
- export declare const MODERATION_REASONS: readonly ['ok', 'spam', 'promotional', 'abuse', 'off_topic', 'personal_info'];
45
+ export declare const MODERATION_REASONS: readonly ['ok', 'spam', 'promotional', 'abuse', 'off_topic', 'personal_info', 'dwell_expired'];
46
46
  export type ModerationReason = (typeof MODERATION_REASONS)[number];
47
47
  /** Never sent to a reader. Rides along on the owner's Telegram notification. */
48
48
  export interface ModerationVerdict {
@@ -491,3 +491,30 @@ export declare function commentPolicyFromTags(tags: readonly CommentPolicyTagLik
491
491
  `readonly` and `off` answer no; the difference between them is drawn, not
492
492
  enforced. */
493
493
  export declare const acceptsComments: (policy: CommentPolicy) => boolean;
494
+ /** Evidence recorded when the comment was written, never upgraded by claiming. */
495
+ export type CommentAuthAtWrite = 'unknown' | 'anonymous' | 'verified';
496
+ export type CommentClaimMethod = 'session' | 'confirmed';
497
+ export interface ReaderClaimCandidate {
498
+ id: string;
499
+ surface: CommentSurface;
500
+ postId: string;
501
+ body: string;
502
+ createdAt: string;
503
+ authorName: string;
504
+ }
505
+ export interface ReaderClaimsResult {
506
+ comments: ReaderClaimCandidate[];
507
+ hasMore: boolean;
508
+ }
509
+ export interface ReaderClaimsInput {
510
+ commentIds: string[];
511
+ }
512
+ export interface ReaderClaimResult {
513
+ claimedIds: string[];
514
+ }
515
+ /** Optional client reports are operational observations, never identity evidence. */
516
+ export interface CommentTelemetryInput {
517
+ kind: 'comment' | 'reaction';
518
+ outcome: 'accepted' | 'http_error' | 'network_error' | 'challenge_failed';
519
+ challenges: number;
520
+ }
package/dist/comments.js CHANGED
@@ -18,7 +18,8 @@ var MODERATION_REASONS = [
18
18
  "promotional",
19
19
  "abuse",
20
20
  "off_topic",
21
- "personal_info"
21
+ "personal_info",
22
+ "dwell_expired"
22
23
  ];
23
24
  var READER_VERIFY_OUTCOMES = ["confirmed", "already_confirmed", "expired", "invalid"];
24
25
  var READER_MUTE_OUTCOMES = ["muted", "unmuted", "invalid"];
package/dist/index.js CHANGED
@@ -10,7 +10,6 @@ var BLOG_ANALYTICS_EVENTS_DEFAULT_LIMIT = 50;
10
10
  var BLOG_ANALYTICS_READ_THRESHOLD_MS = 5000;
11
11
  var BLOG_ANALYTICS_COMPLETION_SCROLL_DEPTH = 0.9;
12
12
  var BLOG_ANALYTICS_RANGE_OPTIONS = [7, 30, 90];
13
-
14
13
  // src/comments.ts
15
14
  var READER_PROVIDERS = ["email", "github", "google"];
16
15
  var COMMENT_LOCALES = ["zh", "en"];
@@ -31,7 +30,8 @@ var MODERATION_REASONS = [
31
30
  "promotional",
32
31
  "abuse",
33
32
  "off_topic",
34
- "personal_info"
33
+ "personal_info",
34
+ "dwell_expired"
35
35
  ];
36
36
  var READER_VERIFY_OUTCOMES = ["confirmed", "already_confirmed", "expired", "invalid"];
37
37
  var READER_MUTE_OUTCOMES = ["muted", "unmuted", "invalid"];
@@ -73,7 +73,6 @@ function commentPolicyFromTags(tags, base = DEFAULT_COMMENT_POLICY) {
73
73
  return policy;
74
74
  }
75
75
  var acceptsComments = (policy) => policy.mode === "open";
76
-
77
76
  // src/content.ts
78
77
  var CONTENT_DOCUMENT_SOURCES = ["mood", "post"];
79
78
  var POST_LOCALE_RE = /^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/;
@@ -99,15 +98,12 @@ var MESSAGE_MIN_BODY_LENGTH = 2;
99
98
  var MESSAGE_MAX_BODY_LENGTH = 4000;
100
99
  var MESSAGE_MAX_NAME_LENGTH = 32;
101
100
  var MESSAGE_STATES = ["new", "read", "replied", "archived", "spam"];
102
-
103
101
  // src/mood.ts
104
102
  var MOOD_SENTIMENT_LABELS = ["joy", "calm", "melancholy", "anger", "anxiety", "neutral"];
105
103
  var MOOD_AI_MODELS = ["gpt-5.5", "gpt-5", "claude-sonnet-4.6"];
106
104
  var MOOD_COMMENT_ORIGINS = ["telegram", "web"];
107
-
108
105
  // src/notify.ts
109
106
  var NOTIFY_CHANNELS = ["mood", "blog", "privacy", "announcement"];
110
-
111
107
  // src/routes.ts
112
108
  var API_PREFIX = "/api";
113
109
  var HEALTH_PATH = "/health";
@@ -147,7 +143,6 @@ var LEGACY_NOTIFY_BASE_PATH = "/v2/notify";
147
143
  var LEGACY_GHOST_WEBHOOK_PATH = "/v2/ghost/webhook";
148
144
  var LEGACY_MUSICKIT_TOKEN_PATH = "/v2/musickit/token";
149
145
  var LEGACY_HEALTH_PATH = "/v2/health";
150
-
151
146
  // src/telegram-ops.ts
152
147
  var TELEGRAM_OPS_WEBHOOK_PATH = "/webhooks/telegram-ops";
153
148
  var TELEGRAM_OPS_COMMENT_CALLBACK_PREFIXES = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunizao/contracts",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Shared TypeScript contracts for buxx.me services and clients.",
5
5
  "type": "module",
6
6
  "sideEffects": false,