@@ -41,6 +41,7 @@ import {
4141 AppVersionMetadata ,
4242 DocumentContent ,
4343 MAX_APP_BYTES ,
44+ MAX_APPROVAL_PACKAGE_BYTES ,
4445 MATERIAL_KIND ,
4546 ArtifactKindWrite ,
4647 DesignBundle ,
@@ -138,6 +139,11 @@ import {
138139 materialContent ,
139140 materialMimeType ,
140141} from "./materials.ts" ;
142+ import {
143+ parseApprovalPackageBundle ,
144+ readProjectPackageSource ,
145+ validateApprovalPackage ,
146+ } from "./approval-package.ts" ;
141147import { requestErrorMetadata } from "../request-error.ts" ;
142148
143149const log = getLogger ( [ "corbits-builder" , "hub" , "artifacts" ] ) ;
@@ -151,8 +157,8 @@ const APPROVAL_COVERS: Record<string, (kind: string) => boolean> = {
151157 kind !== "app" && kind !== "design" && kind !== "design_option" ,
152158 instruct_coder : ( kind ) => kind !== "app" && kind !== "design_option" ,
153159} ;
154- // Whole documents, the filed app among them (up to 1 MiB of source) .
155- const MAX_READ_CHARS = 1024 * 1024 ;
160+ // Whole documents, the filed app and approval package among them .
161+ const MAX_READ_CHARS = MAX_APPROVAL_PACKAGE_BYTES ;
156162const RECENT_FAILURES = 5 ;
157163const RunError = type ( { "+" : "delete" , message : "string" , "code?" : "string" } ) ;
158164const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" ;
@@ -184,6 +190,68 @@ async function isDirectory(path: string) {
184190
185191const artifactResource = ( kind : string ) => "sb-artifact:" + kind ;
186192
193+ function validateArtifactWriteContent (
194+ input : typeof ArtifactKindWrite . infer ,
195+ ) : boolean {
196+ const bundled =
197+ input . kind === "design" ||
198+ input . kind === "design_option" ||
199+ input . kind === "app" ||
200+ input . kind === "approval_package" ;
201+ if ( bundled && input . openQuestions )
202+ throw new ProjectError ( 400 , "invalid_artifact_write" , {
203+ issues : "openQuestions belong to documents, not a " + input . kind ,
204+ } ) ;
205+ if ( input . approaches ) {
206+ if ( input . kind !== "approaches" )
207+ throw new ProjectError ( 400 , "invalid_artifact_write" , {
208+ issues :
209+ "approaches belong to the approaches document, not a " + input . kind ,
210+ } ) ;
211+ const issue = approachesIssue ( input . approaches ) ;
212+ if ( issue )
213+ throw new ProjectError ( 400 , "invalid_artifact_write" , {
214+ issues : issue ,
215+ } ) ;
216+ }
217+ if (
218+ input . kind === "design" ||
219+ input . kind === "design_option" ||
220+ input . kind === "app"
221+ ) {
222+ const bundle = ( input . kind === "app" ? AppBundle : DesignBundle ) (
223+ parseJson ( input . content ) ,
224+ ) ;
225+ if ( bundle instanceof type . errors )
226+ throw new ProjectError ( 400 , "invalid_" + input . kind , {
227+ issues : bundle . summary ,
228+ } ) ;
229+ }
230+ if ( input . kind === "app" ) {
231+ const bytes = Buffer . byteLength ( input . content ) ;
232+ if ( bytes > MAX_APP_BYTES )
233+ throw new ProjectError ( 413 , "app_too_large" , {
234+ bytes,
235+ limit : MAX_APP_BYTES ,
236+ } ) ;
237+ } else if ( input . kind === "approval_package" ) {
238+ const bytes = Buffer . byteLength ( input . content ) ;
239+ if ( bytes > MAX_APPROVAL_PACKAGE_BYTES )
240+ throw new ProjectError ( 413 , "approval_package_too_large" , {
241+ bytes,
242+ limit : MAX_APPROVAL_PACKAGE_BYTES ,
243+ } ) ;
244+ parseApprovalPackageBundle ( input . content ) ;
245+ } else {
246+ const document = DocumentContent ( input . content ) ;
247+ if ( document instanceof type . errors )
248+ throw new ProjectError ( 400 , "invalid_artifact_write" , {
249+ issues : document . summary ,
250+ } ) ;
251+ }
252+ return bundled ;
253+ }
254+
187255// A document version records the open questions it leaves for the person; a version filed without
188256// them, before they were recorded or by an import, leaves none. An approaches version may also
189257// record its approaches for a side-by-side comparison.
@@ -1254,55 +1322,17 @@ export function createArtifactService(
12541322 throw new ProjectError ( 400 , "invalid_artifact_write" , {
12551323 issues : input . summary ,
12561324 } ) ;
1257- const bundled =
1258- input . kind === "design" ||
1259- input . kind === "design_option" ||
1260- input . kind === "app" ;
1261- if ( bundled && input . openQuestions )
1262- throw new ProjectError ( 400 , "invalid_artifact_write" , {
1263- issues : "openQuestions belong to documents, not a " + input . kind ,
1264- } ) ;
1265- if ( input . approaches ) {
1266- if ( input . kind !== "approaches" )
1267- throw new ProjectError ( 400 , "invalid_artifact_write" , {
1268- issues :
1269- "approaches belong to the approaches document, not a " +
1270- input . kind ,
1271- } ) ;
1272- const issue = approachesIssue ( input . approaches ) ;
1273- if ( issue )
1274- throw new ProjectError ( 400 , "invalid_artifact_write" , {
1275- issues : issue ,
1276- } ) ;
1277- }
1278- if ( bundled ) {
1279- const bundle = ( input . kind === "app" ? AppBundle : DesignBundle ) (
1280- parseJson ( input . content ) ,
1281- ) ;
1282- if ( bundle instanceof type . errors )
1283- throw new ProjectError ( 400 , "invalid_" + input . kind , {
1284- issues : bundle . summary ,
1285- } ) ;
1286- }
1287- if ( input . kind === "app" ) {
1288- const bytes = Buffer . byteLength ( input . content ) ;
1289- if ( bytes > MAX_APP_BYTES )
1290- throw new ProjectError ( 413 , "app_too_large" , {
1291- bytes,
1292- limit : MAX_APP_BYTES ,
1293- } ) ;
1294- } else {
1295- const document = DocumentContent ( input . content ) ;
1296- if ( document instanceof type . errors )
1297- throw new ProjectError ( 400 , "invalid_artifact_write" , {
1298- issues : document . summary ,
1299- } ) ;
1300- }
1325+ const bundled = validateArtifactWriteContent ( input ) ;
13011326 const allowed = await requireProject ( actor , projectId , "read" ) ;
13021327 if ( ! ( await allowed ( artifactResource ( input . kind ) , "write" ) ) )
13031328 throw new ProjectError ( 403 , "artifact_write_refused" , {
13041329 kind : input . kind ,
13051330 } ) ;
1331+ if ( input . kind === "approval_package" )
1332+ await validateApprovalPackage (
1333+ input . content ,
1334+ readProjectPackageSource ( db , actor . tenantId ) ,
1335+ ) ;
13061336 const [ active ] = await rawDB
13071337 . select ( { kind : principal . kind , refId : principal . refId } )
13081338 . from ( principal )
0 commit comments