@superblocksteam/shared 0.9613.2 → 0.9614.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 (203) hide show
  1. package/dist/database-lifecycle/destructiveMigrations.d.ts +18 -0
  2. package/dist/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
  3. package/dist/database-lifecycle/destructiveMigrations.js.map +1 -1
  4. package/dist/database-lifecycle/index.d.ts +8 -1
  5. package/dist/database-lifecycle/index.d.ts.map +1 -1
  6. package/dist/database-lifecycle/index.js +2 -1
  7. package/dist/database-lifecycle/index.js.map +1 -1
  8. package/dist/dbfs/types.d.ts +1 -0
  9. package/dist/dbfs/types.d.ts.map +1 -1
  10. package/dist/dbfs/types.js +14 -3
  11. package/dist/dbfs/types.js.map +1 -1
  12. package/dist/errors/http.d.ts +5 -3
  13. package/dist/errors/http.d.ts.map +1 -1
  14. package/dist/errors/http.js +9 -5
  15. package/dist/errors/http.js.map +1 -1
  16. package/dist/types/ai/index.d.ts +1 -0
  17. package/dist/types/ai/index.d.ts.map +1 -1
  18. package/dist/types/ai/index.js +1 -0
  19. package/dist/types/ai/index.js.map +1 -1
  20. package/dist/types/audit/ocsf.d.ts +11 -0
  21. package/dist/types/audit/ocsf.d.ts.map +1 -1
  22. package/dist/types/billing/index.d.ts +1 -0
  23. package/dist/types/billing/index.d.ts.map +1 -1
  24. package/dist/types/billing/index.js +1 -0
  25. package/dist/types/billing/index.js.map +1 -1
  26. package/dist/types/billing/usageSource.d.ts +26 -0
  27. package/dist/types/billing/usageSource.d.ts.map +1 -0
  28. package/dist/types/billing/usageSource.js +100 -0
  29. package/dist/types/billing/usageSource.js.map +1 -0
  30. package/dist/types/policyGate/errors.d.ts +3 -0
  31. package/dist/types/policyGate/errors.d.ts.map +1 -1
  32. package/dist/types/policyGate/errors.js +1 -0
  33. package/dist/types/policyGate/errors.js.map +1 -1
  34. package/dist/types/policyGate/index.d.ts +5 -1
  35. package/dist/types/policyGate/index.d.ts.map +1 -1
  36. package/dist/types/policyGate/index.js +11 -0
  37. package/dist/types/policyGate/index.js.map +1 -1
  38. package/dist/types/policyGate/index.test.d.ts +2 -0
  39. package/dist/types/policyGate/index.test.d.ts.map +1 -0
  40. package/dist/types/policyGate/index.test.js +23 -0
  41. package/dist/types/policyGate/index.test.js.map +1 -0
  42. package/dist/types/publishPipeline/index.d.ts +27 -0
  43. package/dist/types/publishPipeline/index.d.ts.map +1 -1
  44. package/dist/types/publishPipeline/index.js +1 -0
  45. package/dist/types/publishPipeline/index.js.map +1 -1
  46. package/dist/types/publishPipeline/plan.d.ts +9 -4
  47. package/dist/types/publishPipeline/plan.d.ts.map +1 -1
  48. package/dist/types/publishPipeline/plan.js +37 -20
  49. package/dist/types/publishPipeline/plan.js.map +1 -1
  50. package/dist/types/publishPipeline/plan.test.js +28 -8
  51. package/dist/types/publishPipeline/plan.test.js.map +1 -1
  52. package/dist/types/publishPipeline/policyStatus.d.ts +7 -0
  53. package/dist/types/publishPipeline/policyStatus.d.ts.map +1 -0
  54. package/dist/types/publishPipeline/policyStatus.js +40 -0
  55. package/dist/types/publishPipeline/policyStatus.js.map +1 -0
  56. package/dist/types/reviewPolicy/index.d.ts +14 -4
  57. package/dist/types/reviewPolicy/index.d.ts.map +1 -1
  58. package/dist/types/reviewPolicy/index.js +11 -5
  59. package/dist/types/reviewPolicy/index.js.map +1 -1
  60. package/dist/types/scim/index.d.ts +21 -1
  61. package/dist/types/scim/index.d.ts.map +1 -1
  62. package/dist/types/scim/index.js +19 -1
  63. package/dist/types/scim/index.js.map +1 -1
  64. package/dist/utils/attachment-delivery.d.ts +7 -0
  65. package/dist/utils/attachment-delivery.d.ts.map +1 -1
  66. package/dist/utils/attachment-delivery.js +19 -2
  67. package/dist/utils/attachment-delivery.js.map +1 -1
  68. package/dist/utils/attachment-delivery.test.d.ts +2 -0
  69. package/dist/utils/attachment-delivery.test.d.ts.map +1 -0
  70. package/dist/utils/attachment-delivery.test.js +40 -0
  71. package/dist/utils/attachment-delivery.test.js.map +1 -0
  72. package/dist/utils/attachment-upload.d.ts +3 -2
  73. package/dist/utils/attachment-upload.d.ts.map +1 -1
  74. package/dist/utils/attachment-upload.js +11 -3
  75. package/dist/utils/attachment-upload.js.map +1 -1
  76. package/dist/utils/attachment-upload.test.js +40 -1
  77. package/dist/utils/attachment-upload.test.js.map +1 -1
  78. package/dist/utils/attachment-validation.d.ts +64 -0
  79. package/dist/utils/attachment-validation.d.ts.map +1 -0
  80. package/dist/utils/attachment-validation.js +269 -0
  81. package/dist/utils/attachment-validation.js.map +1 -0
  82. package/dist/utils/attachment-validation.test.d.ts +2 -0
  83. package/dist/utils/attachment-validation.test.d.ts.map +1 -0
  84. package/dist/utils/attachment-validation.test.js +171 -0
  85. package/dist/utils/attachment-validation.test.js.map +1 -0
  86. package/dist/utils/index.d.ts +1 -0
  87. package/dist/utils/index.d.ts.map +1 -1
  88. package/dist/utils/index.js +1 -0
  89. package/dist/utils/index.js.map +1 -1
  90. package/dist-esm/database-lifecycle/destructiveMigrations.d.ts +18 -0
  91. package/dist-esm/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
  92. package/dist-esm/database-lifecycle/destructiveMigrations.js.map +1 -1
  93. package/dist-esm/database-lifecycle/index.d.ts +8 -1
  94. package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
  95. package/dist-esm/database-lifecycle/index.js +1 -0
  96. package/dist-esm/database-lifecycle/index.js.map +1 -1
  97. package/dist-esm/dbfs/types.d.ts +1 -0
  98. package/dist-esm/dbfs/types.d.ts.map +1 -1
  99. package/dist-esm/dbfs/types.js +13 -2
  100. package/dist-esm/dbfs/types.js.map +1 -1
  101. package/dist-esm/errors/http.d.ts +5 -3
  102. package/dist-esm/errors/http.d.ts.map +1 -1
  103. package/dist-esm/errors/http.js +9 -5
  104. package/dist-esm/errors/http.js.map +1 -1
  105. package/dist-esm/types/ai/index.d.ts +1 -0
  106. package/dist-esm/types/ai/index.d.ts.map +1 -1
  107. package/dist-esm/types/ai/index.js +1 -0
  108. package/dist-esm/types/ai/index.js.map +1 -1
  109. package/dist-esm/types/audit/ocsf.d.ts +11 -0
  110. package/dist-esm/types/audit/ocsf.d.ts.map +1 -1
  111. package/dist-esm/types/billing/index.d.ts +1 -0
  112. package/dist-esm/types/billing/index.d.ts.map +1 -1
  113. package/dist-esm/types/billing/index.js +1 -0
  114. package/dist-esm/types/billing/index.js.map +1 -1
  115. package/dist-esm/types/billing/usageSource.d.ts +26 -0
  116. package/dist-esm/types/billing/usageSource.d.ts.map +1 -0
  117. package/dist-esm/types/billing/usageSource.js +97 -0
  118. package/dist-esm/types/billing/usageSource.js.map +1 -0
  119. package/dist-esm/types/policyGate/errors.d.ts +3 -0
  120. package/dist-esm/types/policyGate/errors.d.ts.map +1 -1
  121. package/dist-esm/types/policyGate/errors.js +1 -0
  122. package/dist-esm/types/policyGate/errors.js.map +1 -1
  123. package/dist-esm/types/policyGate/index.d.ts +5 -1
  124. package/dist-esm/types/policyGate/index.d.ts.map +1 -1
  125. package/dist-esm/types/policyGate/index.js +10 -0
  126. package/dist-esm/types/policyGate/index.js.map +1 -1
  127. package/dist-esm/types/policyGate/index.test.d.ts +2 -0
  128. package/dist-esm/types/policyGate/index.test.d.ts.map +1 -0
  129. package/dist-esm/types/policyGate/index.test.js +21 -0
  130. package/dist-esm/types/policyGate/index.test.js.map +1 -0
  131. package/dist-esm/types/publishPipeline/index.d.ts +27 -0
  132. package/dist-esm/types/publishPipeline/index.d.ts.map +1 -1
  133. package/dist-esm/types/publishPipeline/index.js +1 -0
  134. package/dist-esm/types/publishPipeline/index.js.map +1 -1
  135. package/dist-esm/types/publishPipeline/plan.d.ts +9 -4
  136. package/dist-esm/types/publishPipeline/plan.d.ts.map +1 -1
  137. package/dist-esm/types/publishPipeline/plan.js +36 -19
  138. package/dist-esm/types/publishPipeline/plan.js.map +1 -1
  139. package/dist-esm/types/publishPipeline/plan.test.js +29 -9
  140. package/dist-esm/types/publishPipeline/plan.test.js.map +1 -1
  141. package/dist-esm/types/publishPipeline/policyStatus.d.ts +7 -0
  142. package/dist-esm/types/publishPipeline/policyStatus.d.ts.map +1 -0
  143. package/dist-esm/types/publishPipeline/policyStatus.js +37 -0
  144. package/dist-esm/types/publishPipeline/policyStatus.js.map +1 -0
  145. package/dist-esm/types/reviewPolicy/index.d.ts +14 -4
  146. package/dist-esm/types/reviewPolicy/index.d.ts.map +1 -1
  147. package/dist-esm/types/reviewPolicy/index.js +10 -4
  148. package/dist-esm/types/reviewPolicy/index.js.map +1 -1
  149. package/dist-esm/types/scim/index.d.ts +21 -1
  150. package/dist-esm/types/scim/index.d.ts.map +1 -1
  151. package/dist-esm/types/scim/index.js +18 -0
  152. package/dist-esm/types/scim/index.js.map +1 -1
  153. package/dist-esm/utils/attachment-delivery.d.ts +7 -0
  154. package/dist-esm/utils/attachment-delivery.d.ts.map +1 -1
  155. package/dist-esm/utils/attachment-delivery.js +17 -2
  156. package/dist-esm/utils/attachment-delivery.js.map +1 -1
  157. package/dist-esm/utils/attachment-delivery.test.d.ts +2 -0
  158. package/dist-esm/utils/attachment-delivery.test.d.ts.map +1 -0
  159. package/dist-esm/utils/attachment-delivery.test.js +38 -0
  160. package/dist-esm/utils/attachment-delivery.test.js.map +1 -0
  161. package/dist-esm/utils/attachment-upload.d.ts +3 -2
  162. package/dist-esm/utils/attachment-upload.d.ts.map +1 -1
  163. package/dist-esm/utils/attachment-upload.js +10 -2
  164. package/dist-esm/utils/attachment-upload.js.map +1 -1
  165. package/dist-esm/utils/attachment-upload.test.js +42 -3
  166. package/dist-esm/utils/attachment-upload.test.js.map +1 -1
  167. package/dist-esm/utils/attachment-validation.d.ts +64 -0
  168. package/dist-esm/utils/attachment-validation.d.ts.map +1 -0
  169. package/dist-esm/utils/attachment-validation.js +259 -0
  170. package/dist-esm/utils/attachment-validation.js.map +1 -0
  171. package/dist-esm/utils/attachment-validation.test.d.ts +2 -0
  172. package/dist-esm/utils/attachment-validation.test.d.ts.map +1 -0
  173. package/dist-esm/utils/attachment-validation.test.js +169 -0
  174. package/dist-esm/utils/attachment-validation.test.js.map +1 -0
  175. package/dist-esm/utils/index.d.ts +1 -0
  176. package/dist-esm/utils/index.d.ts.map +1 -1
  177. package/dist-esm/utils/index.js +1 -0
  178. package/dist-esm/utils/index.js.map +1 -1
  179. package/package.json +4 -3
  180. package/src/database-lifecycle/destructiveMigrations.ts +19 -0
  181. package/src/database-lifecycle/index.ts +13 -1
  182. package/src/dbfs/types.ts +15 -3
  183. package/src/errors/http.ts +11 -5
  184. package/src/types/ai/index.ts +1 -0
  185. package/src/types/audit/ocsf.ts +14 -0
  186. package/src/types/billing/index.ts +1 -0
  187. package/src/types/billing/usageSource.ts +114 -0
  188. package/src/types/policyGate/errors.ts +1 -0
  189. package/src/types/policyGate/index.test.ts +25 -0
  190. package/src/types/policyGate/index.ts +21 -1
  191. package/src/types/publishPipeline/index.ts +29 -0
  192. package/src/types/publishPipeline/plan.test.ts +34 -9
  193. package/src/types/publishPipeline/plan.ts +41 -19
  194. package/src/types/publishPipeline/policyStatus.ts +44 -0
  195. package/src/types/reviewPolicy/index.ts +18 -4
  196. package/src/types/scim/index.ts +28 -1
  197. package/src/utils/attachment-delivery.test.ts +52 -0
  198. package/src/utils/attachment-delivery.ts +20 -2
  199. package/src/utils/attachment-upload.test.ts +58 -4
  200. package/src/utils/attachment-upload.ts +13 -3
  201. package/src/utils/attachment-validation.test.ts +226 -0
  202. package/src/utils/attachment-validation.ts +269 -0
  203. package/src/utils/index.ts +1 -0
@@ -0,0 +1,169 @@
1
+ import { PDFDocument } from 'pdf-lib';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { AttachmentValidationError, MAX_ARCHIVE_ATTACHMENT_BYTES, MAX_IMAGE_ATTACHMENT_BYTES, MAX_PDF_ATTACHMENT_BYTES, MAX_TEXT_ATTACHMENT_BYTES, assertAttachmentSize, getAttachmentLimitsHelp, getAttachmentUploadPolicy, getAttachmentUploadLimit, validateAttachmentBytes } from './attachment-validation.js';
4
+ const encoder = new TextEncoder();
5
+ async function pdfWithPages(pageCount, useObjectStreams = true) {
6
+ const document = await PDFDocument.create();
7
+ for (let index = 0; index < pageCount; index += 1)
8
+ document.addPage();
9
+ return document.save({ useObjectStreams });
10
+ }
11
+ function expectReason(action, reason) {
12
+ try {
13
+ action();
14
+ throw new Error('expected validation to fail');
15
+ }
16
+ catch (error) {
17
+ expect(error).toBeInstanceOf(AttachmentValidationError);
18
+ if (error instanceof AttachmentValidationError) {
19
+ expect(error.code).toBe('attachment_invalid');
20
+ expect(error.reason).toBe(reason);
21
+ }
22
+ }
23
+ }
24
+ async function expectAsyncReason(action, reason) {
25
+ try {
26
+ await action();
27
+ throw new Error('expected validation to fail');
28
+ }
29
+ catch (error) {
30
+ expect(error).toBeInstanceOf(AttachmentValidationError);
31
+ if (error instanceof AttachmentValidationError)
32
+ expect(error.reason).toBe(reason);
33
+ }
34
+ }
35
+ describe('attachment size policy', () => {
36
+ test.each([
37
+ ['spec.pdf', 'application/pdf', MAX_PDF_ATTACHMENT_BYTES],
38
+ ['photo.jpg', 'image/jpeg', MAX_IMAGE_ATTACHMENT_BYTES],
39
+ ['notes.md', 'text/markdown', MAX_TEXT_ATTACHMENT_BYTES],
40
+ ['bundle.zip', 'application/zip', MAX_ARCHIVE_ATTACHMENT_BYTES],
41
+ ['bundle.tgz', 'application/octet-stream', MAX_ARCHIVE_ATTACHMENT_BYTES],
42
+ ['orders_prod.dump', 'application/octet-stream', MAX_ARCHIVE_ATTACHMENT_BYTES]
43
+ ])('allows the exact limit and rejects one byte more for %s', (fileName, mimeType, limit) => {
44
+ expect(getAttachmentUploadLimit({ fileName, mimeType })).toBe(limit);
45
+ expect(() => assertAttachmentSize({ fileName, mimeType, sizeBytes: limit })).not.toThrow();
46
+ expectReason(() => assertAttachmentSize({ fileName, mimeType, sizeBytes: limit + 1 }), 'too_large');
47
+ });
48
+ test.each([
49
+ ['spec.pdf', 'application/pdf', 4_500_001, 'This PDF is too large. The limit is 4.5 MB. Split it or attach only the pages you need.'],
50
+ [
51
+ 'photo.jpg',
52
+ 'IMAGE/JPEG; charset=binary',
53
+ 3_750_001,
54
+ 'This image is too large. The limit is 3.75 MB after conversion. Resize it or choose a smaller image.'
55
+ ],
56
+ ['notes.md', '', 5_242_881, 'This text/code file is too large. The limit is 5 MiB. Split it or attach only the content you need.'],
57
+ [
58
+ 'bundle.zip',
59
+ 'application/zip',
60
+ 52_428_801,
61
+ 'This archive is too large. The limit is 50 MiB. Remove files from it or split it into smaller archives.'
62
+ ]
63
+ ])('returns only the applicable size error for %s', (fileName, mimeType, sizeBytes, message) => {
64
+ expect(() => assertAttachmentSize({ fileName, mimeType, sizeBytes })).toThrow(message);
65
+ expect(getAttachmentUploadPolicy({ fileName, mimeType }).maxBytes).toBe(sizeBytes - 1);
66
+ });
67
+ test('uses the same policy for MIME types and extension fallback without trusting a conflicting extension', () => {
68
+ expect(getAttachmentUploadPolicy({ fileName: 'SPEC.PDF' })).toEqual(getAttachmentUploadPolicy({ mimeType: 'application/pdf' }));
69
+ expect(getAttachmentUploadPolicy({ fileName: 'spec.pdf', mimeType: 'image/png' }).maxBytes).toBe(3_750_000);
70
+ expect(getAttachmentLimitsHelp(false)).toBe('PDFs: 4.5 MB each, 100 pages total. Images: 3.75 MB after conversion. Text/code: 5 MiB.');
71
+ expect(getAttachmentLimitsHelp(true)).toBe('PDFs: 4.5 MB each, 100 pages total. Images: 3.75 MB after conversion. Text/code: 5 MiB. Supported archives and .dump files: 50 MiB.');
72
+ });
73
+ test('rejects unsupported and Office types', () => {
74
+ expectReason(() => getAttachmentUploadLimit({ fileName: 'logo.svg', mimeType: 'image/svg+xml' }), 'unsupported_type');
75
+ expectReason(() => getAttachmentUploadLimit({ fileName: 'report.docx', mimeType: 'text/plain' }), 'unsupported_type');
76
+ expectReason(() => getAttachmentUploadLimit({ fileName: 'notes.txt', mimeType: 'application/octet-stream' }), 'unsupported_type');
77
+ });
78
+ });
79
+ describe('attachment content validation', () => {
80
+ test.each([
81
+ ['image/jpeg', new Uint8Array([0xff, 0xd8, 0xff])],
82
+ ['image/png', new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])],
83
+ ['image/gif', encoder.encode('GIF89a')],
84
+ ['image/webp', encoder.encode('RIFF0000WEBP')]
85
+ ])('accepts valid %s magic', async (mimeType, bytes) => {
86
+ await expect(validateAttachmentBytes({ mimeType, bytes })).resolves.toEqual({ pdfPages: 0 });
87
+ });
88
+ test.each([
89
+ ['bundle.zip', 'application/zip', new Uint8Array([0x50, 0x4b, 0x05, 0x06])],
90
+ ['bundle.gz', 'application/gzip', new Uint8Array([0x1f, 0x8b])],
91
+ ['bundle.tgz', 'application/x-tgz', new Uint8Array([0x1f, 0x8b])]
92
+ ])('accepts valid archive magic for %s', async (fileName, mimeType, bytes) => {
93
+ await expect(validateAttachmentBytes({ fileName, mimeType, bytes })).resolves.toEqual({ pdfPages: 0 });
94
+ });
95
+ test('accepts postgres custom-format dump magic for .dump files', async () => {
96
+ const bytes = encoder.encode('PGDMP\x01\x0e\x00');
97
+ await expect(validateAttachmentBytes({ fileName: 'orders_prod.dump', mimeType: 'application/octet-stream', bytes })).resolves.toEqual({
98
+ pdfPages: 0
99
+ });
100
+ await expect(validateAttachmentBytes({ fileName: 'orders_prod.dump', mimeType: '', bytes })).resolves.toEqual({ pdfPages: 0 });
101
+ await expect(validateAttachmentBytes({ fileName: 'orders_prod.dump', mimeType: 'text/plain', bytes })).resolves.toEqual({
102
+ pdfPages: 0
103
+ });
104
+ await expectAsyncReason(() => validateAttachmentBytes({
105
+ fileName: 'orders_prod.dump',
106
+ mimeType: 'application/octet-stream',
107
+ bytes: encoder.encode('not a dump')
108
+ }), 'invalid_content');
109
+ });
110
+ test('accepts dump mime type without a .dump filename', async () => {
111
+ const bytes = encoder.encode('PGDMP\x01\x0e\x00');
112
+ await expect(validateAttachmentBytes({ fileName: 'orders', mimeType: 'application/x-postgresql-dump', bytes })).resolves.toEqual({
113
+ pdfPages: 0
114
+ });
115
+ });
116
+ test.each([
117
+ ['bundle.tgz', new Uint8Array([0x1f, 0x8b])],
118
+ ['bundle.gzip', new Uint8Array([0x1f, 0x8b])],
119
+ ['bundle.tar.gz', new Uint8Array([0x1f, 0x8b])],
120
+ ['bundle.zip', new Uint8Array([0x50, 0x4b, 0x05, 0x06])]
121
+ ])('accepts archive filename fallback for generic MIME on %s', async (fileName, bytes) => {
122
+ await expect(validateAttachmentBytes({ fileName, mimeType: 'application/octet-stream', bytes })).resolves.toEqual({ pdfPages: 0 });
123
+ await expectAsyncReason(() => validateAttachmentBytes({ fileName, mimeType: 'application/octet-stream', bytes: encoder.encode('not an archive') }), 'invalid_content');
124
+ });
125
+ test('does not use archive filename fallback for an incompatible declared MIME type', async () => {
126
+ await expectAsyncReason(() => validateAttachmentBytes({ fileName: 'bundle.tgz', mimeType: 'text/plain', bytes: new Uint8Array([0x1f, 0x8b]) }), 'invalid_content');
127
+ });
128
+ test('accepts UTF-8 text and rejects binary or disguised content', async () => {
129
+ await expect(validateAttachmentBytes({ fileName: 'notes.md', mimeType: 'text/markdown', bytes: encoder.encode('hello') })).resolves.toEqual({
130
+ pdfPages: 0
131
+ });
132
+ await expectAsyncReason(() => validateAttachmentBytes({ fileName: 'notes.txt', mimeType: 'text/plain', bytes: new Uint8Array([0xff, 0x00]) }), 'invalid_content');
133
+ await expectAsyncReason(() => validateAttachmentBytes({ fileName: 'notes.txt', mimeType: 'text/plain', bytes: new Uint8Array([0x50, 0x4b, 0x03, 0x04]) }), 'invalid_content');
134
+ });
135
+ test('rejects renamed random bytes for images and archives', async () => {
136
+ await expectAsyncReason(() => validateAttachmentBytes({ mimeType: 'image/png', bytes: encoder.encode('not png') }), 'invalid_content');
137
+ await expectAsyncReason(() => validateAttachmentBytes({ fileName: 'bundle.zip', mimeType: 'application/zip', bytes: encoder.encode('not zip') }), 'invalid_content');
138
+ });
139
+ });
140
+ describe('PDF validation', () => {
141
+ test('counts 100 pages in a compressed object stream PDF', async () => {
142
+ const bytes = await pdfWithPages(100);
143
+ await expect(validateAttachmentBytes({ fileName: 'spec.pdf', mimeType: 'application/pdf', bytes })).resolves.toEqual({ pdfPages: 100 });
144
+ });
145
+ test('rejects 101 pages', async () => {
146
+ const bytes = await pdfWithPages(101, false);
147
+ await expectAsyncReason(() => validateAttachmentBytes({ mimeType: 'application/pdf', bytes }), 'too_many_pdf_pages');
148
+ });
149
+ test('rejects malformed, empty, and encrypted PDFs', async () => {
150
+ await expectAsyncReason(() => validateAttachmentBytes({ mimeType: 'application/pdf', bytes: encoder.encode('%PDF-broken') }), 'invalid_content');
151
+ await expectAsyncReason(() => validateAttachmentBytes({ mimeType: 'application/pdf', bytes: new Uint8Array() }), 'invalid_content');
152
+ const encrypted = await PDFDocument.create();
153
+ encrypted.addPage();
154
+ encrypted.context.trailerInfo.Encrypt = encrypted.context.register(encrypted.context.obj({ Filter: 'Standard', V: 1 }));
155
+ const encryptedBytes = await encrypted.save();
156
+ await expectAsyncReason(() => validateAttachmentBytes({ mimeType: 'application/pdf', bytes: encryptedBytes }), 'invalid_content');
157
+ });
158
+ test('rejects a cyclic page tree without recursive traversal', async () => {
159
+ const cyclic = await PDFDocument.create();
160
+ cyclic.addPage();
161
+ const pages = cyclic.catalog.Pages();
162
+ const pagesReference = cyclic.context.getObjectRef(pages);
163
+ if (!pagesReference)
164
+ throw new Error('expected page tree reference');
165
+ pages.Kids().push(pagesReference);
166
+ await expectAsyncReason(async () => validateAttachmentBytes({ mimeType: 'application/pdf', bytes: await cyclic.save() }), 'invalid_content');
167
+ });
168
+ });
169
+ //# sourceMappingURL=attachment-validation.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-validation.test.js","sourceRoot":"","sources":["../../src/utils/attachment-validation.test.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  export * from './apiv2.js';
2
2
  export * from './attachment-delivery.js';
3
3
  export * from './attachment-upload.js';
4
+ export * from './attachment-validation.js';
4
5
  export * from './bytestring.js';
5
6
  export * from './configuration.js';
6
7
  export * from './env.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AAEzB,OAAO,MAAM,MAAM,2BAA2B,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AAEzB,OAAO,MAAM,MAAM,2BAA2B,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -1,6 +1,7 @@
1
1
  export * from './apiv2.js';
2
2
  export * from './attachment-delivery.js';
3
3
  export * from './attachment-upload.js';
4
+ export * from './attachment-validation.js';
4
5
  export * from './bytestring.js';
5
6
  export * from './configuration.js';
6
7
  export * from './env.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AAEzB,OAAO,MAAM,MAAM,2BAA2B,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AAEzB,OAAO,MAAM,MAAM,2BAA2B,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superblocksteam/shared",
3
- "version": "0.9613.2",
3
+ "version": "0.9614.0",
4
4
  "description": "Superblocks Shared Resources",
5
5
  "license": "Superblocks Community Software License",
6
6
  "repository": {
@@ -39,6 +39,7 @@
39
39
  "json5": "2.2.3",
40
40
  "lodash": "^4.17.21",
41
41
  "normalizr": "3.6.2",
42
+ "pdf-lib": "1.17.1",
42
43
  "semver": "^7.6.0",
43
44
  "slugify": "^1.6.6",
44
45
  "uuid": "^11.1.1"
@@ -48,7 +49,7 @@
48
49
  "@types/lodash": "^4.17.0",
49
50
  "@types/node": "^24",
50
51
  "@types/semver": "^7.5.6",
51
- "@vitest/coverage-v8": "^4.0.17",
52
+ "@vitest/coverage-v8": "^4.1.11",
52
53
  "chokidar-cli": "3.0.0",
53
54
  "eslint": "^9.39.2",
54
55
  "eslint-config-prettier": "^10.1.8",
@@ -59,7 +60,7 @@
59
60
  "globals": "^17.3.0",
60
61
  "typescript": "^6.0.3",
61
62
  "typescript-eslint": "^8.59.2",
62
- "vitest": "^4.0.17"
63
+ "vitest": "^4.1.11"
63
64
  },
64
65
  "engines": {
65
66
  "node": ">=24",
@@ -23,6 +23,25 @@ export type DestructiveMigrationFile = {
23
23
  sql: string;
24
24
  };
25
25
 
26
+ /**
27
+ * What publishing one version would delete, asked before anything runs.
28
+ *
29
+ * The server answers this both on its own endpoint, before a publish starts,
30
+ * and through the publish checklist once one is under way. Both answers come
31
+ * from the same scan, so they say the same thing.
32
+ */
33
+ export type DestructiveMigrationGate = {
34
+ /** One row per change that removes something, so the user sees what goes. */
35
+ findings: DestructiveMigrationFinding[];
36
+ /**
37
+ * Publishing must be confirmed first: either the changes remove data, or
38
+ * they could not be checked and might.
39
+ */
40
+ needsConfirmation: boolean;
41
+ /** Nobody could check these changes, so the question has to be hedged. */
42
+ scanUnavailable: boolean;
43
+ };
44
+
26
45
  const MAX_EXCERPT_LENGTH = 160;
27
46
 
28
47
  /**
@@ -54,6 +54,7 @@ export function isLifecycleManagedIntegrationAllowedForApiTrigger(
54
54
  export const DATABASE_LIFECYCLE_TAG_OPERATIONS = 'databaseLifecycle:operations';
55
55
  export const DATABASE_LIFECYCLE_TAG_ENGINES = 'databaseLifecycle:engines';
56
56
  export const DATABASE_LIFECYCLE_TAG_CAPABILITIES = 'databaseLifecycle:capabilities';
57
+ export const DATABASE_LIFECYCLE_TAG_PHYSICAL_CONFIG_FINGERPRINT = 'databaseLifecycle:physicalConfigFingerprint';
57
58
  export {
58
59
  CapacityPolicyValidationError,
59
60
  DATABASE_ENGINES,
@@ -372,6 +373,9 @@ export type PhysicalDatabaseInstance = {
372
373
  // Stable customer OPA deployment identity that owns this physical pool.
373
374
  // Absent on legacy rows (no backfill); agent-scoped lookups skip those.
374
375
  agentName?: string;
376
+ // Hash of the normalized physical Terraform configuration. Absent on legacy
377
+ // rows until registration or a successful physical-pool apply stamps it.
378
+ physicalConfigFingerprint?: string;
375
379
  metadata: Record<string, unknown>;
376
380
  created?: Date;
377
381
  updated?: Date;
@@ -396,7 +400,12 @@ export {
396
400
  DESTRUCTIVE_MIGRATION_ACK_REQUIRED_MESSAGE,
397
401
  DESTRUCTIVE_MIGRATION_SCAN_UNAVAILABLE
398
402
  } from './destructiveMigrations.js';
399
- export type { DestructiveMigrationFile, DestructiveMigrationFinding, DestructiveMigrationKind } from './destructiveMigrations.js';
403
+ export type {
404
+ DestructiveMigrationFile,
405
+ DestructiveMigrationFinding,
406
+ DestructiveMigrationGate,
407
+ DestructiveMigrationKind
408
+ } from './destructiveMigrations.js';
400
409
 
401
410
  export type LifecycleDispatchContinuation = {
402
411
  currentState?: string;
@@ -431,11 +440,13 @@ export type PhysicalPoolProvisioningError = {
431
440
 
432
441
  // Stable wizard/admin response for POST/GET physical-pool-provisioning.
433
442
  export type PhysicalPoolProvisioningState = {
443
+ applyRunId?: string;
434
444
  alreadyInProgress?: boolean;
435
445
  completedAt?: string;
436
446
  error?: PhysicalPoolProvisioningError;
437
447
  physicalDatabaseInstanceId?: string;
438
448
  provisioningId?: string;
449
+ retryRequestId?: string;
439
450
  startedAt?: string;
440
451
  startedBy?: PhysicalPoolProvisioningActor;
441
452
  status: PhysicalPoolProvisioningStatus;
@@ -497,6 +508,7 @@ export type LifecycleDispatchPayload = {
497
508
  // server supplies rather than anything a caller picks, and it goes away with
498
509
  // the worker's remaining shims in ENG-5127.
499
510
  environment?: string;
511
+ metadata?: { physicalConfigFingerprint?: string };
500
512
  migrations?: LifecycleMigration[];
501
513
  operation: LifecycleOperation;
502
514
  profile: string;
package/src/dbfs/types.ts CHANGED
@@ -26,11 +26,23 @@ export type DirectoryEntryWithPath = {
26
26
  path: string;
27
27
  };
28
28
 
29
- export const codeModeSyncExcludedDirectories = ['.git', '.superblocks', 'build', 'integration_cache', 'node_modules'];
30
-
31
- // The CLI omits these paths from downloads, and the server rejects them at CLI upload boundaries.
29
+ // Directory basenames skipped on DBFS upload/hash. `dist` is fullstack
30
+ // `tsc --build` emit (`outDir: ./dist/client|server`); runtime never reads it,
31
+ // but local-dev and in-tree typecheck can create it.
32
+ export const codeModeSyncExcludedDirectories = ['.git', '.superblocks', 'build', 'dist', 'integration_cache', 'node_modules'];
33
+
34
+ // The server rejects these names at CLI/live-edit PUT boundaries. Keep this
35
+ // list frozen for rollout compatibility: a pinned older CLI still hashes
36
+ // listings that include `dist`, so rejecting it would 400 the whole upload
37
+ // and the client's local directory hash would no longer resolve.
32
38
  export const codeModeDownloadExcludedDirectories = ['.git', 'build', 'integration_cache', 'node_modules'];
33
39
 
40
+ // Live-edit/local-dev download and archive skip these even when a historical
41
+ // DBFS tree still contains them. `dist` is download-skipped without being
42
+ // PUT-rejected so new sandboxes stop fetching polluted trees while old
43
+ // clients can still persist a listing that names dist.
44
+ export const codeModeDownloadSkippedDirectories = ['.git', 'build', 'dist', 'integration_cache', 'node_modules'];
45
+
34
46
  // Types for the batch put method
35
47
 
36
48
  interface PutManyItemFileContents {
@@ -1,12 +1,18 @@
1
+ import type { ScimErrorType } from '../types/scim/index.js';
2
+
1
3
  export class HttpError extends Error {
2
4
  status: number;
3
5
  message: string;
4
6
  title?: string;
5
- constructor(status: number, message: string, title?: string) {
7
+ // RFC 7644 section 3.12 keyword, set at the throw site because one error class covers several
8
+ // keywords. Only SCIM error rendering reads it.
9
+ scimType?: ScimErrorType;
10
+ constructor(status: number, message: string, title?: string, scimType?: ScimErrorType) {
6
11
  super(message);
7
12
  this.status = status;
8
13
  this.message = message;
9
14
  this.title = title;
15
+ this.scimType = scimType;
10
16
  }
11
17
  }
12
18
 
@@ -23,8 +29,8 @@ export abstract class RetryableHttpError extends HttpError {
23
29
  }
24
30
 
25
31
  export class BadRequestError extends HttpError {
26
- constructor(message: string) {
27
- super(400, message, 'Bad request');
32
+ constructor(message: string, scimType?: ScimErrorType) {
33
+ super(400, message, 'Bad request', scimType);
28
34
  }
29
35
  }
30
36
 
@@ -85,8 +91,8 @@ export class RbacUnauthorizedError extends UnauthorizedError {
85
91
  }
86
92
 
87
93
  export class ConflictError extends HttpError {
88
- constructor(message: string) {
89
- super(409, message, 'Conflict occurred.');
94
+ constructor(message: string, scimType?: ScimErrorType) {
95
+ super(409, message, 'Conflict occurred.', scimType);
90
96
  }
91
97
  }
92
98
 
@@ -72,6 +72,7 @@ export enum AiSystemTriggerType {
72
72
  ATTACHMENT_LABEL = 'attachment_label',
73
73
  COMMIT_MESSAGE = 'commit_message',
74
74
  DASHBOARD_CHAT = 'dashboard_chat',
75
+ DESTRUCTIVE_MIGRATION_SUMMARY = 'destructive_migration_summary',
75
76
  ERROR_SUMMARY = 'error_summary',
76
77
  FACT_METADATA = 'fact_metadata',
77
78
  PR_DESCRIPTION = 'pr_description'
@@ -393,6 +393,13 @@ export interface TriageVerdict {
393
393
  verdict: 'exploitable' | 'not_exploitable' | 'uncertain';
394
394
  }
395
395
 
396
+ export type NpmInstallTriageResult =
397
+ | TriageVerdict
398
+ | {
399
+ advisoryId: string;
400
+ unavailable: true;
401
+ };
402
+
396
403
  /**
397
404
  * Wire payload for the historical `v1.audit.npmInstallBlocked` socket method.
398
405
  *
@@ -451,11 +458,18 @@ export interface NpmInstallBlockedAuditReport {
451
458
  };
452
459
  /** Provisional install-time exploitability decision for a CVE finding. */
453
460
  triage?: TriageVerdict;
461
+ /** Analysis results attached to the blocked install attempt. */
462
+ triageVerdicts?: NpmInstallTriageResult[];
454
463
  /**
455
464
  * Builder-approved proceed-with-reason override when `reason ===
456
465
  * "vulnerability_user_override"`. Server scrubs `reason` and caps lengths.
457
466
  */
458
467
  userOverride?: {
468
+ advisories?: Array<{
469
+ advisoryId: string;
470
+ package: string;
471
+ severity?: string;
472
+ }>;
459
473
  packages: string[];
460
474
  reason: string;
461
475
  };
@@ -2,3 +2,4 @@ export * from './freeCreditsTier.js';
2
2
  export * from './pricingModel.js';
3
3
  export * from './billing.js';
4
4
  export * from './spendAlert.js';
5
+ export * from './usageSource.js';
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Where a GAU debit was drawn from. Stamped on each ai_token_usage row at
3
+ * recording time and never rewritten, so rows keep the source of the plan
4
+ * that was active when the usage happened, not the org's current plan.
5
+ *
6
+ * `topup` is the auto/manual ai_credits_topup source (TEAMS and the POC
7
+ * refill flow). `on_demand` is the enterprise on-demand bucket. `byo` is
8
+ * bring-your-own-key usage, which consumes no GAUs.
9
+ */
10
+ export type UsageSource = 'byo' | 'enterprise' | 'legacy' | 'on_demand' | 'overage_unbilled' | 'pool' | 'seat' | 'topup' | 'trial';
11
+
12
+ // Keyed on the UsageSource union so this is compile-time exhaustive: adding a
13
+ // new UsageSource member fails to build here until the author decides whether
14
+ // the Usage page shows it (add it below) or hides it (add it to the Exclude).
15
+ // That decision can never again be made implicitly by a stale per-plan list.
16
+ const GAU_DISPLAY_SOURCE_EXHAUSTIVE: Record<Exclude<UsageSource, 'byo'>, true> = {
17
+ enterprise: true,
18
+ legacy: true,
19
+ on_demand: true,
20
+ overage_unbilled: true,
21
+ pool: true,
22
+ seat: true,
23
+ topup: true,
24
+ trial: true
25
+ };
26
+
27
+ /**
28
+ * Every usage_source the Usage page's sub-banner surfaces (tiles, chart,
29
+ * usage table, Spending page) display: all GAU-consuming sources, excluding
30
+ * only `byo`.
31
+ */
32
+ export const GAU_DISPLAY_USAGE_SOURCES: readonly UsageSource[] = Object.keys(GAU_DISPLAY_SOURCE_EXHAUSTIVE) as UsageSource[];
33
+
34
+ // Keyed on the whole UsageSource union, so adding a source fails the build
35
+ // until someone decides whether it draws on a contract commitment. The
36
+ // previous rule was a denylist ("committed = everything except on_demand /
37
+ // overage_unbilled / topup"), which made "counts against your commitment" the
38
+ // silent default for every future source -- and swept in `trial`, which
39
+ // debits no entitlement at all.
40
+ const CONTRACT_COMMITTED_EXHAUSTIVE: Record<UsageSource, boolean> = {
41
+ // Consumes no GAUs -- the customer is paying their own model provider.
42
+ byo: false,
43
+ // Pooled enterprise / POC consumption.
44
+ enterprise: true,
45
+ // Rows predating the usage_source column; they were pool draws.
46
+ legacy: true,
47
+ // Overage beyond the commitment, invoiced separately at a premium.
48
+ on_demand: false,
49
+ // Recorded past the org cap and deliberately never billed.
50
+ overage_unbilled: false,
51
+ // The shared-pool slice for granular enterprise contracts and TEAMS.
52
+ pool: true,
53
+ // Per-seat allocation; its grants are inside the commit denominator.
54
+ seat: true,
55
+ // Separately purchased pool. The banner backs topups out of the commit
56
+ // denominator, so counting their spend in the numerator would push
57
+ // utilisation past 100%.
58
+ topup: false,
59
+ // Free trial allowance. The trial write path debits no entitlement, so this
60
+ // usage never drew on a commitment even when it falls inside the term.
61
+ // Exposure was durable on annual / contract_term cycles, where the window is
62
+ // the whole contract term; monthly cycles only mis-counted the conversion
63
+ // month and then self-healed (deriveEnterpriseCalendarPeriod).
64
+ trial: false
65
+ };
66
+
67
+ /**
68
+ * The usage_source values that count as drawing down a contract commitment.
69
+ *
70
+ * Feeds the plan banner's committed-vs-overage split. Deliberately an
71
+ * allowlist: see the note on {@link CONTRACT_COMMITTED_EXHAUSTIVE}.
72
+ */
73
+ export const CONTRACT_COMMITTED_USAGE_SOURCES: readonly UsageSource[] = Object.entries(CONTRACT_COMMITTED_EXHAUSTIVE)
74
+ .filter(([, isCommitted]) => isCommitted)
75
+ .map(([source]) => source as UsageSource);
76
+
77
+ // The sources an enterprise contract bills, used for the entitlement-window
78
+ // math on /usage-records. Broader than CONTRACT_COMMITTED_USAGE_SOURCES: that
79
+ // one answers "did this draw down the commitment", this one answers "is this
80
+ // on the contract at all", so overage and top-up consumption count here and
81
+ // trial still does not. Keyed on the union for the same reason as the others --
82
+ // a new source must be classified rather than silently omitted from overage.
83
+ const ENTERPRISE_CONTRACT_EXHAUSTIVE: Record<UsageSource, boolean> = {
84
+ byo: false,
85
+ enterprise: true,
86
+ legacy: true,
87
+ on_demand: true,
88
+ overage_unbilled: true,
89
+ pool: true,
90
+ seat: true,
91
+ topup: true,
92
+ trial: false
93
+ };
94
+
95
+ export const ENTERPRISE_CONTRACT_USAGE_SOURCES: readonly UsageSource[] = Object.entries(ENTERPRISE_CONTRACT_EXHAUSTIVE)
96
+ .filter(([, onContract]) => onContract)
97
+ .map(([source]) => source as UsageSource);
98
+
99
+ // The sources that draw on a spendable balance rather than a commitment.
100
+ // Expressed against CONTRACT_COMMITTED so the one deliberate difference stays
101
+ // visible: top-ups are separately purchased pool, counted here because the
102
+ // balance they feed includes them, and excluded from the commitment because
103
+ // the banner backs them out of its denominator.
104
+ const POOL_LIKE_EXHAUSTIVE: Record<UsageSource, boolean> = {
105
+ ...CONTRACT_COMMITTED_EXHAUSTIVE,
106
+ // Pre-column rows. They predate every current balance, so no live balance
107
+ // shows them.
108
+ legacy: false,
109
+ topup: true
110
+ };
111
+
112
+ export const POOL_LIKE_USAGE_SOURCES: readonly UsageSource[] = Object.entries(POOL_LIKE_EXHAUSTIVE)
113
+ .filter(([, isPoolLike]) => isPoolLike)
114
+ .map(([source]) => source as UsageSource);
@@ -41,6 +41,7 @@ export const POLICY_GATE_ERROR_CODES = {
41
41
 
42
42
  // Server dispatch and stale-run failures.
43
43
  canceled: { category: 'user_action' }, // The user canceled the in-flight publish.
44
+ dependency_scan_stalled: { category: 'platform_transient' }, // The in-process dependency scan never completed.
44
45
  dev_server_unreachable: { category: 'platform_transient' }, // The dev server did not finish the run.
45
46
  dispatch_failed: { category: 'platform_transient' }, // The server could not start the run.
46
47
 
@@ -0,0 +1,25 @@
1
+ import { describe, expect, test } from 'vitest';
2
+
3
+ import { isLockfilePath } from './index.js';
4
+
5
+ describe('isLockfilePath', () => {
6
+ test('recognizes known lockfile basenames', () => {
7
+ expect(isLockfilePath('pnpm-lock.yaml')).toBe(true);
8
+ expect(isLockfilePath('yarn.lock')).toBe(true);
9
+ expect(isLockfilePath('package-lock.json')).toBe(true);
10
+ expect(isLockfilePath('bun.lock')).toBe(true);
11
+ expect(isLockfilePath('bun.lockb')).toBe(true);
12
+ });
13
+
14
+ test('recognizes a lockfile nested in a directory', () => {
15
+ expect(isLockfilePath('server/pnpm-lock.yaml')).toBe(true);
16
+ });
17
+
18
+ test('rejects a non-lockfile path', () => {
19
+ expect(isLockfilePath('server/apis/fetch-url.ts')).toBe(false);
20
+ });
21
+
22
+ test('rejects a path that merely contains a lockfile name as a substring', () => {
23
+ expect(isLockfilePath('vendor/yarn.lock.bak')).toBe(false);
24
+ });
25
+ });