@@ -60,7 +60,6 @@ export interface WorkspaceFileSecretProvenanceEnvelope<T> {
6060}
6161
6262interface ModelSafeWorkspaceFileRow {
63- id : string
6463 key : string
6564 workspaceId : string | null
6665 context : string
@@ -543,9 +542,12 @@ export async function importWorkspaceFileSecretProvenanceForRuntime(args: {
543542}
544543
545544/**
546- * Removes model attachments whose canonical workspace-file record is tainted, unknown, or does
547- * not match the supplied file id. Missing legacy records remain compatible; any persisted record
548- * is classified exclusively by its trusted key/id binding and private provenance row.
545+ * Removes model attachments whose canonical workspace-file record is tainted or unknown.
546+ * Missing legacy records remain compatible; persisted records are classified by their unique
547+ * active storage-key binding and private provenance row. Attachment ids are deliberately ignored:
548+ * older persisted workflows omit them and file normalization may synthesize a runtime-only id.
549+ * This classification is not file authorization; callers still enforce storage access before
550+ * reading bytes or issuing a provider URL.
549551 */
550552export async function filterModelSafeWorkspaceFileAttachments <
551553 TAttachment extends WorkspaceFileAttachmentIdentity ,
@@ -567,32 +569,14 @@ export async function filterModelSafeWorkspaceFileAttachments<
567569 ]
568570 if ( keys . length === 0 ) return [ ...attachments ]
569571
570- const rows = await db
571- . select ( {
572- id : workspaceFiles . id ,
573- key : workspaceFiles . key ,
574- workspaceId : workspaceFiles . workspaceId ,
575- context : workspaceFiles . context ,
576- fileContentUpdatedAt : workspaceFiles . contentUpdatedAt ,
577- secretProvenanceVersion : workspaceFiles . secretProvenanceVersion ,
578- provenanceContentUpdatedAt : workspaceFileSecretProvenance . contentUpdatedAt ,
579- status : workspaceFileSecretProvenance . status ,
580- entries : workspaceFileSecretProvenance . entries ,
581- } )
582- . from ( workspaceFiles )
583- . leftJoin (
584- workspaceFileSecretProvenance ,
585- eq ( workspaceFileSecretProvenance . fileId , workspaceFiles . id )
586- )
587- . where ( and ( inArray ( workspaceFiles . key , keys ) , isNull ( workspaceFiles . deletedAt ) ) )
572+ const rows = await loadModelSafeWorkspaceFileRows ( keys )
588573
589574 const rowByKey = new Map ( rows . map ( ( row ) => [ row . key , row ] ) )
590575 return attachments . filter ( ( attachment ) => {
591576 if ( typeof attachment . key !== 'string' || attachment . key . length === 0 ) return true
592577 const row = rowByKey . get ( attachment . key )
593578 if ( ! row ) return true
594579 if ( row . context !== 'workspace' && row . context !== 'mothership' ) return true
595- if ( typeof attachment . id !== 'string' || attachment . id !== row . id ) return false
596580 return isModelSafeWorkspaceFileRow ( row , options . workspaceId )
597581 } )
598582}
@@ -614,6 +598,28 @@ function isModelSafeWorkspaceFileRow(
614598 return row . entries . length === 0
615599}
616600
601+ async function loadModelSafeWorkspaceFileRows (
602+ keys : readonly string [ ]
603+ ) : Promise < ModelSafeWorkspaceFileRow [ ] > {
604+ return db
605+ . select ( {
606+ key : workspaceFiles . key ,
607+ workspaceId : workspaceFiles . workspaceId ,
608+ context : workspaceFiles . context ,
609+ fileContentUpdatedAt : workspaceFiles . contentUpdatedAt ,
610+ secretProvenanceVersion : workspaceFiles . secretProvenanceVersion ,
611+ provenanceContentUpdatedAt : workspaceFileSecretProvenance . contentUpdatedAt ,
612+ status : workspaceFileSecretProvenance . status ,
613+ entries : workspaceFileSecretProvenance . entries ,
614+ } )
615+ . from ( workspaceFiles )
616+ . leftJoin (
617+ workspaceFileSecretProvenance ,
618+ eq ( workspaceFileSecretProvenance . fileId , workspaceFiles . id )
619+ )
620+ . where ( and ( inArray ( workspaceFiles . key , [ ...keys ] ) , isNull ( workspaceFiles . deletedAt ) ) )
621+ }
622+
617623/**
618624 * Verifies a server-authorized storage key before its bytes or signed URL cross a model boundary.
619625 * Unlike attachment filtering, the key has already passed access control, so no caller-provided
@@ -642,24 +648,7 @@ export async function areModelSafeWorkspaceFileKeys(
642648 throw new Error ( 'Too many file keys to verify secret provenance' )
643649 }
644650
645- const rows = await db
646- . select ( {
647- id : workspaceFiles . id ,
648- key : workspaceFiles . key ,
649- workspaceId : workspaceFiles . workspaceId ,
650- context : workspaceFiles . context ,
651- fileContentUpdatedAt : workspaceFiles . contentUpdatedAt ,
652- secretProvenanceVersion : workspaceFiles . secretProvenanceVersion ,
653- provenanceContentUpdatedAt : workspaceFileSecretProvenance . contentUpdatedAt ,
654- status : workspaceFileSecretProvenance . status ,
655- entries : workspaceFileSecretProvenance . entries ,
656- } )
657- . from ( workspaceFiles )
658- . leftJoin (
659- workspaceFileSecretProvenance ,
660- eq ( workspaceFileSecretProvenance . fileId , workspaceFiles . id )
661- )
662- . where ( and ( inArray ( workspaceFiles . key , uniqueKeys ) , isNull ( workspaceFiles . deletedAt ) ) )
651+ const rows = await loadModelSafeWorkspaceFileRows ( uniqueKeys )
663652
664653 return rows . every (
665654 ( row ) =>
0 commit comments