@superblocksteam/shared 0.9613.2 → 0.9614.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/database-lifecycle/destructiveMigrations.d.ts +18 -0
- package/dist/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
- package/dist/database-lifecycle/destructiveMigrations.js.map +1 -1
- package/dist/database-lifecycle/index.d.ts +8 -1
- package/dist/database-lifecycle/index.d.ts.map +1 -1
- package/dist/database-lifecycle/index.js +2 -1
- package/dist/database-lifecycle/index.js.map +1 -1
- package/dist/dbfs/types.d.ts +1 -0
- package/dist/dbfs/types.d.ts.map +1 -1
- package/dist/dbfs/types.js +14 -3
- package/dist/dbfs/types.js.map +1 -1
- package/dist/errors/http.d.ts +5 -3
- package/dist/errors/http.d.ts.map +1 -1
- package/dist/errors/http.js +9 -5
- package/dist/errors/http.js.map +1 -1
- package/dist/types/ai/index.d.ts +1 -0
- package/dist/types/ai/index.d.ts.map +1 -1
- package/dist/types/ai/index.js +1 -0
- package/dist/types/ai/index.js.map +1 -1
- package/dist/types/audit/ocsf.d.ts +11 -0
- package/dist/types/audit/ocsf.d.ts.map +1 -1
- package/dist/types/billing/index.d.ts +1 -0
- package/dist/types/billing/index.d.ts.map +1 -1
- package/dist/types/billing/index.js +1 -0
- package/dist/types/billing/index.js.map +1 -1
- package/dist/types/billing/usageSource.d.ts +26 -0
- package/dist/types/billing/usageSource.d.ts.map +1 -0
- package/dist/types/billing/usageSource.js +100 -0
- package/dist/types/billing/usageSource.js.map +1 -0
- package/dist/types/policyGate/errors.d.ts +3 -0
- package/dist/types/policyGate/errors.d.ts.map +1 -1
- package/dist/types/policyGate/errors.js +1 -0
- package/dist/types/policyGate/errors.js.map +1 -1
- package/dist/types/policyGate/index.d.ts +5 -1
- package/dist/types/policyGate/index.d.ts.map +1 -1
- package/dist/types/policyGate/index.js +11 -0
- package/dist/types/policyGate/index.js.map +1 -1
- package/dist/types/policyGate/index.test.d.ts +2 -0
- package/dist/types/policyGate/index.test.d.ts.map +1 -0
- package/dist/types/policyGate/index.test.js +23 -0
- package/dist/types/policyGate/index.test.js.map +1 -0
- package/dist/types/publishPipeline/index.d.ts +27 -0
- package/dist/types/publishPipeline/index.d.ts.map +1 -1
- package/dist/types/publishPipeline/index.js +1 -0
- package/dist/types/publishPipeline/index.js.map +1 -1
- package/dist/types/publishPipeline/plan.d.ts +9 -4
- package/dist/types/publishPipeline/plan.d.ts.map +1 -1
- package/dist/types/publishPipeline/plan.js +37 -20
- package/dist/types/publishPipeline/plan.js.map +1 -1
- package/dist/types/publishPipeline/plan.test.js +28 -8
- package/dist/types/publishPipeline/plan.test.js.map +1 -1
- package/dist/types/publishPipeline/policyStatus.d.ts +7 -0
- package/dist/types/publishPipeline/policyStatus.d.ts.map +1 -0
- package/dist/types/publishPipeline/policyStatus.js +40 -0
- package/dist/types/publishPipeline/policyStatus.js.map +1 -0
- package/dist/types/reviewPolicy/index.d.ts +14 -4
- package/dist/types/reviewPolicy/index.d.ts.map +1 -1
- package/dist/types/reviewPolicy/index.js +11 -5
- package/dist/types/reviewPolicy/index.js.map +1 -1
- package/dist/types/scim/index.d.ts +21 -1
- package/dist/types/scim/index.d.ts.map +1 -1
- package/dist/types/scim/index.js +19 -1
- package/dist/types/scim/index.js.map +1 -1
- package/dist/utils/attachment-delivery.d.ts +7 -0
- package/dist/utils/attachment-delivery.d.ts.map +1 -1
- package/dist/utils/attachment-delivery.js +19 -2
- package/dist/utils/attachment-delivery.js.map +1 -1
- package/dist/utils/attachment-delivery.test.d.ts +2 -0
- package/dist/utils/attachment-delivery.test.d.ts.map +1 -0
- package/dist/utils/attachment-delivery.test.js +40 -0
- package/dist/utils/attachment-delivery.test.js.map +1 -0
- package/dist/utils/attachment-upload.d.ts +3 -2
- package/dist/utils/attachment-upload.d.ts.map +1 -1
- package/dist/utils/attachment-upload.js +11 -3
- package/dist/utils/attachment-upload.js.map +1 -1
- package/dist/utils/attachment-upload.test.js +40 -1
- package/dist/utils/attachment-upload.test.js.map +1 -1
- package/dist/utils/attachment-validation.d.ts +64 -0
- package/dist/utils/attachment-validation.d.ts.map +1 -0
- package/dist/utils/attachment-validation.js +269 -0
- package/dist/utils/attachment-validation.js.map +1 -0
- package/dist/utils/attachment-validation.test.d.ts +2 -0
- package/dist/utils/attachment-validation.test.d.ts.map +1 -0
- package/dist/utils/attachment-validation.test.js +171 -0
- package/dist/utils/attachment-validation.test.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.d.ts +18 -0
- package/dist-esm/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.js.map +1 -1
- package/dist-esm/database-lifecycle/index.d.ts +8 -1
- package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/index.js +1 -0
- package/dist-esm/database-lifecycle/index.js.map +1 -1
- package/dist-esm/dbfs/types.d.ts +1 -0
- package/dist-esm/dbfs/types.d.ts.map +1 -1
- package/dist-esm/dbfs/types.js +13 -2
- package/dist-esm/dbfs/types.js.map +1 -1
- package/dist-esm/errors/http.d.ts +5 -3
- package/dist-esm/errors/http.d.ts.map +1 -1
- package/dist-esm/errors/http.js +9 -5
- package/dist-esm/errors/http.js.map +1 -1
- package/dist-esm/types/ai/index.d.ts +1 -0
- package/dist-esm/types/ai/index.d.ts.map +1 -1
- package/dist-esm/types/ai/index.js +1 -0
- package/dist-esm/types/ai/index.js.map +1 -1
- package/dist-esm/types/audit/ocsf.d.ts +11 -0
- package/dist-esm/types/audit/ocsf.d.ts.map +1 -1
- package/dist-esm/types/billing/index.d.ts +1 -0
- package/dist-esm/types/billing/index.d.ts.map +1 -1
- package/dist-esm/types/billing/index.js +1 -0
- package/dist-esm/types/billing/index.js.map +1 -1
- package/dist-esm/types/billing/usageSource.d.ts +26 -0
- package/dist-esm/types/billing/usageSource.d.ts.map +1 -0
- package/dist-esm/types/billing/usageSource.js +97 -0
- package/dist-esm/types/billing/usageSource.js.map +1 -0
- package/dist-esm/types/policyGate/errors.d.ts +3 -0
- package/dist-esm/types/policyGate/errors.d.ts.map +1 -1
- package/dist-esm/types/policyGate/errors.js +1 -0
- package/dist-esm/types/policyGate/errors.js.map +1 -1
- package/dist-esm/types/policyGate/index.d.ts +5 -1
- package/dist-esm/types/policyGate/index.d.ts.map +1 -1
- package/dist-esm/types/policyGate/index.js +10 -0
- package/dist-esm/types/policyGate/index.js.map +1 -1
- package/dist-esm/types/policyGate/index.test.d.ts +2 -0
- package/dist-esm/types/policyGate/index.test.d.ts.map +1 -0
- package/dist-esm/types/policyGate/index.test.js +21 -0
- package/dist-esm/types/policyGate/index.test.js.map +1 -0
- package/dist-esm/types/publishPipeline/index.d.ts +27 -0
- package/dist-esm/types/publishPipeline/index.d.ts.map +1 -1
- package/dist-esm/types/publishPipeline/index.js +1 -0
- package/dist-esm/types/publishPipeline/index.js.map +1 -1
- package/dist-esm/types/publishPipeline/plan.d.ts +9 -4
- package/dist-esm/types/publishPipeline/plan.d.ts.map +1 -1
- package/dist-esm/types/publishPipeline/plan.js +36 -19
- package/dist-esm/types/publishPipeline/plan.js.map +1 -1
- package/dist-esm/types/publishPipeline/plan.test.js +29 -9
- package/dist-esm/types/publishPipeline/plan.test.js.map +1 -1
- package/dist-esm/types/publishPipeline/policyStatus.d.ts +7 -0
- package/dist-esm/types/publishPipeline/policyStatus.d.ts.map +1 -0
- package/dist-esm/types/publishPipeline/policyStatus.js +37 -0
- package/dist-esm/types/publishPipeline/policyStatus.js.map +1 -0
- package/dist-esm/types/reviewPolicy/index.d.ts +14 -4
- package/dist-esm/types/reviewPolicy/index.d.ts.map +1 -1
- package/dist-esm/types/reviewPolicy/index.js +10 -4
- package/dist-esm/types/reviewPolicy/index.js.map +1 -1
- package/dist-esm/types/scim/index.d.ts +21 -1
- package/dist-esm/types/scim/index.d.ts.map +1 -1
- package/dist-esm/types/scim/index.js +18 -0
- package/dist-esm/types/scim/index.js.map +1 -1
- package/dist-esm/utils/attachment-delivery.d.ts +7 -0
- package/dist-esm/utils/attachment-delivery.d.ts.map +1 -1
- package/dist-esm/utils/attachment-delivery.js +17 -2
- package/dist-esm/utils/attachment-delivery.js.map +1 -1
- package/dist-esm/utils/attachment-delivery.test.d.ts +2 -0
- package/dist-esm/utils/attachment-delivery.test.d.ts.map +1 -0
- package/dist-esm/utils/attachment-delivery.test.js +38 -0
- package/dist-esm/utils/attachment-delivery.test.js.map +1 -0
- package/dist-esm/utils/attachment-upload.d.ts +3 -2
- package/dist-esm/utils/attachment-upload.d.ts.map +1 -1
- package/dist-esm/utils/attachment-upload.js +10 -2
- package/dist-esm/utils/attachment-upload.js.map +1 -1
- package/dist-esm/utils/attachment-upload.test.js +42 -3
- package/dist-esm/utils/attachment-upload.test.js.map +1 -1
- package/dist-esm/utils/attachment-validation.d.ts +64 -0
- package/dist-esm/utils/attachment-validation.d.ts.map +1 -0
- package/dist-esm/utils/attachment-validation.js +259 -0
- package/dist-esm/utils/attachment-validation.js.map +1 -0
- package/dist-esm/utils/attachment-validation.test.d.ts +2 -0
- package/dist-esm/utils/attachment-validation.test.d.ts.map +1 -0
- package/dist-esm/utils/attachment-validation.test.js +169 -0
- package/dist-esm/utils/attachment-validation.test.js.map +1 -0
- package/dist-esm/utils/index.d.ts +1 -0
- package/dist-esm/utils/index.d.ts.map +1 -1
- package/dist-esm/utils/index.js +1 -0
- package/dist-esm/utils/index.js.map +1 -1
- package/package.json +4 -3
- package/src/database-lifecycle/destructiveMigrations.ts +19 -0
- package/src/database-lifecycle/index.ts +13 -1
- package/src/dbfs/types.ts +15 -3
- package/src/errors/http.ts +11 -5
- package/src/types/ai/index.ts +1 -0
- package/src/types/audit/ocsf.ts +14 -0
- package/src/types/billing/index.ts +1 -0
- package/src/types/billing/usageSource.ts +114 -0
- package/src/types/policyGate/errors.ts +1 -0
- package/src/types/policyGate/index.test.ts +25 -0
- package/src/types/policyGate/index.ts +21 -1
- package/src/types/publishPipeline/index.ts +29 -0
- package/src/types/publishPipeline/plan.test.ts +34 -9
- package/src/types/publishPipeline/plan.ts +41 -19
- package/src/types/publishPipeline/policyStatus.ts +44 -0
- package/src/types/reviewPolicy/index.ts +18 -4
- package/src/types/scim/index.ts +28 -1
- package/src/utils/attachment-delivery.test.ts +52 -0
- package/src/utils/attachment-delivery.ts +20 -2
- package/src/utils/attachment-upload.test.ts +58 -4
- package/src/utils/attachment-upload.ts +13 -3
- package/src/utils/attachment-validation.test.ts +226 -0
- package/src/utils/attachment-validation.ts +269 -0
- 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 +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"}
|
package/dist-esm/utils/index.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.9614.1",
|
|
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.
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
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 {
|
package/src/errors/http.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/src/types/ai/index.ts
CHANGED
|
@@ -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'
|
package/src/types/audit/ocsf.ts
CHANGED
|
@@ -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
|
};
|
|
@@ -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
|
+
});
|