2828 * provisional" and is not red. A package `uncapped` as `carried` (a cache HIT
2929 * re-confirmed it, but no run of the window executed it) is not red either:
3030 * it is in the dataset, and the next refresh that executes it gives it one.
31- * - ONLY a package absent from the dataset is red for having no ceiling -- the
32- * new-package shape. A package that has never landed is never measured by a
33- * refresh, so for a new package that red is lifted by a ruled raise below,
34- * not by waiting.
31+ * - A package absent from the dataset is a NEW package: it prints "no
32+ * ceiling: new package" and is not red, and the first refresh after it lands
33+ * enters it in the dataset under the formula above. A package the dataset
34+ * KNOWS but that lost its row is not new and stays red: a weight with neither
35+ * a ceiling nor an `uncapped` reason, or a ceiling, an `uncapped` reason or a
36+ * `provisional` mark with no weight, is a table the generator did not write,
37+ * and readCeilingTable refuses it. Ruling (Q2 B):
38+ * https://github.com/objectstack-ai/objectstack/issues/16468#issuecomment-6082629070
3539 * - A ceiling rises only by a ruling: RULED_CEILING_RAISES, each entry naming
3640 * the ruling's comment URL. Nothing else in this file moves a ceiling.
3741 *
4448 * hand-typed one is exactly what the generator exists to prevent. Until the
4549 * first refresh after this check landed writes `ceilings`, a dataset WITHOUT
4650 * the key is read as NOT MEASURED ("no ceiling table yet") and exits 0. That
47- * is distinct from a package absent from a dataset that HAS the key (red), and
48- * the reading is printed as a warning annotation on every run, because a
49- * persistent NOT MEASURED is a defect: it means the refresh stopped writing
50- * ceilings.
51+ * is distinct from a package absent from a dataset that HAS the key (a new
52+ * package), and the reading is printed as a warning annotation on every run,
53+ * because a persistent NOT MEASURED is a defect: it means the refresh stopped
54+ * writing ceilings.
5155 *
5256 * ## Where it runs, and why the CLI's slices are SUMMED
5357 *
8993 *
9094 * ## Exit codes
9195 *
92- * 0 OK, or NOT MEASURED (no table yet; nothing executed; no captures)
93- * 1 a package over its ceiling, a package absent from the dataset, or a
94- * refusal (a malformed ceiling table or raise record)
96+ * 0 OK, or NOT MEASURED (no table yet; nothing executed; no captures); a
97+ * new package alone never makes it 1
98+ * 1 a package over its ceiling, or a refusal (a malformed ceiling table --
99+ * a package the dataset knows that lost its row among them -- or raise
100+ * record)
95101 */
96102
97103import { readFileSync , readdirSync , statSync } from 'node:fs' ;
@@ -120,9 +126,9 @@ export const SLOWEST_FILES_NAMED = 5;
120126// raise is reviewable only if the words that authorised it can be read. The
121127// effective ceiling is the larger of the generated one and the raise, so a raise
122128// below the generated ceiling changes nothing and is reported as stale. An entry
123- // for a package absent from the dataset gives a new package its first ceiling --
124- // the only way a package that has never landed can get one, since a refresh only
125- // measures what runs on `main` .
129+ // for a package absent from the dataset gives that new package a ceiling, graded
130+ // from then on, before the first refresh after it lands could; without one it
131+ // prints "no ceiling: new package" and is not red .
126132//
127133// ⛔ Never raise a ceiling by editing `ceilings` in scripts/test-shard-timings.json:
128134// that file is generated, and the next refresh holds whatever it finds.
@@ -198,6 +204,21 @@ export function readCeilingTable(dataset) {
198204 'the dataset rather than editing it.'
199205 ) ;
200206 }
207+ // And the other direction: the generator writes a ceiling, an `uncapped`
208+ // reason or a `provisional` mark only beside a weight, so a package named by
209+ // one of them without a weight is a package the dataset KNOWS that lost its
210+ // row. Graded, it would read as a new package and pass; refused, it stays red.
211+ const weightless = [ ...new Set ( [ ...Object . keys ( ceilings ) , ...Object . keys ( uncapped ) , ...provisional ] ) ]
212+ . filter ( ( n ) => ! Object . hasOwn ( packages , n ) )
213+ . sort ( ( a , b ) => String ( a ) . localeCompare ( String ( b ) , 'en' ) ) ;
214+ if ( weightless . length > 0 ) {
215+ throw new Error (
216+ `${ DATASET_REL } : ${ weightless . length } package(s) carry a ceiling, an \`uncapped\` reason or a ` +
217+ `\`provisional\` mark but no weight (${ weightless . join ( ', ' ) } ). The generator writes those only beside a ` +
218+ 'weight, so each is a package the dataset knows that lost its row, not a new package; regenerate the ' +
219+ 'dataset rather than editing it.'
220+ ) ;
221+ }
201222 return {
202223 table : {
203224 packages,
@@ -221,8 +242,8 @@ export function readCeilingTable(dataset) {
221242 * `status`:
222243 *
223244 * over graded, over its effective ceiling RED
224- * absent not in the dataset, and no ruled raise RED
225245 * ok graded, within its effective ceiling
246+ * new not in the dataset, and no ruled raise no ceiling, not red
226247 * provisional in the dataset's `provisional` list no ceiling, not red
227248 * uncapped `uncapped` with a reason (`carried`) no ceiling, not red
228249 * partial a sliced package missing a part this run NOT MEASURED
@@ -268,7 +289,7 @@ export function gradeCeilings({ merged, dataset, raises = RULED_CEILING_RAISES }
268289 row . raise = raise ;
269290 if ( generated === null && raise === null ) {
270291 if ( ! inDataset ) {
271- row . status = 'absent ' ;
292+ row . status = 'new ' ;
272293 } else {
273294 row . status = 'uncapped' ;
274295 row . reason = table . uncapped [ name ] ;
@@ -285,7 +306,9 @@ export function gradeCeilings({ merged, dataset, raises = RULED_CEILING_RAISES }
285306
286307// ── Rendering ─────────────────────────────────────────────────────────────
287308
288- const RED_STATUSES = new Set ( [ 'over' , 'absent' ] ) ;
309+ // A new package is not among them (ruling Q2 B): it is red only once a ceiling
310+ // exists for it and it runs over.
311+ const RED_STATUSES = new Set ( [ 'over' ] ) ;
289312
290313function sliceNote ( row ) {
291314 if ( ! row . slices ) return '' ;
@@ -357,8 +380,11 @@ export function renderCeilingVerdict(report, label) {
357380 return ` -- ${ r . name } : ${ r . seconds . toFixed ( 2 ) } s -- no ceiling: ${ r . reason } ` ;
358381 case 'partial' :
359382 return ` -- ${ r . name } : NOT MEASURED -- a part of its suite is missing from this run${ sliceNote ( r ) } ; a part is never graded as the whole` ;
360- case 'absent' :
361- return ` NEW ${ r . name } : ${ r . seconds . toFixed ( 2 ) } s -- no ceiling: not in ${ DATASET_REL } (a new package)` ;
383+ case 'new' :
384+ return (
385+ ` -- ${ r . name } : ${ r . seconds . toFixed ( 2 ) } s -- no ceiling: new package (not in ${ DATASET_REL } ; ` +
386+ 'the first refresh after it lands enters it)'
387+ ) ;
362388 default :
363389 throw new Error ( `unknown row status ${ r . status } ` ) ;
364390 }
@@ -385,13 +411,10 @@ export function renderCeilingVerdict(report, label) {
385411 }
386412
387413 const detail = red
388- . map ( ( r ) =>
389- r . status === 'over'
390- ? ` ${ r . name } : ${ r . seconds . toFixed ( 2 ) } s measured against its ${ r . ceiling . toFixed ( 2 ) } s ceiling ` +
391- `(${ ( r . seconds / r . ceiling ) . toFixed ( 2 ) } x; ${ ceilingText ( r ) } )${ sliceNote ( r ) } . Its slowest test files:\n${ filesText ( r ) } `
392- : ` ${ r . name } : ${ r . seconds . toFixed ( 2 ) } s and no ceiling -- it is not in ${ DATASET_REL } , so it is a new package. ` +
393- 'It stays red until a ruled raise gives it a first ceiling (a refresh only measures what runs on main). ' +
394- `Its slowest test files:\n${ filesText ( r ) } `
414+ . map (
415+ ( r ) =>
416+ ` ${ r . name } : ${ r . seconds . toFixed ( 2 ) } s measured against its ${ r . ceiling . toFixed ( 2 ) } s ceiling ` +
417+ `(${ ( r . seconds / r . ceiling ) . toFixed ( 2 ) } x; ${ ceilingText ( r ) } )${ sliceNote ( r ) } . Its slowest test files:\n${ filesText ( r ) } `
395418 )
396419 . join ( '\n' ) ;
397420 return {
@@ -400,14 +423,12 @@ export function renderCeilingVerdict(report, label) {
400423 out : red . map (
401424 ( r ) =>
402425 `::error title=Suite-duration ceiling::${ escapeWorkflowCommandMessage (
403- r . status === 'over'
404- ? `${ r . name } : ${ r . seconds . toFixed ( 2 ) } s, over its ${ r . ceiling . toFixed ( 2 ) } s ceiling. Slowest file: ` +
405- `${ r . files [ 0 ] ? `${ r . files [ 0 ] . file } ${ ( r . files [ 0 ] . ms / 1000 ) . toFixed ( 2 ) } s` : '(none captured)' } .`
406- : `${ r . name } : ${ r . seconds . toFixed ( 2 ) } s and no ceiling -- a package absent from ${ DATASET_REL } .`
426+ `${ r . name } : ${ r . seconds . toFixed ( 2 ) } s, over its ${ r . ceiling . toFixed ( 2 ) } s ceiling. Slowest file: ` +
427+ `${ r . files [ 0 ] ? `${ r . files [ 0 ] . file } ${ ( r . files [ 0 ] . ms / 1000 ) . toFixed ( 2 ) } s` : '(none captured)' } .`
407428 ) } `
408429 ) ,
409430 err : [
410- `${ head } : OVER -- ${ label } : ${ red . length } package(s) over their suite-duration ceiling or without one .\n` +
431+ `${ head } : OVER -- ${ label } : ${ red . length } package(s) over their suite-duration ceiling.\n` +
411432 `${ detail } \n` +
412433 ` ${ basis } .\n` +
413434 ' Make the suite faster, or take the raise to a ruling: a ceiling rises only by an entry in\n' +
@@ -515,10 +536,10 @@ let selfTestReachedVerdict = false;
515536const SELF_TEST_BATTERIES = Object . freeze ( {
516537 'over the ceiling is red, naming the package and its slowest files' : 6 ,
517538 'provisional prints "no ceiling: provisional" and is not red' : 3 ,
518- 'a package absent from the dataset is red; an uncapped one is not ' : 4 ,
539+ 'a new package prints "no ceiling: new package" and is not red; a known one that lost its row is refused ' : 10 ,
519540 'no ceiling table yet is NOT MEASURED, never red' : 5 ,
520541 'file-sliced packages are summed across shards' : 6 ,
521- 'a ruled raise lifts a ceiling, and only a well-formed one does' : 7 ,
542+ 'a ruled raise lifts a ceiling, and only a well-formed one does' : 8 ,
522543 'a malformed ceiling table is refused, never read as a pass' : 4 ,
523544} ) ;
524545
@@ -630,14 +651,35 @@ function selfTest() {
630651 if ( held . exitCode !== 0 || status ( held , 'prov' ) !== 'provisional' ) throw new Error ( `provisional: a held ceiling overrode the provisional exemption (${ held . verdict } )` ) ;
631652 } ) ;
632653
633- battery ( 'a package absent from the dataset is red; an uncapped one is not' ) ;
634- const fresh = run ( [ capture ( '2/6' , [ [ 'brand-new' , 5 ] , [ 'a' , 10 ] ] , [ file ( 'brand-new' , 'n.test.ts' , 4_000 ) ] ) ] ) ;
654+ battery ( 'a new package prints "no ceiling: new package" and is not red; a known one that lost its row is refused' ) ;
635655 check ( ( ) => {
636- if ( fresh . exitCode !== 1 || status ( fresh , 'brand-new' ) !== 'absent' ) throw new Error ( `absent: exit ${ fresh . exitCode } , ${ status ( fresh , 'brand-new' ) } ` ) ;
656+ // Absent from the dataset, beside a package within its ceiling: the run is OK.
657+ const fresh = run ( [ capture ( '2/6' , [ [ 'brand-new' , 9999 ] , [ 'a' , 10 ] ] , [ file ( 'brand-new' , 'n.test.ts' , 4_000 ) ] ) ] ) ;
658+ if ( fresh . exitCode !== 0 || fresh . verdict !== 'OK' || status ( fresh , 'brand-new' ) !== 'new' ) {
659+ throw new Error ( `new: exit ${ fresh . exitCode } , verdict ${ fresh . verdict } , ${ status ( fresh , 'brand-new' ) } ` ) ;
660+ }
637661 } ) ;
638662 check ( ( ) => {
639- if ( ! fresh . err [ 0 ] . includes ( 'brand-new: 5.00s and no ceiling' ) || ! fresh . err [ 0 ] . includes ( 'n.test.ts' ) ) {
640- throw new Error ( `absent: the package or its files are not named:\n${ fresh . err [ 0 ] } ` ) ;
663+ const fresh = run ( [ capture ( '2/6' , [ [ 'brand-new' , 5 ] , [ 'a' , 10 ] ] ) ] ) ;
664+ if ( ! fresh . err [ 0 ] . includes ( 'brand-new: 5.00s -- no ceiling: new package' ) ) throw new Error ( `new: the line is wrong:\n${ fresh . err [ 0 ] } ` ) ;
665+ } ) ;
666+ check ( ( ) => {
667+ // Beside a package over its ceiling, only that one is red and named.
668+ const both = run ( [ capture ( '2/6' , [ [ 'brand-new' , 9999 ] , [ 'a' , 151 ] ] ) ] ) ;
669+ if (
670+ both . exitCode !== 1 ||
671+ ! both . err [ 0 ] . includes ( '1 package(s) over their suite-duration ceiling' ) ||
672+ both . out . length !== 1 ||
673+ both . out . some ( ( l ) => l . includes ( 'brand-new' ) )
674+ ) {
675+ throw new Error ( `new: a new package was counted red beside an over one:\n${ both . err [ 0 ] } \n${ JSON . stringify ( both . out ) } ` ) ;
676+ }
677+ } ) ;
678+ check ( ( ) => {
679+ // Alone, it grades nothing: NOT MEASURED, exit 0.
680+ const alone = run ( [ capture ( '2/6' , [ [ 'brand-new' , 9999 ] ] ) ] ) ;
681+ if ( alone . exitCode !== 0 || alone . verdict !== 'NOT MEASURED' || status ( alone , 'brand-new' ) !== 'new' ) {
682+ throw new Error ( `new: a new package alone read ${ alone . verdict } , exit ${ alone . exitCode } ` ) ;
641683 }
642684 } ) ;
643685 check ( ( ) => {
@@ -648,6 +690,38 @@ function selfTest() {
648690 const carried = run ( [ capture ( '2/6' , [ [ 'carried' , 9999 ] , [ 'a' , 1 ] ] ) ] ) ;
649691 if ( ! carried . err [ 0 ] . includes ( 'carried: 9999.00s -- no ceiling: carried' ) ) throw new Error ( `uncapped: the reason is not named:\n${ carried . err [ 0 ] } ` ) ;
650692 } ) ;
693+ // A package the dataset KNOWS that lost its row is not new: it is refused, so
694+ // it stays red rather than passing as "no ceiling: new package".
695+ const refusal = ( fn ) => {
696+ try {
697+ fn ( ) ;
698+ return null ;
699+ } catch ( err ) {
700+ return err ?. message ?? String ( err ) ;
701+ }
702+ } ;
703+ check ( ( ) => {
704+ // A weight with neither a ceiling nor an `uncapped` reason (`b` lost its ceiling row).
705+ const why = refusal ( ( ) => run ( [ capture ( '1/6' , [ [ 'b' , 1 ] ] ) ] , dataset ( { ceilings : { a : 150 , cli : 2000 } } ) ) ) ;
706+ if ( why === null || ! why . includes ( 'neither a ceiling nor an `uncapped`' ) || ! why . includes ( '(b)' ) ) {
707+ throw new Error ( `lost row: a weighed package without a ceiling row was graded (${ why } )` ) ;
708+ }
709+ } ) ;
710+ check ( ( ) => {
711+ // A held ceiling with no weight (`b` lost its weight row).
712+ const why = refusal ( ( ) => run ( [ capture ( '1/6' , [ [ 'b' , 1 ] ] ) ] , dataset ( { packages : { a : 100 , prov : 10 , carried : 20 , cli : 1500 } } ) ) ) ;
713+ if ( why === null || ! why . includes ( 'but no weight (b)' ) ) throw new Error ( `lost row: a ceiling with no weight was graded as new (${ why } )` ) ;
714+ } ) ;
715+ check ( ( ) => {
716+ // An `uncapped` reason with no weight.
717+ const why = refusal ( ( ) => run ( [ capture ( '1/6' , [ [ 'carried' , 1 ] ] ) ] , dataset ( { packages : { a : 100 , b : 50 , prov : 10 , cli : 1500 } } ) ) ) ;
718+ if ( why === null || ! why . includes ( 'but no weight (carried)' ) ) throw new Error ( `lost row: an uncapped reason with no weight was graded as new (${ why } )` ) ;
719+ } ) ;
720+ check ( ( ) => {
721+ // A `provisional` mark with no weight.
722+ const why = refusal ( ( ) => run ( [ capture ( '1/6' , [ [ 'ghost' , 1 ] ] ) ] , dataset ( { provisional : [ 'prov' , 'ghost' ] } ) ) ) ;
723+ if ( why === null || ! why . includes ( 'but no weight (ghost)' ) ) throw new Error ( `lost row: a provisional mark with no weight was graded as new (${ why } )` ) ;
724+ } ) ;
651725
652726 battery ( 'no ceiling table yet is NOT MEASURED, never red' ) ;
653727 const { ceilings : _c , uncapped : _u , ...preCeilings } = dataset ( ) ;
@@ -727,9 +801,16 @@ function selfTest() {
727801 if ( r . exitCode !== 1 || ! r . err [ 0 ] . includes ( raiseUrl ) ) throw new Error ( `raise: over the raise was not red, or the ruling not named:\n${ r . err [ 0 ] } ` ) ;
728802 } ) ;
729803 check ( ( ) => {
730- // A new package's first ceiling, before any refresh could give it one .
804+ // A ruled ceiling for a new package grades it before the first refresh after it lands could .
731805 const r = run ( [ capture ( '1/6' , [ [ 'brand-new' , 5 ] ] ) ] , dataset ( ) , { 'brand-new' : { seconds : 30 , ruling : raiseUrl } } ) ;
732- if ( r . exitCode !== 0 || status ( r , 'brand-new' ) !== 'ok' ) throw new Error ( `raise: a ruled first ceiling did not lift the new-package red (${ status ( r , 'brand-new' ) } )` ) ;
806+ if ( r . exitCode !== 0 || status ( r , 'brand-new' ) !== 'ok' ) throw new Error ( `raise: a ruled ceiling did not grade a new package (${ status ( r , 'brand-new' ) } )` ) ;
807+ } ) ;
808+ check ( ( ) => {
809+ // ...and one it can run over: a ruled ceiling is a ceiling, new package or not.
810+ const r = run ( [ capture ( '1/6' , [ [ 'brand-new' , 31 ] ] ) ] , dataset ( ) , { 'brand-new' : { seconds : 30 , ruling : raiseUrl } } ) ;
811+ if ( r . exitCode !== 1 || status ( r , 'brand-new' ) !== 'over' || ! r . err [ 0 ] . includes ( 'brand-new: 31.00s measured against its 30.00s ceiling' ) ) {
812+ throw new Error ( `raise: a new package over its ruled ceiling was not red (${ status ( r , 'brand-new' ) } ):\n${ r . err [ 0 ] } ` ) ;
813+ }
733814 } ) ;
734815 check ( ( ) => {
735816 // A raise at or under the generated ceiling changes nothing, and says so.
0 commit comments