@buildinternet/releases-api-types 0.52.1 → 0.53.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/releases-api-types",
3
- "version": "0.52.1",
3
+ "version": "0.53.0",
4
4
  "description": "Wire protocol — request/response types for the Releases registry API. Shared by web, workers, and clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,7 +17,7 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "@buildinternet/releases-core": "^0.30.0",
20
+ "@buildinternet/releases-core": "^0.31.0",
21
21
  "zod": "^4.4.3"
22
22
  },
23
23
  "publishConfig": {
package/src/api-types.ts CHANGED
@@ -21,6 +21,11 @@ import type {
21
21
  OpenRouterCatalogModelSchema,
22
22
  AiLaneModelsResponseSchema,
23
23
  } from "./schemas/ai-models.js";
24
+ import type {
25
+ MarketingClassifierThresholdPutSchema,
26
+ MarketingFilteredSourceSchema,
27
+ MarketingClassifierStateSchema,
28
+ } from "./schemas/marketing-classifier.js";
24
29
  import type {
25
30
  MediaItemSchema,
26
31
  PaginationSchema,
@@ -221,6 +226,7 @@ import type {
221
226
  ClassificationRecentResponseSchema,
222
227
  ClassificationRecentItemSchema,
223
228
  } from "./schemas/classifications.js";
229
+ import type { SemanticAlertSummarySchema } from "./schemas/semantic-alert-summary.js";
224
230
  import type {
225
231
  RecommendationNotifyAddedBodySchema,
226
232
  RecommendationNotifyAddedResultSchema,
@@ -309,6 +315,13 @@ export {
309
315
  OpenRouterCatalogModelSchema,
310
316
  AiLaneModelsResponseSchema,
311
317
  } from "./schemas/ai-models.js";
318
+ export {
319
+ MarketingClassifierThresholdPutSchema,
320
+ MarketingFilteredSourceSchema,
321
+ MarketingClassifierStateSchema,
322
+ MARKETING_THRESHOLD_MIN,
323
+ MARKETING_THRESHOLD_MAX,
324
+ } from "./schemas/marketing-classifier.js";
312
325
  export {
313
326
  OrgListItemSchema,
314
327
  OrgListResponseSchema,
@@ -561,6 +574,7 @@ export {
561
574
  ClassificationRecentResponseSchema,
562
575
  ClassificationRecentItemSchema,
563
576
  } from "./schemas/classifications.js";
577
+ export { SemanticAlertSummarySchema } from "./schemas/semantic-alert-summary.js";
564
578
  export {
565
579
  RecommendationNotifyAddedBodySchema,
566
580
  RecommendationNotifyAddedResultSchema,
@@ -639,6 +653,10 @@ export type AiLaneState = z.infer<typeof AiLaneStateSchema>;
639
653
  export type OpenRouterCatalogModel = z.infer<typeof OpenRouterCatalogModelSchema>;
640
654
  export type AiLaneModelsResponse = z.infer<typeof AiLaneModelsResponseSchema>;
641
655
 
656
+ export type MarketingClassifierThresholdPut = z.infer<typeof MarketingClassifierThresholdPutSchema>;
657
+ export type MarketingFilteredSource = z.infer<typeof MarketingFilteredSourceSchema>;
658
+ export type MarketingClassifierState = z.infer<typeof MarketingClassifierStateSchema>;
659
+
642
660
  // ── Stub tier (#1947) ──
643
661
 
644
662
  export type OrgStatus = z.infer<typeof OrgStatusSchema>;
@@ -1250,6 +1268,46 @@ export interface TestUserWebhookResponse {
1250
1268
  eventId: string;
1251
1269
  }
1252
1270
 
1271
+ // ── Workspace webhooks ──
1272
+
1273
+ /** Workspace member role (Better Auth `member.role`, not `user.role`). */
1274
+ export type WorkspaceMemberRole = "owner" | "admin" | "member";
1275
+
1276
+ /**
1277
+ * A workspace-owned webhook subscription row (no signing secret). Workspace
1278
+ * webhooks are org-scoped only; there is no workspace follows scope.
1279
+ */
1280
+ export interface WorkspaceWebhookSubscription extends Omit<
1281
+ UserWebhookSubscription,
1282
+ "userId" | "scope"
1283
+ > {
1284
+ workspaceId: string;
1285
+ scope: "org";
1286
+ }
1287
+
1288
+ /** List item returned by GET /v1/workspaces/:workspaceId/webhooks. */
1289
+ export interface WorkspaceWebhookListItem extends Omit<UserWebhookListItem, "scope"> {
1290
+ workspaceId: string;
1291
+ scope: "org";
1292
+ }
1293
+
1294
+ /** GET /v1/workspaces/:workspaceId/webhooks response. */
1295
+ export interface WorkspaceWebhookListResponse {
1296
+ subscriptions: WorkspaceWebhookListItem[];
1297
+ /** The caller's role in the workspace. */
1298
+ role: WorkspaceMemberRole;
1299
+ /** True for owners and admins: create, edit, rotate, and delete. Members can view and test. */
1300
+ canManage: boolean;
1301
+ }
1302
+
1303
+ /** POST /v1/workspaces/:workspaceId/webhooks response — signing key shown once (omitted for slack/discord). */
1304
+ export interface CreateWorkspaceWebhookResponse
1305
+ extends WorkspaceWebhookSubscription, UserWebhookDeliveryHealth {
1306
+ orgSlug: string | null;
1307
+ orgName: string | null;
1308
+ signingKey?: string;
1309
+ }
1310
+
1253
1311
  /** One Analytics Engine delivery-attempt row from GET …/webhooks/:id/deliveries. */
1254
1312
  export interface WebhookDeliveryRow {
1255
1313
  timestamp?: string;
@@ -1279,15 +1337,88 @@ export interface DigestPrefsRequest {
1279
1337
  cadence: DigestCadence;
1280
1338
  }
1281
1339
 
1340
+ // ── Semantic alerts (#2304 Phase 1) ──
1341
+
1342
+ /** Hard cap on saved semantic alerts per account. Enforced on create. */
1343
+ export const SEMANTIC_ALERT_MAX_PER_USER = 5;
1344
+
1345
+ /** Freeform interest text, after trim. User-private — never an analytics label. */
1346
+ export const SEMANTIC_ALERT_QUERY_MAX_CHARS = 500;
1347
+
1348
+ /** Default selected-choice probability. Phase 2 matcher uses this unless overridden. */
1349
+ export const SEMANTIC_ALERT_THRESHOLD_DEFAULT = 0.8;
1350
+
1351
+ export const SEMANTIC_ALERT_THRESHOLD_MIN = 0.5;
1352
+ export const SEMANTIC_ALERT_THRESHOLD_MAX = 1;
1353
+
1354
+ /**
1355
+ * v1 candidate pool. Matching (Phase 2) scores only releases that already hit
1356
+ * the caller's follow graph. Not a per-alert setting.
1357
+ */
1358
+ export const SEMANTIC_ALERT_CANDIDATE_POOL = "follows" as const;
1359
+
1360
+ /** One saved semantic alert. Query text is returned only to the owning user. */
1361
+ export interface SemanticAlert {
1362
+ id: string;
1363
+ query: string;
1364
+ enabled: boolean;
1365
+ /** Inclusive match probability in [0.50, 1.00]. Default 0.80. */
1366
+ threshold: number;
1367
+ /** Stored preference. Phase 1 does not send email. */
1368
+ deliverEmail: boolean;
1369
+ /** Stored preference. Phase 1 does not send webhook deliveries. */
1370
+ deliverWebhook: boolean;
1371
+ /** Optional link to one of the caller's `/v1/me/webhooks` subscriptions. */
1372
+ webhookSubscriptionId: string | null;
1373
+ createdAt: string;
1374
+ updatedAt: string;
1375
+ }
1376
+
1377
+ /**
1378
+ * Claimed-match activity for one alert (#2320). Counts are rows in
1379
+ * `semantic_alert_matches` (notified matches only — below-threshold scores
1380
+ * are not stored). Present on list and notifications-bootstrap rows.
1381
+ */
1382
+ export interface SemanticAlertActivity {
1383
+ /** Matches in the last 7 days. Included in `matches30d`. */
1384
+ matches7d: number;
1385
+ /** Matches in the last 30 days. */
1386
+ matches30d: number;
1387
+ /** Newest claim, or null when the alert has never matched. */
1388
+ lastMatchedAt: string | null;
1389
+ /** Newest claim's release, when that row still exists. */
1390
+ lastMatch: {
1391
+ releaseId: string;
1392
+ title: string;
1393
+ /** Canonical site path, `/release/rel_…`. */
1394
+ path: string;
1395
+ } | null;
1396
+ }
1397
+
1398
+ /** A list row: the saved alert plus recent match activity. */
1399
+ export interface SemanticAlertListItem extends SemanticAlert {
1400
+ activity: SemanticAlertActivity;
1401
+ }
1402
+
1403
+ /** GET /v1/me/semantic-alerts. `candidatePool` is the locked v1 scope. */
1404
+ export interface SemanticAlertListResponse {
1405
+ alerts: SemanticAlertListItem[];
1406
+ candidatePool: typeof SEMANTIC_ALERT_CANDIDATE_POOL;
1407
+ maxAlerts: number;
1408
+ }
1409
+
1282
1410
  /**
1283
1411
  * GET /v1/me/settings/notifications — one-shot bootstrap for `/account/notifications`.
1284
1412
  * Composes digest cadence + feed token + webhook list so the settings page can
1285
1413
  * render with a single round-trip instead of three independent mounts.
1414
+ * `semanticAlerts` is `null` when `semantic-alerts-enabled` is off (hide the
1415
+ * section); an array, possibly empty, when the lane is on.
1286
1416
  */
1287
1417
  export interface NotificationSettingsResponse {
1288
1418
  cadence: DigestCadence;
1289
1419
  feedToken: FeedToken | null;
1290
1420
  webhooks: UserWebhookListItem[];
1421
+ semanticAlerts: SemanticAlertListItem[] | null;
1291
1422
  }
1292
1423
 
1293
1424
  /**
@@ -1300,6 +1431,24 @@ export interface DeveloperSettingsResponse {
1300
1431
  apiKeys: UserApiKey[] | null;
1301
1432
  }
1302
1433
 
1434
+ /** GET /v1/me/workspaces — one row per workspace the caller belongs to. */
1435
+ export interface MeWorkspace {
1436
+ /** Better Auth organization id. */
1437
+ id: string;
1438
+ name: string;
1439
+ slug: string;
1440
+ logo: string | null;
1441
+ /** Workspace membership role (`member.role`, not `user.role`). */
1442
+ role: WorkspaceMemberRole;
1443
+ /** The caller's active workspace (see docs/architecture/workspaces.md). */
1444
+ active: boolean;
1445
+ createdAt: string;
1446
+ }
1447
+
1448
+ export interface MeWorkspacesResponse {
1449
+ workspaces: MeWorkspace[];
1450
+ }
1451
+
1303
1452
  // ── Search ──
1304
1453
 
1305
1454
  export type SearchOrgHit = z.infer<typeof SearchOrgHitSchema>;
@@ -1770,6 +1919,8 @@ export type ClassificationSummary = z.infer<typeof ClassificationSummarySchema>;
1770
1919
  export type ClassificationRecentResponse = z.infer<typeof ClassificationRecentResponseSchema>;
1771
1920
  export type ClassificationRecentItem = z.infer<typeof ClassificationRecentItemSchema>;
1772
1921
 
1922
+ export type SemanticAlertSummary = z.infer<typeof SemanticAlertSummarySchema>;
1923
+
1773
1924
  // ── Recommendations ──
1774
1925
  export type RecommendationNotifyAddedBody = z.infer<typeof RecommendationNotifyAddedBodySchema>;
1775
1926
  export type RecommendationNotifyAddedResult = z.infer<typeof RecommendationNotifyAddedResultSchema>;
@@ -11,7 +11,8 @@ const ClassificationOriginFilterSchema = z.enum(["ingest", "manual", "eval", "al
11
11
  const ClassificationDispositionSchema = z.enum(["kept", "suppressed", "failed", "skipped"]);
12
12
 
13
13
  const ProbabilityBinSchema = z.object({
14
- /** Inclusive. Width is 0.1. `0.8` is the start of the threshold bin. */
14
+ /** Inclusive. Width is 0.1. See `probability.threshold` for the current
15
+ * effective suppression threshold and which bin it falls in. */
15
16
  start: z.number(),
16
17
  /** Exclusive, except the last bin (`0.9`–`1`) which includes `1`. */
17
18
  end: z.number(),
@@ -65,7 +66,10 @@ export const ClassificationSummarySchema = z.object({
65
66
  }),
66
67
  ),
67
68
  probability: z.object({
68
- threshold: z.literal(0.8),
69
+ /** Current effective suppression threshold (operator override, else the
70
+ * code default) — no longer a fixed literal now that it's editable via
71
+ * `GET/PUT /v1/admin/marketing-classifier`. */
72
+ threshold: z.number(),
69
73
  selected: ProbabilityHistogramSchema,
70
74
  confidence: ProbabilityHistogramSchema,
71
75
  }),
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+
3
+ export const MARKETING_THRESHOLD_MIN = 0.5;
4
+ export const MARKETING_THRESHOLD_MAX = 0.99;
5
+
6
+ /** PUT /v1/admin/marketing-classifier — set the suppression threshold. */
7
+ export const MarketingClassifierThresholdPutSchema = z
8
+ .object({
9
+ threshold: z.number().min(MARKETING_THRESHOLD_MIN).max(MARKETING_THRESHOLD_MAX),
10
+ })
11
+ .strict();
12
+
13
+ export const MarketingFilteredSourceSchema = z.object({
14
+ id: z.string(),
15
+ slug: z.string(),
16
+ name: z.string(),
17
+ type: z.string(),
18
+ orgSlug: z.string().nullable(),
19
+ hint: z.string().nullable(),
20
+ recentReleaseCount: z.number(),
21
+ });
22
+
23
+ /** GET/PUT /v1/admin/marketing-classifier response. */
24
+ export const MarketingClassifierStateSchema = z.object({
25
+ threshold: z.number(),
26
+ defaultThreshold: z.number(),
27
+ updatedAt: z.string().nullable(),
28
+ sources: z.array(MarketingFilteredSourceSchema),
29
+ });
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Admin interest-alert quality (`GET /v1/admin/semantic-alerts/summary`).
5
+ * Decisions live in the classifications Analytics Engine dataset with
6
+ * `blob4 = semantic-alert`. This is the hydrated read model. Alert query
7
+ * text is not a field — it is not stored on the points.
8
+ */
9
+
10
+ const ProbabilityBinSchema = z.object({
11
+ /** Inclusive. Width is 0.1. `0.8` is the start of the default-threshold bin. */
12
+ start: z.number(),
13
+ /** Exclusive, except the last bin (`0.9`–`1`) which includes `1`. */
14
+ end: z.number(),
15
+ count: z.number(),
16
+ });
17
+
18
+ const SemanticAlertDispositionCountsSchema = z.object({
19
+ matched: z.number(),
20
+ belowThreshold: z.number(),
21
+ failed: z.number(),
22
+ });
23
+
24
+ /**
25
+ * `GET /v1/admin/semantic-alerts/summary`.
26
+ *
27
+ * `scored` is matched + below threshold (a probability came back).
28
+ * `matchRate` is matched / scored, or null when nothing was scored.
29
+ * `failed` is fail-closed and stays out of the rate.
30
+ * Cost is not on these points; `meta.costLane` names the `ai_usage` lane.
31
+ */
32
+ export const SemanticAlertSummarySchema = z.object({
33
+ after: z.string(),
34
+ before: z.string(),
35
+ bucket: z.enum(["hour", "day"]),
36
+ totals: SemanticAlertDispositionCountsSchema.extend({
37
+ scored: z.number(),
38
+ matchRate: z.number().nullable(),
39
+ }),
40
+ series: z.array(
41
+ SemanticAlertDispositionCountsSchema.extend({
42
+ t: z.string(),
43
+ }),
44
+ ),
45
+ failures: z.array(
46
+ z.object({
47
+ category: z.enum(["provider_error", "invalid_probability", "unknown"]),
48
+ count: z.number(),
49
+ }),
50
+ ),
51
+ probability: z.object({
52
+ /** Default alert threshold. Each point stores its own threshold separately. */
53
+ defaultThreshold: z.literal(0.8),
54
+ bins: z.array(ProbabilityBinSchema),
55
+ /** Sampled rows whose probability double is `< 0` (the absent sentinel). */
56
+ missing: z.number(),
57
+ }),
58
+ meta: z.object({
59
+ dataset: z.string(),
60
+ retentionDays: z.literal(90),
61
+ sampled: z.literal(true),
62
+ /** `ai_usage` lane that carries JEV cost. Not summed from these points. */
63
+ costLane: z.literal("semantic-alert-match"),
64
+ }),
65
+ });