@@ -9,6 +9,7 @@ const scriptDirectory = dirname(fileURLToPath(import.meta.url));
99const root = resolve ( scriptDirectory , ".." ) ;
1010const supportDirectory = resolve ( root , "tests/support" ) ;
1111const providerFiles = [
12+ resolve ( supportDirectory , "object_graph_observation.rs" ) ,
1213 resolve ( supportDirectory , "runtime_completion_oracle.rs" ) ,
1314 resolve ( supportDirectory , "runtime_observation.rs" ) ,
1415] ;
@@ -155,21 +156,76 @@ const tombstoneSources = [
155156 }
156157 Err(error) => panic!("Rust engine failure for {description}: {error}"),
157158 }
159+ }` ,
160+ } ,
161+ {
162+ label : "Object graph descriptor-bit formatter without a trailing argument comma" ,
163+ origin : "tests/oracle/object/oracle_object_descriptors.rs" ,
164+ replacement : "object_graph_observation::data_bits" ,
165+ source : String . raw `fn data_bits(writable: bool, enumerable: bool, configurable: bool) -> String {
166+ format!(
167+ "D:{}{}{}",
168+ Number(writable),
169+ Number(enumerable),
170+ Number(configurable)
171+ )
172+ }` ,
173+ } ,
174+ {
175+ label : "Object graph integer-property reader" ,
176+ origin : "tests/oracle/object/oracle_object_assign.rs" ,
177+ replacement : "object_graph_observation::int_property" ,
178+ source : String . raw `fn int_property(runtime: &Runtime, context: &mut Context, object: &ObjectRef, name: &str) -> i32 {
179+ let Value::Int(value) = context
180+ .get_property(object, &runtime.intern_property_key(name).unwrap())
181+ .unwrap()
182+ else {
183+ panic!("{name} was not an Int property");
184+ };
185+ value
158186}` ,
159187 } ,
160188] ;
161189
162- // All exact copies of the shared providers were retired with this gate. Keep
163- // the mechanism explicit so a narrowly justified exception remains a reviewed
164- // path-and-fingerprint decision instead of a global clone-count waiver.
165- const providerAllowlist = new Set ( ) ;
190+ // The Object graph cleanup was deliberately bounded to eight oracle modules.
191+ // Adjacent pre-existing copies stay explicit as path-and-fingerprint
192+ // exceptions, so the gate still rejects any new copy and detects stale entries.
193+ const helperAllowlist = new Set ( [
194+ "tests/oracle/collections/oracle_map.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
195+ "tests/oracle/collections/oracle_set.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
196+ "tests/oracle/collections/oracle_weak_collections.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
197+ "tests/oracle/errors/oracle_errors.rs\u0000decb1b0e66595c263bc68421bb9797396e381256f18cfb9bf9892c576822ad4a" ,
198+ "tests/oracle/function_semantics/oracle_function_prototype_prefix.rs\u0000decb1b0e66595c263bc68421bb9797396e381256f18cfb9bf9892c576822ad4a" ,
199+ "tests/oracle/global/oracle_global_numeric_predicates.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
200+ "tests/oracle/global/oracle_global_uri_codecs.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
201+ "tests/oracle/number/oracle_number_constructor_conversion.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
202+ "tests/oracle/object/oracle_object_group_by.rs\u00002ecb9b416736102f1170cfa102c85182007491633a58badf2c1677354d8fa244" ,
203+ "tests/oracle/object/oracle_object_group_by.rs\u00003da3ed15c59afc7ed8c28f61354fa7f1ad406d948f45faa8d6989b5805fbeec7" ,
204+ "tests/oracle/object/oracle_object_group_by.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
205+ "tests/oracle/object/oracle_object_group_by.rs\u0000174e66d9def3034c785ba13ce8818d1a4b6132fa1aa62f3956f8a870a18c6690" ,
206+ "tests/oracle/object/oracle_object_group_by.rs\u0000ae3af224efe82f1d4d7b2adf0ac80991c51681123f90bc2ed647e15b29c3832d" ,
207+ "tests/oracle/object/oracle_object_group_by.rs\u0000decb1b0e66595c263bc68421bb9797396e381256f18cfb9bf9892c576822ad4a" ,
208+ "tests/oracle/object/oracle_object_intrinsic.rs\u00002ecb9b416736102f1170cfa102c85182007491633a58badf2c1677354d8fa244" ,
209+ "tests/oracle/object/oracle_object_intrinsic.rs\u00005d9661cb5d6d4945fe9108896091b79c50bcf99ec24a535d745088a4bd150a86" ,
210+ "tests/oracle/object/oracle_object_intrinsic.rs\u0000decb1b0e66595c263bc68421bb9797396e381256f18cfb9bf9892c576822ad4a" ,
211+ "tests/oracle/string/oracle_string_index_search.rs\u0000ae3af224efe82f1d4d7b2adf0ac80991c51681123f90bc2ed647e15b29c3832d" ,
212+ "tests/oracle/string/oracle_string_index_search.rs\u0000174e66d9def3034c785ba13ce8818d1a4b6132fa1aa62f3956f8a870a18c6690" ,
213+ "tests/oracle/string/oracle_string_index_search.rs\u0000c09ce90549ab8e65e6a88c4f7261c5ec6e7aa8a11bee43106b827b8823bc1377" ,
214+ "tests/oracle/string/oracle_string_index_search.rs\u0000decb1b0e66595c263bc68421bb9797396e381256f18cfb9bf9892c576822ad4a" ,
215+ "tests/oracle/string/oracle_string_intrinsic.rs\u00003da3ed15c59afc7ed8c28f61354fa7f1ad406d948f45faa8d6989b5805fbeec7" ,
216+ "tests/oracle/string/oracle_string_intrinsic.rs\u0000c09ce90549ab8e65e6a88c4f7261c5ec6e7aa8a11bee43106b827b8823bc1377" ,
217+ "tests/oracle/string/oracle_string_intrinsic.rs\u0000decb1b0e66595c263bc68421bb9797396e381256f18cfb9bf9892c576822ad4a" ,
218+ "tests/oracle/string/oracle_string_split.rs\u0000174e66d9def3034c785ba13ce8818d1a4b6132fa1aa62f3956f8a870a18c6690" ,
219+ "tests/oracle_math_intrinsic.rs\u00002ecb9b416736102f1170cfa102c85182007491633a58badf2c1677354d8fa244" ,
220+ ] ) ;
166221
167222runCanaries ( ) ;
168223
169224const providers = loadProviders ( ) ;
170225const tombstones = loadTombstones ( ) ;
171226const consumerFiles = collectConsumerFiles ( ) ;
172227const functions = consumerFiles . flatMap ( ( path ) => scanFile ( path ) ) ;
228+ runAllowlistMultiplicityCanary ( functions , providers , tombstones ) ;
173229const failures = checkFunctions ( functions , providers , tombstones ) ;
174230
175231if ( options . report ) {
@@ -283,15 +339,16 @@ function scanFile(path) {
283339
284340function checkFunctions ( functions , providers , tombstones ) {
285341 const failures = [ ] ;
286- const usedAllowlist = new Set ( ) ;
342+ const allowlistUseCounts = new Map ( ) ;
287343 for ( const helper of functions ) {
288344 const displayPath = display ( helper . path ) ;
345+ const allowlistKey = `${ displayPath } \0${ helper . fingerprint } ` ;
346+ const allowlisted = helperAllowlist . has ( allowlistKey ) ;
347+ let protectedHelper = false ;
289348 const providersForFingerprint = providers . get ( helper . fingerprint ) ;
290349 if ( providersForFingerprint !== undefined ) {
291- const allowlistKey = `${ displayPath } \0${ helper . fingerprint } ` ;
292- if ( providerAllowlist . has ( allowlistKey ) ) {
293- usedAllowlist . add ( allowlistKey ) ;
294- } else {
350+ protectedHelper = true ;
351+ if ( ! allowlisted ) {
295352 const providerNames = providersForFingerprint
296353 . map ( ( provider ) => provider . name )
297354 . sort ( )
@@ -305,22 +362,56 @@ function checkFunctions(functions, providers, tombstones) {
305362 }
306363 const tombstone = tombstones . get ( helper . fingerprint ) ;
307364 if ( tombstone !== undefined ) {
308- failures . push (
309- `${ displayPath } :${ helper . line } restores retired ${ tombstone . label } ` +
310- `(from ${ tombstone . origin } ); use ${ tombstone . replacement } ` ,
365+ protectedHelper = true ;
366+ if ( ! allowlisted ) {
367+ failures . push (
368+ `${ displayPath } :${ helper . line } restores retired ${ tombstone . label } ` +
369+ `(from ${ tombstone . origin } ); use ${ tombstone . replacement } ` ,
370+ ) ;
371+ }
372+ }
373+ if ( allowlisted && protectedHelper ) {
374+ allowlistUseCounts . set (
375+ allowlistKey ,
376+ ( allowlistUseCounts . get ( allowlistKey ) ?? 0 ) + 1 ,
311377 ) ;
312378 }
313379 }
314- for ( const allowlistKey of providerAllowlist ) {
315- if ( ! usedAllowlist . has ( allowlistKey ) ) {
380+ for ( const allowlistKey of helperAllowlist ) {
381+ const useCount = allowlistUseCounts . get ( allowlistKey ) ?? 0 ;
382+ if ( useCount === 0 ) {
383+ failures . push (
384+ `stale shared-helper allowlist entry: ${ allowlistKey . replace ( "\0" , " " ) } ` ,
385+ ) ;
386+ } else if ( useCount !== 1 ) {
316387 failures . push (
317- `stale shared-provider allowlist entry: ${ allowlistKey . replace ( "\0" , " " ) } ` ,
388+ `shared-helper allowlist multiplicity drift: ` +
389+ `${ allowlistKey . replace ( "\0" , " " ) } expected=1 actual=${ useCount } ` ,
318390 ) ;
319391 }
320392 }
321393 return failures . sort ( ) ;
322394}
323395
396+ function runAllowlistMultiplicityCanary ( functions , providers , tombstones ) {
397+ const [ allowlistKey ] = helperAllowlist ;
398+ const helper = functions . find (
399+ ( candidate ) =>
400+ `${ display ( candidate . path ) } \0${ candidate . fingerprint } ` === allowlistKey ,
401+ ) ;
402+ if ( helper === undefined ) {
403+ fail ( "allowlist multiplicity canary could not find its protected helper" ) ;
404+ }
405+ const failures = checkFunctions ( [ helper , helper ] , providers , tombstones ) ;
406+ if (
407+ ! failures . some ( ( failure ) =>
408+ failure . startsWith ( "shared-helper allowlist multiplicity drift:" ) ,
409+ )
410+ ) {
411+ fail ( "allowlist multiplicity canary accepted a second protected helper copy" ) ;
412+ }
413+ }
414+
324415function printCensus ( functions , consumerFiles , providers , tombstones ) {
325416 const groups = new Map ( ) ;
326417 for ( const helper of functions ) {
0 commit comments