@bunizao/contracts 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,6 +301,47 @@ 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[];
@@ -308,6 +353,13 @@ export interface AdminSourceProfile {
308
353
  type: AdminSourceKeyType;
309
354
  value: string;
310
355
  };
356
+ identitySummary?: {
357
+ verifiedAccounts: number;
358
+ anonymousSessions: number;
359
+ claimedComments: number;
360
+ sharedStorage: boolean;
361
+ sharedFingerprint: boolean;
362
+ };
311
363
  firstSeenAt: string | null;
312
364
  lastSeenAt: string | null;
313
365
  comments: {
@@ -327,9 +379,7 @@ export interface AdminSourceProfile {
327
379
  /** Newest 50. */
328
380
  rows: AdminReactionRecord[];
329
381
  };
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. */
382
+ /** Distinct observed values. Shared environments do not establish a person. */
333
383
  spread: Record<'session' | 'ip' | 'ip24' | 'fp' | 'clientFp' | 'clientFpStable' | 'storageId' | 'email' | 'asn' | 'ua', number>;
334
384
  /** Every bot and vpn hint this source has tripped, with how often. */
335
385
  hints: Array<{
@@ -337,9 +387,8 @@ export interface AdminSourceProfile {
337
387
  kind: 'bot' | 'vpn';
338
388
  count: number;
339
389
  }>;
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. */
390
+ /** Links describe verified-at-write account evidence or shared storage.
391
+ clientFpStable remains zero for compatibility; it never creates links. */
343
392
  linked: {
344
393
  sessions: number;
345
394
  via: Record<'email' | 'clientFpStable' | 'storageId', number>;
@@ -374,12 +423,52 @@ export interface AdminInsightRow {
374
423
  }
375
424
  export type AdminCommentInsightsWindow = '7d' | '30d' | '90d';
376
425
  export type AdminReactionInsightsWindow = '48h' | '7d';
426
+ export interface AdminCommentQuality {
427
+ since: string;
428
+ collectedSince: string | null;
429
+ available: {
430
+ moderation: boolean;
431
+ requests: boolean;
432
+ clientReports: boolean;
433
+ };
434
+ /** A cohort of automatically held comments and the first later owner decision. */
435
+ moderation: {
436
+ held: number;
437
+ reviewed: number;
438
+ released: number;
439
+ releasedShare: number | null;
440
+ };
441
+ /** Request outcomes include retries. Authenticated does not mean known benign. */
442
+ requests: {
443
+ attempts: number;
444
+ failures: number;
445
+ failureShare: number | null;
446
+ authenticatedAttempts: number;
447
+ authenticatedFailures: number;
448
+ authenticatedFailureShare: number | null;
449
+ outcomes: Array<{
450
+ kind: 'comment' | 'reaction';
451
+ outcome: string;
452
+ count: number;
453
+ }>;
454
+ };
455
+ /** Self-reported and incomplete when the browser cannot deliver telemetry. */
456
+ clientReports: {
457
+ reports: number;
458
+ failures: number;
459
+ networkFailures: number;
460
+ challengedAttempts: number;
461
+ repeatedChallenges: number;
462
+ untrusted: true;
463
+ };
464
+ }
377
465
  /** `GET /admin/comments/insights?window=`. Every table carries a held rate,
378
466
  because a source is only interesting relative to how the automatic pass
379
467
  treats it. NULL groups show as their own row, never dropped. */
380
468
  export interface AdminCommentInsights {
381
469
  window: AdminCommentInsightsWindow;
382
470
  since: string;
471
+ quality?: AdminCommentQuality;
383
472
  networks: Array<AdminInsightRow & {
384
473
  asn: number | null;
385
474
  asOrg: string | null;
@@ -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/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"];
@@ -73,7 +72,6 @@ function commentPolicyFromTags(tags, base = DEFAULT_COMMENT_POLICY) {
73
72
  return policy;
74
73
  }
75
74
  var acceptsComments = (policy) => policy.mode === "open";
76
-
77
75
  // src/content.ts
78
76
  var CONTENT_DOCUMENT_SOURCES = ["mood", "post"];
79
77
  var POST_LOCALE_RE = /^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/;
@@ -99,15 +97,12 @@ var MESSAGE_MIN_BODY_LENGTH = 2;
99
97
  var MESSAGE_MAX_BODY_LENGTH = 4000;
100
98
  var MESSAGE_MAX_NAME_LENGTH = 32;
101
99
  var MESSAGE_STATES = ["new", "read", "replied", "archived", "spam"];
102
-
103
100
  // src/mood.ts
104
101
  var MOOD_SENTIMENT_LABELS = ["joy", "calm", "melancholy", "anger", "anxiety", "neutral"];
105
102
  var MOOD_AI_MODELS = ["gpt-5.5", "gpt-5", "claude-sonnet-4.6"];
106
103
  var MOOD_COMMENT_ORIGINS = ["telegram", "web"];
107
-
108
104
  // src/notify.ts
109
105
  var NOTIFY_CHANNELS = ["mood", "blog", "privacy", "announcement"];
110
-
111
106
  // src/routes.ts
112
107
  var API_PREFIX = "/api";
113
108
  var HEALTH_PATH = "/health";
@@ -147,7 +142,6 @@ var LEGACY_NOTIFY_BASE_PATH = "/v2/notify";
147
142
  var LEGACY_GHOST_WEBHOOK_PATH = "/v2/ghost/webhook";
148
143
  var LEGACY_MUSICKIT_TOKEN_PATH = "/v2/musickit/token";
149
144
  var LEGACY_HEALTH_PATH = "/v2/health";
150
-
151
145
  // src/telegram-ops.ts
152
146
  var TELEGRAM_OPS_WEBHOOK_PATH = "/webhooks/telegram-ops";
153
147
  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.0",
4
4
  "description": "Shared TypeScript contracts for buxx.me services and clients.",
5
5
  "type": "module",
6
6
  "sideEffects": false,