11'use strict'
22
3- // Offline proof for the models.manifest.json wiring (QVAC-21937): ensureModel
4- // resolves URLs + integrity from the manifest, re-downloads poisoned/truncated
3+ // Offline integrity/manifest proof for embed-llamacpp, mirroring
4+ // diffusion-cpp/test/unit/ensure-model-integrity.test.js: ensureModel resolves
5+ // URL + sha256/bytes from models.manifest.json, re-downloads poisoned/truncated
56// caches, falls back to MODEL_CONFIGS when the manifest lacks an entry, and the
6- // real integration manifest is well-formed. No network and no native addon are
7- // touched — downloads are injected.
7+ // real manifest is well-formed. No network and no native addon are touched —
8+ // downloads are injected.
89
910const test = require ( 'brittle' )
1011const fs = require ( 'bare-fs' )
@@ -27,7 +28,7 @@ const BAD_SHORT = 'too-short'
2728
2829function mkTmpDir ( ) {
2930 const base = ( typeof os . tmpdir === 'function' && os . tmpdir ( ) ) || '/tmp'
30- const dir = path . join ( base , `qvac-embed-manifest -${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` )
31+ const dir = path . join ( base , `qvac-embed-integrity -${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` )
3132 fs . mkdirSync ( dir , { recursive : true } )
3233 return dir
3334}
@@ -80,7 +81,7 @@ test('correct cached file verifies and performs NO download', async function (t)
8081 try {
8182 writeModel ( dir , name , GOOD )
8283 resetDownloadCount ( )
83- const [ , resolvedDir ] = await ensureModel ( name , { modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
84+ const [ , resolvedDir ] = await ensureModel ( { modelName : name , modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
8485 t . is ( spy . calls , 0 , 'no download for a valid cached file' )
8586 t . is ( getDownloadCount ( ) , 0 , 'download counter stays 0 on a warm run' )
8687 t . is ( resolvedDir , dir )
@@ -95,12 +96,12 @@ test('poisoned cached file (sha mismatch) is deleted and re-downloaded', async f
9596 const dir = mkTmpDir ( )
9697 const spy = { calls : 0 }
9798 try {
98- writeModel ( dir , name , BAD_SAME_LEN )
99+ writeModel ( dir , name , BAD_SAME_LEN ) // same length -> defeats size check, forces sha path
99100 resetDownloadCount ( )
100- await ensureModel ( name , { modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
101+ await ensureModel ( { modelName : name , modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
101102 t . is ( spy . calls , 1 , 're-downloaded exactly once after integrity failure' )
102103 t . is ( getDownloadCount ( ) , 1 , 'download counter incremented' )
103- t . is ( fs . readFileSync ( path . join ( dir , name ) , 'utf8' ) , GOOD )
104+ t . is ( fs . readFileSync ( path . join ( dir , name ) , 'utf8' ) , GOOD , 'file replaced with correct content' )
104105 } finally {
105106 fs . rmSync ( dir , { recursive : true , force : true } )
106107 }
@@ -113,7 +114,7 @@ test('truncated cached file (size mismatch) is re-downloaded', async function (t
113114 const spy = { calls : 0 }
114115 try {
115116 writeModel ( dir , name , BAD_SHORT )
116- await ensureModel ( name , { modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
117+ await ensureModel ( { modelName : name , modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
117118 t . is ( spy . calls , 1 , 'size mismatch triggered a re-download' )
118119 t . is ( fs . readFileSync ( path . join ( dir , name ) , 'utf8' ) , GOOD )
119120 } finally {
@@ -129,7 +130,7 @@ test('persistent mismatch hard-fails and removes the bad file', async function (
129130 try {
130131 writeModel ( dir , name , BAD_SAME_LEN )
131132 await t . exception (
132- ensureModel ( name , { modelDir : dir , manifest, download : fakeDownloader ( BAD_SAME_LEN , spy ) } ) ,
133+ ensureModel ( { modelName : name , modelDir : dir , manifest, download : fakeDownloader ( BAD_SAME_LEN , spy ) } ) ,
133134 / f a i l e d i n t e g r i t y /
134135 )
135136 t . is ( spy . calls , 1 , 'attempted a re-download before failing' )
@@ -145,21 +146,35 @@ test('missing file downloads then verifies', async function (t) {
145146 const dir = mkTmpDir ( )
146147 const spy = { calls : 0 }
147148 try {
148- await ensureModel ( name , { modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
149+ await ensureModel ( { modelName : name , modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
149150 t . is ( spy . calls , 1 , 'downloaded the missing file' )
150151 t . is ( fs . readFileSync ( path . join ( dir , name ) , 'utf8' ) , GOOD )
151152 } finally {
152153 fs . rmSync ( dir , { recursive : true , force : true } )
153154 }
154155} )
155156
157+ test ( 'entry without pinned integrity skips verification (keeps a non-zero cached file)' , async function ( t ) {
158+ const name = 'gte-large_fp16.gguf'
159+ const manifest = { models : { [ name ] : { urls : [ 'https://example.invalid/x' ] , sha256 : null , bytes : null } } }
160+ const dir = mkTmpDir ( )
161+ const spy = { calls : 0 }
162+ try {
163+ writeModel ( dir , name , BAD_SAME_LEN ) // any non-zero content is accepted when unpinned
164+ await ensureModel ( { modelName : name , modelDir : dir , manifest, download : fakeDownloader ( GOOD , spy ) } )
165+ t . is ( spy . calls , 0 , 'no download when an unpinned file already exists and is non-zero' )
166+ } finally {
167+ fs . rmSync ( dir , { recursive : true , force : true } )
168+ }
169+ } )
170+
156171test ( 'MODEL_CONFIGS is used as a fallback when the manifest has no entry' , async function ( t ) {
157172 const name = 'gte-large_fp16.gguf' // known in MODEL_CONFIGS
158173 const dir = mkTmpDir ( )
159174 const spy = { calls : 0 }
160175 try {
161176 t . ok ( MODEL_CONFIGS [ name ] , 'model is declared in MODEL_CONFIGS' )
162- await ensureModel ( name , { modelDir : dir , manifest : { models : { } } , download : fakeDownloader ( GOOD , spy ) } )
177+ await ensureModel ( { modelName : name , modelDir : dir , manifest : { models : { } } , download : fakeDownloader ( GOOD , spy ) } )
163178 t . is ( spy . calls , 1 , 'downloaded via the MODEL_CONFIGS fallback url' )
164179 t . is ( fs . readFileSync ( path . join ( dir , name ) , 'utf8' ) , GOOD )
165180 } finally {
@@ -171,7 +186,7 @@ test('truly unknown model (no config, no manifest entry) throws', async function
171186 const dir = mkTmpDir ( )
172187 try {
173188 await t . exception (
174- ensureModel ( 'nope.gguf' , { modelDir : dir , manifest : { models : { } } } ) ,
189+ ensureModel ( { modelName : 'nope.gguf' , modelDir : dir , manifest : { models : { } } } ) ,
175190 / U n k n o w n m o d e l /
176191 )
177192 } finally {
@@ -193,17 +208,28 @@ test('verifyModelFile flags size before hashing (fail-fast)', async function (t)
193208 }
194209} )
195210
196- test ( 'real integration manifest is well-formed and matches MODEL_CONFIGS' , function ( t ) {
211+ test ( 'integration manifest is well-formed and covers MODEL_CONFIGS' , function ( t ) {
197212 const manifest = loadManifest ( )
198213 t . ok ( manifest && manifest . models , 'models.manifest.json loads' )
199214 const names = Object . keys ( manifest . models )
200215 t . is ( new Set ( names ) . size , names . length , 'no duplicate model keys' )
216+
201217 for ( const [ name , entry ] of Object . entries ( manifest . models ) ) {
202218 const hasUrl = Array . isArray ( entry . urls ) && entry . urls . length > 0 &&
203219 entry . urls . every ( u => typeof u === 'string' && u . startsWith ( 'https://' ) )
204220 t . ok ( hasUrl , `${ name } has at least one https url` )
221+
222+ // Integrity is pinned in CI (scripts/generate-model-manifest.mjs). When
223+ // present it must be the right shape; null is allowed until pinned.
224+ if ( entry . sha256 !== null && entry . sha256 !== undefined ) {
225+ t . ok ( / ^ [ 0 - 9 a - f ] { 64 } $ / i. test ( entry . sha256 ) , `${ name } sha256 is 64 hex chars when pinned` )
226+ }
227+ if ( entry . bytes !== null && entry . bytes !== undefined ) {
228+ t . ok ( Number . isInteger ( entry . bytes ) && entry . bytes > 0 , `${ name } bytes is a positive integer when pinned` )
229+ }
205230 }
206- // Every MODEL_CONFIGS model must be represented in the manifest so warm covers it.
231+
232+ // Every MODEL_CONFIGS model must be represented so warm covers it.
207233 for ( const name of Object . keys ( MODEL_CONFIGS ) ) {
208234 t . ok ( resolveModelEntry ( name ) , `${ name } present in manifest` )
209235 }
0 commit comments