@@ -6,6 +6,11 @@ import { resolveStateDir } from "../../config/paths.js";
66import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP } from "../../infra/archive.js" ;
77import { formatErrorMessage } from "../../infra/errors.js" ;
88import { createAsyncLock , readDurableJsonFile , writeJsonAtomic } from "../../infra/json-files.js" ;
9+ import {
10+ asDateTimestampMs ,
11+ isFutureDateTimestampMs ,
12+ resolveExpiresAtMsFromDurationMs ,
13+ } from "../../shared/number-coercion.js" ;
914import { validateRequestedSkillSlug } from "./archive-install.js" ;
1015
1116export const SKILL_UPLOAD_TTL_MS = 60 * 60 * 1000 ;
@@ -209,10 +214,14 @@ async function assertNotExpired(
209214 record : SkillUploadRecord ,
210215 now : number ,
211216) : Promise < void > {
212- if ( record . expiresAt <= now ) {
217+ const validNow = asDateTimestampMs ( now ) ;
218+ if ( validNow !== undefined && ! isFutureDateTimestampMs ( record . expiresAt , { nowMs : validNow } ) ) {
213219 await removeRecordFiles ( rootDir , record ) ;
214220 throw new SkillUploadRequestError ( "upload has expired" ) ;
215221 }
222+ if ( validNow === undefined ) {
223+ throw new SkillUploadRequestError ( "upload has expired" ) ;
224+ }
216225}
217226
218227async function computeFileSha256 ( filePath : string ) : Promise < string > {
@@ -286,7 +295,12 @@ async function cleanupExpiredUploads(
286295 }
287296 await withLock ( `${ rootDir } :upload:${ uploadId } ` , async ( ) => {
288297 const record = await readRecordIfPresent ( rootDir , uploadId ) . catch ( ( ) => null ) ;
289- if ( record && record . expiresAt <= nowMs ) {
298+ const validNow = asDateTimestampMs ( nowMs ) ;
299+ if (
300+ record &&
301+ validNow !== undefined &&
302+ ! isFutureDateTimestampMs ( record . expiresAt , { nowMs : validNow } )
303+ ) {
290304 await removeRecordFiles ( rootDir , record ) ;
291305 }
292306 } ) ;
@@ -297,7 +311,7 @@ async function countActiveUploads(rootDir: string, nowMs: number): Promise<numbe
297311 let count = 0 ;
298312 for ( const uploadId of await listUploadIds ( rootDir ) ) {
299313 const record = await readRecordIfPresent ( rootDir , uploadId ) . catch ( ( ) => null ) ;
300- if ( record && record . expiresAt > nowMs ) {
314+ if ( record && isFutureDateTimestampMs ( record . expiresAt , { nowMs } ) ) {
301315 count += 1 ;
302316 }
303317 }
@@ -395,7 +409,7 @@ export function createSkillUploadStore(options?: {
395409 `${ rootDir } :upload:${ existingUploadId } ` ,
396410 async ( ) => {
397411 const record = await readRecordIfPresent ( rootDir , existingUploadId ) ;
398- if ( record && record . expiresAt > now ( ) ) {
412+ if ( record && isFutureDateTimestampMs ( record . expiresAt , { nowMs : now ( ) } ) ) {
399413 return {
400414 uploadId : record . uploadId ,
401415 receivedBytes : record . receivedBytes ,
@@ -424,6 +438,10 @@ export function createSkillUploadStore(options?: {
424438 const uploadDir = resolveUploadDir ( rootDir , uploadId ) ;
425439 const archivePath = resolveArchivePath ( rootDir , uploadId ) ;
426440 const createdAt = now ( ) ;
441+ const expiresAt = resolveExpiresAtMsFromDurationMs ( ttlMs , { nowMs : createdAt } ) ;
442+ if ( expiresAt === undefined ) {
443+ throw new SkillUploadRequestError ( "invalid upload expiry" ) ;
444+ }
427445 const record : SkillUploadRecord = {
428446 version : 1 ,
429447 kind : params . kind ,
@@ -435,7 +453,7 @@ export function createSkillUploadStore(options?: {
435453 receivedBytes : 0 ,
436454 archivePath,
437455 createdAt,
438- expiresAt : createdAt + ttlMs ,
456+ expiresAt,
439457 committed : false ,
440458 ...( keyHash ? { idempotencyKeyHash : keyHash } : { } ) ,
441459 } ;
0 commit comments