rivet-design 0.28.1 → 0.28.3

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 (89) hide show
  1. package/dist/cli/commands/libraryOpen.js +1 -0
  2. package/dist/cli/commands/mcpServe.js +5 -1
  3. package/dist/cli/commands/open.js +17 -6
  4. package/dist/cli/commands/profile.js +8 -1
  5. package/dist/cli/commands/profileDisconnect.d.ts +3 -0
  6. package/dist/cli/commands/profileDisconnect.js +34 -0
  7. package/dist/cli/commands/serve.js +2 -0
  8. package/dist/cli/commands/shared.js +4 -1
  9. package/dist/cli/envelope.d.ts +1 -0
  10. package/dist/cli/router.js +8 -2
  11. package/dist/jest.globalSetup.d.ts +3 -0
  12. package/dist/jest.globalSetup.js +19 -0
  13. package/dist/library/browserLaunch.d.ts +2 -1
  14. package/dist/library/browserLaunch.js +3 -2
  15. package/dist/proxy-middleware/proxy-config.d.ts +0 -1
  16. package/dist/proxy-middleware/proxy-config.js +6 -34
  17. package/dist/routes/agentVariants.js +20 -173
  18. package/dist/routes/referenceProfiles.js +3 -3
  19. package/dist/routes/static.js +8 -2
  20. package/dist/server.d.ts +1 -1
  21. package/dist/server.js +15 -26
  22. package/dist/services/AuthService.js +7 -61
  23. package/dist/services/IntegrationsClient.d.ts +2 -1
  24. package/dist/services/IntegrationsClient.js +46 -20
  25. package/dist/services/TelemetryService.d.ts +16 -2
  26. package/dist/services/TelemetryService.js +18 -0
  27. package/dist/services/accessTokenRefresh.js +2 -2
  28. package/dist/services/profiles/DesignProfiles.d.ts +16 -0
  29. package/dist/services/profiles/DesignProfiles.js +78 -17
  30. package/dist/services/profiles/ProfileAnalysis.d.ts +3 -0
  31. package/dist/services/profiles/ProfileAnalysis.js +66 -1
  32. package/dist/services/profiles/ProfileImports.d.ts +2 -0
  33. package/dist/services/profiles/ProfileImports.js +2 -0
  34. package/dist/services/templates/viteReactTs.js +1 -1
  35. package/dist/storage/profiles/ProfileRecords.d.ts +17 -0
  36. package/dist/storage/profiles/ProfileRecords.js +45 -7
  37. package/dist/storage/profiles/migrations.d.ts +2 -2
  38. package/dist/storage/profiles/migrations.js +7 -0
  39. package/dist/storage/profiles/schema.d.ts +48 -0
  40. package/dist/storage/profiles/schema.js +3 -0
  41. package/dist/types/api-responses.d.ts +39 -0
  42. package/dist/types/api-responses.js +12 -1
  43. package/dist/types/design-profile.d.ts +12 -0
  44. package/dist/types/design-profile.js +10 -1
  45. package/dist/types/rivet-page-types.d.ts +48 -0
  46. package/dist/types/rivet-page-types.js +6 -0
  47. package/dist/utils/failureReason.js +1 -1
  48. package/dist/utils/hostedLogPrivacy.js +1 -1
  49. package/dist/utils/launchRuntime.d.ts +2 -0
  50. package/dist/utils/launchRuntime.js +12 -0
  51. package/dist/utils/rivetPage.d.ts +8 -0
  52. package/dist/utils/rivetPage.js +24 -0
  53. package/dist/utils/rivetPage.testHelpers.d.ts +6 -0
  54. package/dist/utils/rivetPage.testHelpers.js +18 -0
  55. package/dist/utils/rivetPageDocument.d.ts +7 -0
  56. package/dist/utils/rivetPageDocument.js +41 -0
  57. package/dist/utils/scriptJson.d.ts +2 -0
  58. package/dist/utils/scriptJson.js +9 -0
  59. package/dist/utils/skills/cli-guidance.d.ts +1 -1
  60. package/dist/utils/skills/cli-guidance.js +2 -1
  61. package/dist/utils/uiDistPath.d.ts +2 -0
  62. package/dist/utils/uiDistPath.js +22 -0
  63. package/package.json +8 -5
  64. package/src/ui/dist/assets/LibraryEntry-CSU5OJwC.js +2 -0
  65. package/src/ui/dist/assets/google-g-CpxN0HWb.png +0 -0
  66. package/src/ui/dist/assets/main-B89lcZEY.css +1 -0
  67. package/src/ui/dist/assets/main-DeCxjHC1.js +204 -0
  68. package/src/ui/dist/index.html +3 -3
  69. package/src/ui/dist/pages/empty-project.html +53 -0
  70. package/src/ui/dist/pages/rivet-page.html +53 -0
  71. package/dist/routes/emptyStatePage.d.ts +0 -2
  72. package/dist/routes/emptyStatePage.js +0 -589
  73. package/src/ui/dist/assets/LibraryEntry-DS6Ce0sW.js +0 -112
  74. package/src/ui/dist/assets/logo-animation/clasp.png +0 -0
  75. package/src/ui/dist/assets/logo-animation/eraser.png +0 -0
  76. package/src/ui/dist/assets/logo-animation/playdough.png +0 -0
  77. package/src/ui/dist/assets/logo-animation/sea/bubblephone.png +0 -0
  78. package/src/ui/dist/assets/logo-animation/sea/cart.png +0 -0
  79. package/src/ui/dist/assets/logo-animation/sea/chair.png +0 -0
  80. package/src/ui/dist/assets/logo-animation/sea/desk.png +0 -0
  81. package/src/ui/dist/assets/logo-animation/sea/mug.png +0 -0
  82. package/src/ui/dist/assets/logo-animation/sea/pitcher.png +0 -0
  83. package/src/ui/dist/assets/logo-animation/sea/pot.png +0 -0
  84. package/src/ui/dist/assets/logo-animation/sea/startac.png +0 -0
  85. package/src/ui/dist/assets/logo-animation/sea/terracotta-1.png +0 -0
  86. package/src/ui/dist/assets/logo-animation/sea/terracotta-2.png +0 -0
  87. package/src/ui/dist/assets/logo-animation/sea/vhsplant.png +0 -0
  88. package/src/ui/dist/assets/main-CQSk-6km.css +0 -1
  89. package/src/ui/dist/assets/main-DkKRuatC.js +0 -218
@@ -41,8 +41,16 @@ const ProviderRateLimit_1 = require("../../utils/ProviderRateLimit");
41
41
  const design_profile_3 = require("../../types/design-profile");
42
42
  const errorMessage_1 = require("../../utils/errorMessage");
43
43
  const APPROVAL_TIMEOUT_MS = 45_000;
44
+ const MS_PER_HOUR = 3_600_000;
44
45
  const SAVED_REFERENCE_IMAGE_LIMIT = 20;
45
46
  const VERIFICATION_TARGET_INSTRUCTIONS = 'Choose tools for the actual browser, simulator, or other target. Do not assume a URL, DOM, CSS, or Playwright. If required tools or the target are unavailable, report unable to verify.';
47
+ const preparationOffReason = ({ mode, isEnabled, }) => {
48
+ if (mode === 'off')
49
+ return 'request_mode_off';
50
+ if (!isEnabled)
51
+ return 'profile_disabled';
52
+ return 'no_uploads';
53
+ };
46
54
  const librarySources = (records) => records.sources().map(({ id, title, provider, isIncluded }) => ({
47
55
  id,
48
56
  title,
@@ -544,21 +552,25 @@ class DesignProfiles {
544
552
  async importReferences(input) {
545
553
  const request = reference_onboarding_1.ReferenceApprovalSchema.parse(input);
546
554
  const accountId = this.options.accountId();
555
+ if (!accountId)
556
+ throw new Error('Sign in to Rivet to connect design references');
557
+ const context = await this.context();
558
+ const wasEnabled = context.records.profile().isEnabled;
547
559
  const plan = await this.withReferenceRecords(async (records, currentAccountId) => {
548
560
  const pending = records.plans.plan(request.id);
549
561
  if (!pending.approvedAt)
550
562
  await this.planning(records, currentAccountId).verifyAccount(pending.profile, AbortSignal.timeout(APPROVAL_TIMEOUT_MS));
551
563
  return records.approvePlan(request);
552
564
  });
553
- if (accountId !== this.options.accountId())
554
- throw new Error('Profile account changed during import approval');
555
- const context = await this.context();
556
565
  if (context.accountId !== accountId ||
557
566
  accountId !== this.options.accountId())
558
567
  throw new Error('Profile account changed during import approval');
559
- if (context.records.profile().isEnabled)
568
+ if (context.records.profile().isEnabled) {
569
+ if (!wasEnabled)
570
+ this.resume(context, plan.jobIds);
560
571
  for (const id of plan.jobIds)
561
- this.enqueue(context, () => this.runImport(context, id));
572
+ this.enqueue(context, () => this.runImport(context, id, 'connect'));
573
+ }
562
574
  return (0, reference_onboarding_1.referencePlanSummary)(plan);
563
575
  }
564
576
  planning(records, accountId) {
@@ -668,12 +680,15 @@ class DesignProfiles {
668
680
  await this.flush(context, job.runtime);
669
681
  context.records.completeRebuild(job.id);
670
682
  context.issue = null;
683
+ const sources = context.records.sources();
671
684
  this.options.telemetry?.trackProfileOperation({
672
685
  stage: 'maintenance',
673
686
  operation: 'index_rebuild',
674
687
  outcome: 'completed',
675
688
  durationMs: Date.now() - started,
676
689
  referenceCount: context.records.coverage().indexed,
690
+ providerCount: new Set(sources.map((source) => source.provider)).size,
691
+ sourceCount: sources.length,
677
692
  });
678
693
  }
679
694
  catch (error) {
@@ -682,6 +697,7 @@ class DesignProfiles {
682
697
  operation: 'index_rebuild',
683
698
  outcome: 'failed',
684
699
  durationMs: Date.now() - started,
700
+ error,
685
701
  });
686
702
  throw error;
687
703
  }
@@ -763,7 +779,7 @@ class DesignProfiles {
763
779
  isComplete: true,
764
780
  total: new Set(references.map((reference) => reference.remoteId)).size,
765
781
  });
766
- this.enqueue(context, () => this.runImport(context, job.id));
782
+ this.enqueue(context, () => this.runImport(context, job.id, 'upload'));
767
783
  return { source, jobId: job.id };
768
784
  }
769
785
  async pins(projectId) {
@@ -797,7 +813,7 @@ class DesignProfiles {
797
813
  },
798
814
  runtime: value.runtime,
799
815
  });
800
- this.enqueue(context, () => this.runImport(context, result.job.id));
816
+ this.enqueue(context, () => this.runImport(context, result.job.id, 'upload'));
801
817
  return { source: result.source, jobId: result.job.id };
802
818
  }
803
819
  async connect(input) {
@@ -852,7 +868,7 @@ class DesignProfiles {
852
868
  throw new Error('Profile account changed or was disabled during X authorization');
853
869
  }
854
870
  const result = context.imports.connect({ ...input, xAccount });
855
- this.enqueue(context, () => this.runImport(context, result.job.id));
871
+ this.enqueue(context, () => this.runImport(context, result.job.id, 'connect'));
856
872
  return result;
857
873
  }
858
874
  async updateJob({ id, action, }) {
@@ -866,7 +882,7 @@ class DesignProfiles {
866
882
  state: action === 'pause' ? 'paused' : 'pending',
867
883
  });
868
884
  if (action !== 'pause')
869
- this.enqueue(context, () => this.runImport(context, id));
885
+ this.enqueue(context, () => this.runImport(context, id, 'retry'));
870
886
  return this.status();
871
887
  }
872
888
  async refresh({ id, runtime }) {
@@ -880,7 +896,7 @@ class DesignProfiles {
880
896
  if (!(0, design_profile_1.isRemoteProfileProvider)(source.provider))
881
897
  throw new Error('Only connected provider sources support refresh');
882
898
  const job = context.records.startImport({ sourceId: id, runtime });
883
- this.enqueue(context, () => this.runImport(context, job.id));
899
+ this.enqueue(context, () => this.runImport(context, job.id, 'refresh'));
884
900
  return { job };
885
901
  }
886
902
  async updateSource({ id, isIncluded, isRemoved, runtime, }) {
@@ -1152,13 +1168,26 @@ class DesignProfiles {
1152
1168
  failedUploadCount: undefined,
1153
1169
  }
1154
1170
  : packet;
1171
+ const selectedProviders = result.selected.flatMap((item) => context.records.reference(item.id)?.provider ?? []);
1155
1172
  this.options.telemetry?.trackProfileOperation({
1156
1173
  stage: 'preparation',
1157
1174
  operation: 'preparation',
1158
1175
  outcome: 'completed',
1159
1176
  durationMs: result.durationMs,
1160
1177
  referenceCount: result.selected.length,
1178
+ candidateCount: result.candidateIds.length,
1161
1179
  status: result.status,
1180
+ ...(selectedProviders.length
1181
+ ? { providerMix: [...new Set(selectedProviders)].sort() }
1182
+ : {}),
1183
+ ...(result.status === 'off'
1184
+ ? {
1185
+ offReason: preparationOffReason({
1186
+ mode: validated.mode,
1187
+ isEnabled: context.records.profile().isEnabled,
1188
+ }),
1189
+ }
1190
+ : {}),
1162
1191
  });
1163
1192
  return { ...result, profileId: context.records.profile().id, accountId };
1164
1193
  }
@@ -1204,7 +1233,7 @@ class DesignProfiles {
1204
1233
  runtime,
1205
1234
  });
1206
1235
  reference = context.records.references(saved.source.id)[0];
1207
- this.enqueue(context, () => this.runImport(context, saved.job.id));
1236
+ this.enqueue(context, () => this.runImport(context, saved.job.id, 'attachment'));
1208
1237
  }
1209
1238
  if (!reference)
1210
1239
  throw new Error('Saved image reference is unavailable');
@@ -1292,13 +1321,14 @@ class DesignProfiles {
1292
1321
  media,
1293
1322
  agent,
1294
1323
  isCurrentAccount: () => this.options.accountId() === accountId,
1324
+ telemetry: this.options.telemetry,
1295
1325
  }),
1296
1326
  schedule: new ProfileImportSchedule_1.ProfileImportSchedule({
1297
1327
  records,
1298
1328
  isActive: () => this.current === context &&
1299
1329
  !context.abort.signal.aborted &&
1300
1330
  this.options.accountId() === accountId,
1301
- onReady: (id) => this.enqueue(context, () => this.runImport(context, id)),
1331
+ onReady: (id) => this.enqueue(context, () => this.runImport(context, id, 'cooldown_retry')),
1302
1332
  }),
1303
1333
  abort: new AbortController(),
1304
1334
  work: Promise.resolve(),
@@ -1316,15 +1346,15 @@ class DesignProfiles {
1316
1346
  this.opening = undefined;
1317
1347
  }
1318
1348
  }
1319
- resume(context) {
1349
+ resume(context, claimedJobIds = []) {
1320
1350
  this.enqueue(context, async () => {
1321
1351
  await context.memory.start();
1322
1352
  context.issue = null;
1323
1353
  });
1324
1354
  for (const job of context.records
1325
1355
  .jobs()
1326
- .filter((job) => job.state === 'pending'))
1327
- this.enqueue(context, () => this.runImport(context, job.id));
1356
+ .filter((job) => job.state === 'pending' && !claimedJobIds.includes(job.id)))
1357
+ this.enqueue(context, () => this.runImport(context, job.id, 'resume'));
1328
1358
  this.enqueue(context, () => this.runRebuild(context));
1329
1359
  }
1330
1360
  enqueue(context, operation) {
@@ -1343,36 +1373,64 @@ class DesignProfiles {
1343
1373
  while (context.records.pendingIndex(1).length)
1344
1374
  await context.index.flush(runtime, context.abort.signal);
1345
1375
  }
1346
- async runImport(context, id) {
1376
+ async runImport(context, id, kind) {
1347
1377
  if (!context.schedule.isReady(id))
1348
1378
  return;
1349
1379
  const job = context.records.jobs().find((item) => item.id === id);
1350
1380
  if (!job || job.state !== 'pending')
1351
1381
  return;
1352
1382
  const started = Date.now();
1383
+ const importContext = {
1384
+ provider: context.records
1385
+ .sources()
1386
+ .find((source) => source.id === job.sourceId)?.provider,
1387
+ importKind: kind,
1388
+ };
1389
+ const syncTimes = context.records
1390
+ .jobs()
1391
+ .filter((item) => item.sourceId === job.sourceId && item.state === 'complete')
1392
+ .map((item) => Date.parse(item.updatedAt));
1393
+ const lastSyncedAt = syncTimes.length ? Math.max(...syncTimes) : undefined;
1394
+ let step = 'memory_start';
1353
1395
  try {
1354
1396
  await context.memory.start();
1355
1397
  const source = context.records.source(job.sourceId);
1356
- if ((0, design_profile_1.isRemoteProfileProvider)(source.provider)) {
1398
+ const isSync = (0, design_profile_1.isRemoteProfileProvider)(source.provider);
1399
+ if (isSync) {
1400
+ step = 'enumerate';
1357
1401
  await context.imports.enumerate(id, context.abort.signal);
1402
+ step = 'download';
1358
1403
  await context.imports.download(id, context.abort.signal);
1359
1404
  }
1405
+ step = 'analysis';
1360
1406
  await context.analysis.run({ jobId: id, signal: context.abort.signal });
1361
1407
  if (context.records.jobs().find((item) => item.id === id)?.state !==
1362
1408
  'pending')
1363
1409
  return;
1410
+ step = 'index';
1364
1411
  context.records.queueIndex(job.runtime);
1365
1412
  await this.runRebuild(context);
1366
1413
  if (context.records.jobs().find((item) => item.id === id)?.state !==
1367
1414
  'pending')
1368
1415
  return;
1369
1416
  context.records.updateJob({ id, state: 'complete' });
1417
+ const completed = context.records.jobs().find((item) => item.id === id);
1370
1418
  this.options.telemetry?.trackProfileOperation({
1371
1419
  stage: 'import',
1372
1420
  operation: 'import',
1373
1421
  outcome: 'completed',
1374
1422
  durationMs: Date.now() - started,
1375
1423
  referenceCount: context.records.references(job.sourceId).length,
1424
+ ...importContext,
1425
+ ...(isSync && completed
1426
+ ? {
1427
+ addedCount: completed.addedCount,
1428
+ removedCount: completed.removedCount,
1429
+ ...(lastSyncedAt === undefined
1430
+ ? {}
1431
+ : { staleHours: (started - lastSyncedAt) / MS_PER_HOUR }),
1432
+ }
1433
+ : {}),
1376
1434
  });
1377
1435
  }
1378
1436
  catch (error) {
@@ -1387,6 +1445,7 @@ class DesignProfiles {
1387
1445
  operation: 'import',
1388
1446
  outcome: 'deferred',
1389
1447
  durationMs: Date.now() - started,
1448
+ ...importContext,
1390
1449
  });
1391
1450
  return;
1392
1451
  }
@@ -1395,6 +1454,8 @@ class DesignProfiles {
1395
1454
  operation: 'import',
1396
1455
  outcome: 'failed',
1397
1456
  durationMs: Date.now() - started,
1457
+ ...importContext,
1458
+ failedStep: error instanceof ProfileRecords_1.UnfinishedImport ? error.step : step,
1398
1459
  error,
1399
1460
  });
1400
1461
  if (!context.abort.signal.aborted &&
@@ -1,12 +1,14 @@
1
1
  import type { ProfileRecords } from '../../storage/profiles/ProfileRecords';
2
2
  import type { ProfileMedia } from './ProfileMedia';
3
3
  import type { ProfileAgent } from './ProfileAgent';
4
+ import type { TelemetryService } from '../TelemetryService';
4
5
  interface ProfileAnalysisOptions {
5
6
  records: ProfileRecords;
6
7
  media: ProfileMedia;
7
8
  agent: Pick<ProfileAgent, 'run'>;
8
9
  isCurrentAccount: () => boolean;
9
10
  batchSize?: 1 | 2 | 4;
11
+ telemetry?: Pick<TelemetryService, 'trackProfileOperation'>;
10
12
  }
11
13
  export declare class ProfileAnalysis {
12
14
  private readonly options;
@@ -17,6 +19,7 @@ export declare class ProfileAnalysis {
17
19
  }): Promise<void>;
18
20
  private isPending;
19
21
  private analyze;
22
+ private track;
20
23
  private fail;
21
24
  }
22
25
  export {};
@@ -4,6 +4,15 @@ exports.ProfileAnalysis = void 0;
4
4
  const modelContract_1 = require("./modelContract");
5
5
  const analysisBatch_1 = require("./analysisBatch");
6
6
  const errorMessage_1 = require("../../utils/errorMessage");
7
+ const failureReason_1 = require("../../utils/failureReason");
8
+ const analysisMediaKind = (references) => {
9
+ const kinds = new Set(references.flatMap((reference) => reference.media.length
10
+ ? reference.media.map((media) => media.representation)
11
+ : ['text']));
12
+ if (kinds.size > 1)
13
+ return 'mixed';
14
+ return [...kinds][0] ?? 'text';
15
+ };
7
16
  class ProfileAnalysis {
8
17
  options;
9
18
  constructor(options) {
@@ -14,7 +23,12 @@ class ProfileAnalysis {
14
23
  const job = records.jobs().find((item) => item.id === jobId);
15
24
  if (!job || job.state !== 'pending')
16
25
  return;
17
- const input = { jobId, runtime: job.runtime, signal };
26
+ const input = {
27
+ jobId,
28
+ runtime: job.runtime,
29
+ signal,
30
+ provider: records.source(job.sourceId).provider,
31
+ };
18
32
  let batch = [];
19
33
  for (const reference of records.references(job.sourceId)) {
20
34
  if (!this.isPending(input))
@@ -23,6 +37,7 @@ class ProfileAnalysis {
23
37
  continue;
24
38
  if (reference.media.length < (reference.remoteImages?.length ?? 0))
25
39
  continue;
40
+ let startedAt = Date.now();
26
41
  try {
27
42
  if (!(0, analysisBatch_1.fitsAnalysisBatch)([reference]))
28
43
  throw new Error('Reference exceeds the analysis budget; split it into smaller references');
@@ -35,6 +50,7 @@ class ProfileAnalysis {
35
50
  batch = [];
36
51
  if (!this.isPending(input))
37
52
  return;
53
+ startedAt = Date.now();
38
54
  }
39
55
  const images = await Promise.all(reference.media.map((image) => media.resolve(image)));
40
56
  batch.push({ reference, images });
@@ -43,6 +59,13 @@ class ProfileAnalysis {
43
59
  if (!this.isPending(input))
44
60
  return;
45
61
  this.fail({ jobId, reference, error });
62
+ this.track({
63
+ job: input,
64
+ references: [reference],
65
+ outcome: 'failed',
66
+ startedAt,
67
+ error,
68
+ });
46
69
  }
47
70
  }
48
71
  if (batch.length && this.isPending(input))
@@ -60,6 +83,7 @@ class ProfileAnalysis {
60
83
  return;
61
84
  const { batch, runtime, signal, jobId } = input;
62
85
  const { agent, records } = this.options;
86
+ const startedAt = Date.now();
63
87
  let result;
64
88
  try {
65
89
  result = await agent.run({
@@ -73,8 +97,18 @@ class ProfileAnalysis {
73
97
  return;
74
98
  for (const { reference } of batch)
75
99
  this.fail({ jobId, reference, error });
100
+ this.track({
101
+ job: input,
102
+ references: batch.map((item) => item.reference),
103
+ outcome: 'failed',
104
+ startedAt,
105
+ error,
106
+ });
76
107
  return;
77
108
  }
109
+ const analyzed = [];
110
+ const failed = [];
111
+ let failure;
78
112
  for (const { reference, images } of batch) {
79
113
  if (!this.isPending(input))
80
114
  return;
@@ -95,11 +129,41 @@ class ProfileAnalysis {
95
129
  usage: (0, analysisBatch_1.analysisUsage)((0, modelContract_1.reportedProfileUsage)(result.usage), batch.length),
96
130
  });
97
131
  records.clearFailure(reference.id, 'analysis');
132
+ analyzed.push(reference);
98
133
  }
99
134
  catch (error) {
100
135
  this.fail({ jobId, reference, error });
136
+ failed.push(reference);
137
+ failure ??= error;
101
138
  }
102
139
  }
140
+ if (analyzed.length)
141
+ this.track({
142
+ job: input,
143
+ references: analyzed,
144
+ outcome: 'completed',
145
+ startedAt,
146
+ });
147
+ if (failed.length)
148
+ this.track({
149
+ job: input,
150
+ references: failed,
151
+ outcome: 'failed',
152
+ startedAt,
153
+ error: failure,
154
+ });
155
+ }
156
+ track({ job, references, outcome, startedAt, error, }) {
157
+ this.options.telemetry?.trackProfileOperation({
158
+ stage: 'analysis',
159
+ operation: 'analysis',
160
+ outcome,
161
+ durationMs: Date.now() - startedAt,
162
+ referenceCount: references.length,
163
+ provider: job.provider,
164
+ mediaKind: analysisMediaKind(references),
165
+ error,
166
+ });
103
167
  }
104
168
  fail({ jobId, reference, error, }) {
105
169
  this.options.records.saveFailure({
@@ -108,6 +172,7 @@ class ProfileAnalysis {
108
172
  revision: reference.revision,
109
173
  stage: 'analysis',
110
174
  error: (0, errorMessage_1.errorMessage)(error, 'Reference analysis failed'),
175
+ reasonCode: (0, failureReason_1.failureReasonCode)(error),
111
176
  });
112
177
  }
113
178
  }
@@ -36,6 +36,8 @@ export declare class ProfileImports {
36
36
  total: number | null;
37
37
  error: string | null;
38
38
  updatedAt: string;
39
+ addedCount: number;
40
+ removedCount: number;
39
41
  };
40
42
  };
41
43
  enumerate(jobId: string, signal: AbortSignal): Promise<void>;
@@ -12,6 +12,7 @@ const ProfileMedia_1 = require("./ProfileMedia");
12
12
  const boardPage_1 = require("./boardPage");
13
13
  const sourceContext_1 = require("../../agent-variants/sourceContext");
14
14
  const errorMessage_1 = require("../../utils/errorMessage");
15
+ const failureReason_1 = require("../../utils/failureReason");
15
16
  const DOWNLOAD_TIMEOUT_MS = 20_000;
16
17
  const MAX_REDIRECTS = 5;
17
18
  const MEDIA_HOSTS = ['pinimg.com', 'are.na', 'cloudfront.net', 'amazonaws.com'];
@@ -202,6 +203,7 @@ class ProfileImports {
202
203
  revision: current.revision,
203
204
  stage: 'download',
204
205
  error: (0, errorMessage_1.errorMessage)(error, 'Image download failed'),
206
+ reasonCode: (0, failureReason_1.failureReasonCode)(error),
205
207
  });
206
208
  }
207
209
  }
@@ -200,7 +200,7 @@ textarea {
200
200
 
201
201
  .eyebrow {
202
202
  margin: 0 0 16px;
203
- color: #ff3300;
203
+ color: #e54b2c;
204
204
  font-size: 13px;
205
205
  font-weight: 700;
206
206
  letter-spacing: 0;
@@ -1,5 +1,7 @@
1
1
  import { ProfilePlans } from './ProfilePlans';
2
2
  import { type GuidanceDocument, type GuidanceInput, type GuidanceSelection, type GuidanceUpdate, type SavedGuidance } from '../../types/guidance';
3
+ import { type ProfileImportStep } from '../../types/design-profile';
4
+ import type { FailureReasonCode } from '../../utils/failureReason';
3
5
  import type { DesignLibraryReference } from '../../types/design-library';
4
6
  import type { ProfileCandidate, ProfileCollectionProgress, ProfileIndexWork, ProfileObservation, ProfileReference, ProfileReferenceInput, ProfileRuntime, ProfileSourceInput } from '../../types/design-profile';
5
7
  type ImportPage = {
@@ -12,6 +14,12 @@ type ImportPage = {
12
14
  title?: string;
13
15
  collection?: ProfileCollectionProgress;
14
16
  };
17
+ type UnfinishedImportStep = Extract<ProfileImportStep, 'enumerate' | 'download' | 'analysis'>;
18
+ export declare class UnfinishedImport extends Error {
19
+ readonly step: UnfinishedImportStep;
20
+ readonly reasonCode: FailureReasonCode;
21
+ constructor(step: UnfinishedImportStep, reasonCode: FailureReasonCode);
22
+ }
15
23
  export declare class ProfileRecords {
16
24
  readonly directory: string;
17
25
  private readonly db;
@@ -245,6 +253,8 @@ export declare class ProfileRecords {
245
253
  total: number | null;
246
254
  error: string | null;
247
255
  updatedAt: string;
256
+ addedCount: number;
257
+ removedCount: number;
248
258
  };
249
259
  };
250
260
  startImport({ sourceId, runtime, }: {
@@ -262,6 +272,8 @@ export declare class ProfileRecords {
262
272
  total: number | null;
263
273
  error: string | null;
264
274
  updatedAt: string;
275
+ addedCount: number;
276
+ removedCount: number;
265
277
  };
266
278
  private writeImport;
267
279
  jobs(): {
@@ -276,6 +288,8 @@ export declare class ProfileRecords {
276
288
  total: number | null;
277
289
  error: string | null;
278
290
  updatedAt: string;
291
+ addedCount: number;
292
+ removedCount: number;
279
293
  }[];
280
294
  deferImport({ id, retryAfterMs }: {
281
295
  id: string;
@@ -290,12 +304,14 @@ export declare class ProfileRecords {
290
304
  state: 'pending' | 'paused' | 'failed' | 'complete';
291
305
  error?: string | null;
292
306
  }): void;
307
+ private unfinishedImport;
293
308
  failures(jobId?: string): {
294
309
  jobId: string;
295
310
  referenceId: string;
296
311
  revision: number;
297
312
  stage: "download" | "analysis";
298
313
  error: string;
314
+ reasonCode: "timeout" | "unknown" | "aborted" | "multiple_lockfiles" | "unsupported_package_manager" | "invalid_package_manager_field" | "invalid_manifest" | "invalid_workspace" | "path_outside_workspace" | "dangling_symlink" | "version_unreadable" | "version_mismatch" | "no_install_command" | "install_failed" | "lockfile_outdated" | "engine_incompatible" | "install_script_failed" | "installer_unstoppable" | "inputs_changed" | "ENOENT" | "EACCES" | "EPERM" | "EEXIST" | "ENOTDIR" | "EISDIR" | "ELOOP" | "ENOSPC" | "EMFILE" | "EADDRINUSE" | "ECONNREFUSED" | "ECONNRESET" | "ETIMEDOUT" | "EBUSY" | "ENOTSUP" | "EINVAL" | "ENOTEMPTY" | "EROFS" | "EAI_AGAIN" | null;
299
315
  attempts: number;
300
316
  updatedAt: string;
301
317
  }[];
@@ -305,6 +321,7 @@ export declare class ProfileRecords {
305
321
  revision: number;
306
322
  stage: 'download' | 'analysis';
307
323
  error: string;
324
+ reasonCode: FailureReasonCode;
308
325
  }): void;
309
326
  clearFailure(referenceId: string, stage: 'download' | 'analysis'): void;
310
327
  saveImportPage(input: ImportPage): void;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ProfileRecords = void 0;
6
+ exports.ProfileRecords = exports.UnfinishedImport = void 0;
7
7
  const guidanceVerifier_1 = require("./guidanceVerifier");
8
8
  const guidance_verifier_1 = require("../../types/guidance-verifier");
9
9
  const reference_connectors_1 = require("../../types/reference-connectors");
@@ -25,6 +25,17 @@ const MIN_RETRY_MS = 1000;
25
25
  const MAX_BACKOFF_EXPONENT = 4;
26
26
  const MAX_DATE_MS = 8_640_000_000_000_000;
27
27
  const identity = (...parts) => (0, crypto_1.createHash)('sha256').update(JSON.stringify(parts)).digest('hex');
28
+ class UnfinishedImport extends Error {
29
+ step;
30
+ reasonCode;
31
+ constructor(step, reasonCode) {
32
+ super('Import has unfinished references');
33
+ this.step = step;
34
+ this.reasonCode = reasonCode;
35
+ this.name = 'UnfinishedImport';
36
+ }
37
+ }
38
+ exports.UnfinishedImport = UnfinishedImport;
28
39
  const guidanceDocument = (document) => guidance_1.GuidanceDocumentSchema.parse({
29
40
  id: document.id,
30
41
  title: document.title,
@@ -649,18 +660,33 @@ class ProfileRecords {
649
660
  const job = this.jobs().find((item) => item.id === id);
650
661
  if (!job || job.state === 'superseded')
651
662
  throw new Error('Import job is no longer active');
652
- if (state === 'complete' &&
653
- (job.state !== 'pending' ||
654
- !job.isEnumerated ||
655
- this.failures(id).length ||
656
- this.references(job.sourceId).some((reference) => !reference.unsupportedReason && !this.observation(reference.id))))
657
- throw new Error('Import has unfinished references');
663
+ if (state === 'complete') {
664
+ if (job.state !== 'pending')
665
+ throw new Error('Import has unfinished references');
666
+ const unfinished = this.unfinishedImport(job);
667
+ if (unfinished)
668
+ throw unfinished;
669
+ }
658
670
  this.db
659
671
  .update(schema_1.importJobs)
660
672
  .set({ state, error, updatedAt: new Date().toISOString() })
661
673
  .where((0, drizzle_orm_1.eq)(schema_1.importJobs.id, id))
662
674
  .run();
663
675
  }
676
+ unfinishedImport(job) {
677
+ if (!job.isEnumerated)
678
+ return new UnfinishedImport('enumerate', 'unknown');
679
+ const failures = this.failures(job.id);
680
+ for (const stage of ['download', 'analysis']) {
681
+ const failure = failures.find((item) => item.stage === stage);
682
+ if (failure)
683
+ return new UnfinishedImport(stage, failure.reasonCode ?? 'unknown');
684
+ }
685
+ const isAnalysisMissing = this.references(job.sourceId).some((reference) => !reference.unsupportedReason && !this.observation(reference.id));
686
+ return isAnalysisMissing
687
+ ? new UnfinishedImport('analysis', 'unknown')
688
+ : null;
689
+ }
664
690
  failures(jobId) {
665
691
  return this.db
666
692
  .select({ failure: schema_1.referenceFailures })
@@ -777,7 +803,15 @@ class ProfileRecords {
777
803
  .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.importCursors.jobId, jobId), (0, drizzle_orm_1.eq)(schema_1.importCursors.cursor, cursor)))
778
804
  .get())
779
805
  throw new Error('Provider returned a repeated page cursor');
806
+ let addedCount = 0;
780
807
  for (const item of validated) {
808
+ const isMember = this.db
809
+ .select()
810
+ .from(schema_1.memberships)
811
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.memberships.sourceId, job.sourceId), (0, drizzle_orm_1.eq)(schema_1.memberships.referenceId, identity(item.provider, item.remoteId))))
812
+ .get();
813
+ if (!isMember)
814
+ addedCount += 1;
781
815
  const reference = this.writeReference(job.sourceId, item, job.id);
782
816
  if (collection)
783
817
  this.db
@@ -819,6 +853,7 @@ class ProfileRecords {
819
853
  .set({ resolvedRemoteId: remoteId, ...(title ? { title } : {}) })
820
854
  .where((0, drizzle_orm_1.eq)(schema_1.sources.id, job.sourceId))
821
855
  .run();
856
+ let removedCount = 0;
822
857
  if (isComplete) {
823
858
  const unseen = this.db
824
859
  .select()
@@ -834,6 +869,7 @@ class ProfileRecords {
834
869
  if (reference)
835
870
  this.queueOrphan(reference);
836
871
  });
872
+ removedCount = unseen.length;
837
873
  }
838
874
  this.db
839
875
  .update(schema_1.importJobs)
@@ -843,6 +879,8 @@ class ProfileRecords {
843
879
  total: collection && isComplete ? enumerated : (total ?? job.total),
844
880
  state: 'pending',
845
881
  isEnumerated: isComplete,
882
+ addedCount: job.addedCount + addedCount,
883
+ removedCount: job.removedCount + removedCount,
846
884
  updatedAt: new Date().toISOString(),
847
885
  })
848
886
  .where((0, drizzle_orm_1.eq)(schema_1.importJobs.id, job.id))
@@ -1,4 +1,4 @@
1
- export declare const PROFILE_SCHEMA = "\nCREATE TABLE profile (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), id TEXT NOT NULL UNIQUE, is_enabled INTEGER NOT NULL, revision INTEGER NOT NULL);\nCREATE TABLE sources (id TEXT PRIMARY KEY, provider TEXT NOT NULL, remote_id TEXT NOT NULL,\n title TEXT NOT NULL, url TEXT, is_included INTEGER NOT NULL, removed_at TEXT, resolved_remote_id TEXT,\n UNIQUE(provider, remote_id));\nCREATE TABLE profile_references (id TEXT PRIMARY KEY, revision INTEGER NOT NULL,\n content_hash TEXT NOT NULL, body TEXT NOT NULL);\nCREATE TABLE memberships (source_id TEXT NOT NULL REFERENCES sources(id),\n reference_id TEXT NOT NULL REFERENCES profile_references(id), seen_job_id TEXT,\n PRIMARY KEY(source_id, reference_id));\nCREATE INDEX memberships_reference ON memberships(reference_id);\nCREATE TABLE observations (reference_id TEXT PRIMARY KEY REFERENCES profile_references(id),\n revision INTEGER NOT NULL, body TEXT NOT NULL);\nCREATE TABLE index_work (reference_id TEXT PRIMARY KEY REFERENCES profile_references(id),\n id TEXT NOT NULL, revision INTEGER NOT NULL, operation TEXT NOT NULL,\n engine_id TEXT, state TEXT NOT NULL);\nCREATE TABLE import_jobs (id TEXT PRIMARY KEY, source_id TEXT NOT NULL REFERENCES sources(id),\n runtime TEXT NOT NULL, state TEXT NOT NULL, cursor TEXT, enumerated INTEGER NOT NULL,\n total INTEGER, error TEXT, updated_at TEXT NOT NULL, is_enumerated INTEGER NOT NULL DEFAULT 0);\nCREATE INDEX import_jobs_source ON import_jobs(source_id);\nCREATE TABLE index_rebuild (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), id TEXT NOT NULL, runtime TEXT NOT NULL);\n\nCREATE TABLE import_cursors (job_id TEXT NOT NULL REFERENCES import_jobs(id), cursor TEXT NOT NULL, PRIMARY KEY(job_id, cursor));\nCREATE TABLE reference_failures (job_id TEXT NOT NULL REFERENCES import_jobs(id), reference_id TEXT NOT NULL REFERENCES profile_references(id), revision INTEGER NOT NULL, stage TEXT NOT NULL, error TEXT NOT NULL, attempts INTEGER NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY(job_id, reference_id, stage));\n\nCREATE TABLE provider_cooldowns (provider TEXT PRIMARY KEY, retry_at TEXT NOT NULL, attempts INTEGER NOT NULL);\nCREATE TABLE import_collection_references (job_id TEXT NOT NULL REFERENCES import_jobs(id), collection_id TEXT NOT NULL, reference_id TEXT NOT NULL REFERENCES profile_references(id), PRIMARY KEY(job_id, collection_id, reference_id));\n\nCREATE TABLE reference_discoveries (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE reference_plans (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE planned_sources (source_id TEXT PRIMARY KEY REFERENCES sources(id), discovery_id TEXT NOT NULL REFERENCES reference_discoveries(id), collection_id TEXT NOT NULL);\nCREATE TABLE reference_onboarding (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);\n\nCREATE TABLE connector_onboarding (provider TEXT PRIMARY KEY, state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);\nCREATE TABLE guidance (id TEXT PRIMARY KEY, title TEXT NOT NULL, markdown TEXT NOT NULL, is_enabled INTEGER NOT NULL);\nALTER TABLE guidance ADD COLUMN attachments TEXT;\nALTER TABLE guidance ADD COLUMN \"references\" TEXT;\nALTER TABLE guidance ADD COLUMN verifier TEXT;\n";
2
- export declare const PROFILE_MIGRATIONS: readonly ["ALTER TABLE import_jobs ADD COLUMN is_enumerated INTEGER NOT NULL DEFAULT 0", "CREATE TABLE index_rebuild (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), id TEXT NOT NULL, runtime TEXT NOT NULL);", "ALTER TABLE sources ADD COLUMN resolved_remote_id TEXT; \nCREATE TABLE import_cursors (job_id TEXT NOT NULL REFERENCES import_jobs(id), cursor TEXT NOT NULL, PRIMARY KEY(job_id, cursor));\nCREATE TABLE reference_failures (job_id TEXT NOT NULL REFERENCES import_jobs(id), reference_id TEXT NOT NULL REFERENCES profile_references(id), revision INTEGER NOT NULL, stage TEXT NOT NULL, error TEXT NOT NULL, attempts INTEGER NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY(job_id, reference_id, stage));\n", "CREATE TABLE provider_cooldowns (provider TEXT PRIMARY KEY, retry_at TEXT NOT NULL, attempts INTEGER NOT NULL);", "CREATE TABLE import_collection_references (job_id TEXT NOT NULL REFERENCES import_jobs(id), collection_id TEXT NOT NULL, reference_id TEXT NOT NULL REFERENCES profile_references(id), PRIMARY KEY(job_id, collection_id, reference_id));", "\nCREATE TABLE reference_discoveries (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE reference_plans (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE planned_sources (source_id TEXT PRIMARY KEY REFERENCES sources(id), discovery_id TEXT NOT NULL REFERENCES reference_discoveries(id), collection_id TEXT NOT NULL);\nCREATE TABLE reference_onboarding (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);\n", "CREATE TABLE connector_onboarding (provider TEXT PRIMARY KEY, state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);", "CREATE TABLE guidance (id TEXT PRIMARY KEY, title TEXT NOT NULL, markdown TEXT NOT NULL, is_enabled INTEGER NOT NULL);", "ALTER TABLE guidance ADD COLUMN attachments TEXT;", "ALTER TABLE guidance ADD COLUMN \"references\" TEXT;", "ALTER TABLE guidance ADD COLUMN verifier TEXT;"];
1
+ export declare const PROFILE_SCHEMA = "\nCREATE TABLE profile (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), id TEXT NOT NULL UNIQUE, is_enabled INTEGER NOT NULL, revision INTEGER NOT NULL);\nCREATE TABLE sources (id TEXT PRIMARY KEY, provider TEXT NOT NULL, remote_id TEXT NOT NULL,\n title TEXT NOT NULL, url TEXT, is_included INTEGER NOT NULL, removed_at TEXT, resolved_remote_id TEXT,\n UNIQUE(provider, remote_id));\nCREATE TABLE profile_references (id TEXT PRIMARY KEY, revision INTEGER NOT NULL,\n content_hash TEXT NOT NULL, body TEXT NOT NULL);\nCREATE TABLE memberships (source_id TEXT NOT NULL REFERENCES sources(id),\n reference_id TEXT NOT NULL REFERENCES profile_references(id), seen_job_id TEXT,\n PRIMARY KEY(source_id, reference_id));\nCREATE INDEX memberships_reference ON memberships(reference_id);\nCREATE TABLE observations (reference_id TEXT PRIMARY KEY REFERENCES profile_references(id),\n revision INTEGER NOT NULL, body TEXT NOT NULL);\nCREATE TABLE index_work (reference_id TEXT PRIMARY KEY REFERENCES profile_references(id),\n id TEXT NOT NULL, revision INTEGER NOT NULL, operation TEXT NOT NULL,\n engine_id TEXT, state TEXT NOT NULL);\nCREATE TABLE import_jobs (id TEXT PRIMARY KEY, source_id TEXT NOT NULL REFERENCES sources(id),\n runtime TEXT NOT NULL, state TEXT NOT NULL, cursor TEXT, enumerated INTEGER NOT NULL,\n total INTEGER, error TEXT, updated_at TEXT NOT NULL, is_enumerated INTEGER NOT NULL DEFAULT 0);\nCREATE INDEX import_jobs_source ON import_jobs(source_id);\nCREATE TABLE index_rebuild (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), id TEXT NOT NULL, runtime TEXT NOT NULL);\n\nCREATE TABLE import_cursors (job_id TEXT NOT NULL REFERENCES import_jobs(id), cursor TEXT NOT NULL, PRIMARY KEY(job_id, cursor));\nCREATE TABLE reference_failures (job_id TEXT NOT NULL REFERENCES import_jobs(id), reference_id TEXT NOT NULL REFERENCES profile_references(id), revision INTEGER NOT NULL, stage TEXT NOT NULL, error TEXT NOT NULL, attempts INTEGER NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY(job_id, reference_id, stage));\n\nCREATE TABLE provider_cooldowns (provider TEXT PRIMARY KEY, retry_at TEXT NOT NULL, attempts INTEGER NOT NULL);\nCREATE TABLE import_collection_references (job_id TEXT NOT NULL REFERENCES import_jobs(id), collection_id TEXT NOT NULL, reference_id TEXT NOT NULL REFERENCES profile_references(id), PRIMARY KEY(job_id, collection_id, reference_id));\n\nCREATE TABLE reference_discoveries (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE reference_plans (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE planned_sources (source_id TEXT PRIMARY KEY REFERENCES sources(id), discovery_id TEXT NOT NULL REFERENCES reference_discoveries(id), collection_id TEXT NOT NULL);\nCREATE TABLE reference_onboarding (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);\n\nCREATE TABLE connector_onboarding (provider TEXT PRIMARY KEY, state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);\nCREATE TABLE guidance (id TEXT PRIMARY KEY, title TEXT NOT NULL, markdown TEXT NOT NULL, is_enabled INTEGER NOT NULL);\nALTER TABLE guidance ADD COLUMN attachments TEXT;\nALTER TABLE guidance ADD COLUMN \"references\" TEXT;\nALTER TABLE guidance ADD COLUMN verifier TEXT;\n\nALTER TABLE import_jobs ADD COLUMN added_count INTEGER NOT NULL DEFAULT 0;\nALTER TABLE import_jobs ADD COLUMN removed_count INTEGER NOT NULL DEFAULT 0;\nALTER TABLE reference_failures ADD COLUMN reason_code TEXT;\n\n";
2
+ export declare const PROFILE_MIGRATIONS: readonly ["ALTER TABLE import_jobs ADD COLUMN is_enumerated INTEGER NOT NULL DEFAULT 0", "CREATE TABLE index_rebuild (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), id TEXT NOT NULL, runtime TEXT NOT NULL);", "ALTER TABLE sources ADD COLUMN resolved_remote_id TEXT; \nCREATE TABLE import_cursors (job_id TEXT NOT NULL REFERENCES import_jobs(id), cursor TEXT NOT NULL, PRIMARY KEY(job_id, cursor));\nCREATE TABLE reference_failures (job_id TEXT NOT NULL REFERENCES import_jobs(id), reference_id TEXT NOT NULL REFERENCES profile_references(id), revision INTEGER NOT NULL, stage TEXT NOT NULL, error TEXT NOT NULL, attempts INTEGER NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY(job_id, reference_id, stage));\n", "CREATE TABLE provider_cooldowns (provider TEXT PRIMARY KEY, retry_at TEXT NOT NULL, attempts INTEGER NOT NULL);", "CREATE TABLE import_collection_references (job_id TEXT NOT NULL REFERENCES import_jobs(id), collection_id TEXT NOT NULL, reference_id TEXT NOT NULL REFERENCES profile_references(id), PRIMARY KEY(job_id, collection_id, reference_id));", "\nCREATE TABLE reference_discoveries (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE reference_plans (id TEXT PRIMARY KEY, body TEXT NOT NULL);\nCREATE TABLE planned_sources (source_id TEXT PRIMARY KEY REFERENCES sources(id), discovery_id TEXT NOT NULL REFERENCES reference_discoveries(id), collection_id TEXT NOT NULL);\nCREATE TABLE reference_onboarding (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);\n", "CREATE TABLE connector_onboarding (provider TEXT PRIMARY KEY, state TEXT NOT NULL, offered_at TEXT, remind_at TEXT);", "CREATE TABLE guidance (id TEXT PRIMARY KEY, title TEXT NOT NULL, markdown TEXT NOT NULL, is_enabled INTEGER NOT NULL);", "ALTER TABLE guidance ADD COLUMN attachments TEXT;", "ALTER TABLE guidance ADD COLUMN \"references\" TEXT;", "ALTER TABLE guidance ADD COLUMN verifier TEXT;", "\nALTER TABLE import_jobs ADD COLUMN added_count INTEGER NOT NULL DEFAULT 0;\nALTER TABLE import_jobs ADD COLUMN removed_count INTEGER NOT NULL DEFAULT 0;\nALTER TABLE reference_failures ADD COLUMN reason_code TEXT;\n"];
3
3
  export declare const PROFILE_SCHEMA_VERSION: number;
4
4
  //# sourceMappingURL=migrations.d.ts.map