@@ -2531,6 +2531,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
25312531 'The GRAIN: a PR-scoped declaration judged at PR scope (#16361)' : 25 ,
25322532 'THE DEPTH: a nested package is a candidate the axis can refuse (#16713)' : 21 ,
25332533 'THE ROOT: a packed `bin` target is a published surface the axis can refuse (#16692)' : 37 ,
2534+ 'THE COPY: a repo file a package build copies into what it packs (#15204)' : 22 ,
25342535 '#18263: the refusal says its reason, and says it where the API can read it' : 35 ,
25352536 '#19008: the level headline is the parsed declaration, not a literal' : 30 ,
25362537 'The protocol lockstep evidence, in BOTH directions (ruling 6056808625)' : 39 ,
@@ -2540,8 +2541,9 @@ const SELF_TEST_BATTERIES = Object.freeze({
25402541// zeroing it, so the roster's own size is pinned too. Lowered 20 → 19 when the
25412542// carrier-axis battery left with the gate label it read (ruling record
25422543// 5770886272 on #19061): the ordinary direction, one battery, one row. Raised
2543- // 19 → 20 with the protocol lockstep battery (ruling record 6056808625).
2544- const SELF_TEST_BATTERY_FLOOR = 20 ;
2544+ // 19 → 20 with the protocol lockstep battery (ruling record 6056808625). Raised
2545+ // 20 → 21 with the copy-leg battery (#15204).
2546+ const SELF_TEST_BATTERY_FLOOR = 21 ;
25452547
25462548// The key an assertion is filed under when no battery is open. It is not a
25472549// declared battery, so it reds by the same set difference rather than silently
@@ -4225,6 +4227,171 @@ function selfTest() {
42254227 ) ;
42264228 }
42274229
4230+ // ── THE COPY: a repo file a package's build copies into what it packs (#15204)
4231+ //
4232+ // `@objectstack/console` has no `src/**` and no `bin`; the one tracked file
4233+ // its `dist` ships is the repo-root `sdui.manifest.json`, copied there by
4234+ // `scripts/build-console.sh`. PR #22812 moved only that file (and spec
4235+ // citations), graded the console `minor` under a true `Clause-②: yes
4236+ // (widening)`, and was refused because no leg named the console.
4237+ battery ( 'THE COPY: a repo file a package build copies into what it packs (#15204)' ) ;
4238+ {
4239+ const CONSOLE = '@objectstack/console' ;
4240+ const SPEC = '@objectstack/spec' ;
4241+ const yesWidening = { value : 'yes' , arm : 'widening' , payload : true , readings : [ 'declaration line: `Clause-②: yes (widening)`' ] } ;
4242+ const declaredNo = { value : 'no' , arm : null , payload : true , readings : [ 'declaration line: `Clause-②: no`' ] } ;
4243+ const ROOT_LINE = 'FRAMEWORK_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"' ;
4244+ const BUILD = [
4245+ '#!/usr/bin/env bash' ,
4246+ ROOT_LINE ,
4247+ 'PINNED_SHA="$(cat "${FRAMEWORK_ROOT}/.objectui-sha")"' ,
4248+ 'BUILD_ROOT="${FRAMEWORK_ROOT}/.cache/objectui-${PINNED_SHA:0:12}"' ,
4249+ 'TARGET="${FRAMEWORK_ROOT}/packages/console/dist"' ,
4250+ 'CONSOLE_DIST="${BUILD_ROOT}/apps/console/dist"' ,
4251+ 'cp -R "$CONSOLE_DIST" "$TARGET"' ,
4252+ 'MANIFEST_SRC="${FRAMEWORK_ROOT}/sdui.manifest.json"' ,
4253+ 'MANIFEST_RECORD="${FRAMEWORK_ROOT}/scripts/sdui-manifest.record.json"' ,
4254+ 'cp "$MANIFEST_SRC" "${TARGET}/sdui.manifest.json"' ,
4255+ '' ,
4256+ ] . join ( '\n' ) ;
4257+ const pairs = ( text ) => JSON . stringify ( packedCopiesIn ( text ) ) ;
4258+ const MANIFEST_PAIR = '[{"source":"sdui.manifest.json","destination":"packages/console/dist/sdui.manifest.json"}]' ;
4259+
4260+ // ── The reader ────────────────────────────────────────────────────────
4261+ assert ( pairs ( BUILD ) === MANIFEST_PAIR , `the copy of a fixed repo file into a package dir is read, and ONLY it — got ${ pairs ( BUILD ) } ` ) ;
4262+ assert (
4263+ ! pairs ( BUILD ) . includes ( 'record' ) ,
4264+ 'control: a variable the script ASSIGNS but never copies (the manifest record, read for a warning line) is not a shipped file' ,
4265+ ) ;
4266+ assert (
4267+ pairs ( BUILD . replace ( ROOT_LINE , 'FRAMEWORK_ROOT="/somewhere"' ) ) === '[]' ,
4268+ 'control: with no variable holding the script\'s own parent dir there is no repo root to resolve against, so nothing is claimed' ,
4269+ ) ;
4270+ assert (
4271+ pairs ( `${ ROOT_LINE } \nT="\${FRAMEWORK_ROOT}/packages/a/dist"\nS="\${FRAMEWORK_ROOT}/x.json"\nS="\${FRAMEWORK_ROOT}/y.json"\ncp "$S" "$T/x.json"\n` ) ===
4272+ '[{"source":"y.json","destination":"packages/a/dist/x.json"}]' ,
4273+ 'assignments are evaluated in ORDER — a reassigned variable is read at the value it has on the `cp` line' ,
4274+ ) ;
4275+ assert (
4276+ pairs ( `${ ROOT_LINE } \ncp "\${UNSET}/x.json" "\${FRAMEWORK_ROOT}/packages/a/dist/x.json"\n` ) === '[]' ,
4277+ 'control: a source that keeps an unexpanded `$` is not one fixed path and is skipped rather than guessed at' ,
4278+ ) ;
4279+
4280+ // ⭐ THE PIN on the real script. The leg reads `build-console.sh`, so the
4281+ // day it stops copying the manifest (or respells the copy so the reader
4282+ // cannot see it) this row reds in lint instead of the leg retiring quietly.
4283+ const realBuild = readFileSync ( resolve ( REPO_ROOT , PACKED_COPY_BUILD_SCRIPTS [ 0 ] ) , 'utf8' ) ;
4284+ const realPairs = packedCopiesIn ( realBuild ) ;
4285+ assert (
4286+ realPairs . some ( ( p ) => p . source === 'sdui.manifest.json' && p . destination === 'packages/console/dist/sdui.manifest.json' ) ,
4287+ `this repository's ${ PACKED_COPY_BUILD_SCRIPTS [ 0 ] } copies the repo-root sdui.manifest.json into packages/console/dist/ as read by this leg — got ${ JSON . stringify ( realPairs ) } ` ,
4288+ ) ;
4289+ assert (
4290+ ! realPairs . some ( ( p ) => p . source === 'scripts/sdui-manifest.record.json' ) ,
4291+ `measured, not chosen: the real build does not ship the manifest record — got ${ JSON . stringify ( realPairs ) } ` ,
4292+ ) ;
4293+
4294+ // ── The owner of a destination ────────────────────────────────────────
4295+ const consoleManifest = ( files ) => ( dir ) => ( dir === 'packages/console' ? { name : CONSOLE , version : '0.0.0' , files } : null ) ;
4296+ assert ( packedCopyOwner ( 'packages/console/dist/sdui.manifest.json' , consoleManifest ( [ 'dist' , 'README.md' ] ) ) === 'packages/console' , 'a destination under a packed `files` entry names its package' ) ;
4297+ assert ( packedCopyOwner ( 'packages/console/dist/sdui.manifest.json' , consoleManifest ( undefined ) ) === 'packages/console' , 'an absent `files` packs everything — npm\'s rule' ) ;
4298+ assert ( packedCopyOwner ( 'packages/console/dist/sdui.manifest.json' , consoleManifest ( [ 'README.md' ] ) ) === null , 'control: a destination its package does not pack ships nothing, and owns nothing' ) ;
4299+ assert ( packedCopyOwner ( 'packages/console/dist/sdui.manifest.json' , ( dir ) => ( dir === 'packages/console' ? false : null ) ) === 'packages/console' , '#4690: an unparseable manifest is a candidate that cannot be named, never "no owner"' ) ;
4300+ assert ( packedCopyOwner ( 'dist/sdui.manifest.json' , ( ) => ( { name : 'x' } ) ) === null , 'control: a destination outside `packages/` is no workspace package' ) ;
4301+
4302+ // ── End to end, on real temp git repositories ─────────────────────────
4303+ const consoleJson = JSON . stringify ( { name : CONSOLE , version : '0.0.0' , files : [ 'dist' , 'README.md' , 'CHANGELOG.md' ] } ) ;
4304+ const specJson = JSON . stringify ( { name : SPEC , version : '0.0.0' , files : [ 'dist' ] } ) ;
4305+ const changeset = ( entries ) => `---\n${ entries . map ( ( [ p , b ] ) => `"${ p } ": ${ b } ` ) . join ( '\n' ) } \n---\n\nbody\n` ;
4306+ const copyRepo = ( { head, build = BUILD } ) =>
4307+ makeRepo (
4308+ {
4309+ 'scripts/build-console.sh' : build ,
4310+ 'packages/console/package.json' : consoleJson ,
4311+ 'packages/spec/package.json' : specJson ,
4312+ 'packages/spec/src/ui/x.ts' : '/** cites objectui@aaaa */\nexport const x = 1;\n' ,
4313+ 'sdui.manifest.json' : '{"components":["a","b"]}\n' ,
4314+ 'scripts/sdui-manifest.record.json' : '{"objectuiSha":"aaaa"}\n' ,
4315+ } ,
4316+ head ,
4317+ ) ;
4318+ const levelOf = ( { dir, base } , declaration ) => {
4319+ const scanned = scan ( { cwd : dir , base } ) ;
4320+ const touched = packagesTouched ( { cwd : dir , from : scanned . base , head : 'HEAD' } ) ;
4321+ const result = judgeLevel ( { levels : scanned . levels , touched, declaration } ) ;
4322+ return { touched, result, exitCode : renderLevel ( result ) . exitCode } ;
4323+ } ;
4324+ const PR22812 = {
4325+ 'sdui.manifest.json' : '{"components":["a","b","c"]}\n' ,
4326+ 'scripts/sdui-manifest.record.json' : '{"objectuiSha":"bbbb"}\n' ,
4327+ 'packages/spec/src/ui/x.ts' : '/** cites objectui@bbbb */\nexport const x = 1;\n' ,
4328+ '.changeset/console-bbbb.md' : changeset ( [ [ CONSOLE , 'minor' ] ] ) ,
4329+ '.changeset/citations-bbbb.md' : changeset ( [ [ SPEC , 'patch' ] ] ) ,
4330+ } ;
4331+
4332+ // THE CARD: #22812's shape is DISCHARGED by the console `minor`.
4333+ const card = levelOf ( copyRepo ( { head : PR22812 } ) , yesWidening ) ;
4334+ assert ( card . touched . packages . join ( ) === `${ CONSOLE } ,${ SPEC } ` && card . touched . unreadable . length === 0 , `THE CARD: the manifest move names ${ CONSOLE } beside the spec — got ${ JSON . stringify ( card . touched ) } ` ) ;
4335+ assert ( card . result . verdict === 'discharged' && card . exitCode === 0 , `THE CARD: console \`minor\` + spec \`patch\` + \`Clause-②: yes (widening)\` is discharged, exit 0 — got ${ card . result . verdict } / ${ card . exitCode } ` ) ;
4336+ assert ( card . result . raised . some ( ( r ) => r . pkg === CONSOLE && r . bump === 'minor' ) , 'and it is the CONSOLE line that discharges it, not anything else' ) ;
4337+
4338+ // FIRING control: the same diff with the build script gone is the
4339+ // pre-leg reading — refused. So the green above is this leg, not a lane
4340+ // that moved.
4341+ const noLeg = levelOf ( copyRepo ( { head : { ...PR22812 , 'scripts/build-console.sh' : null } } ) , yesWidening ) ;
4342+ assert (
4343+ ! noLeg . touched . packages . includes ( CONSOLE ) && noLeg . result . verdict === 'enforce' && noLeg . exitCode === 1 ,
4344+ `FIRING control: without the copy to read, the console is unseen and the same PR is refused, exactly as CI job 114433350057 was — got ${ JSON . stringify ( noLeg . touched ) } / ${ noLeg . result . verdict } ` ,
4345+ ) ;
4346+
4347+ // The manifest moved with NO console changeset at `minor` stays red —
4348+ // both when the console is graded `patch` and when it is not graded.
4349+ const consolePatch = levelOf ( copyRepo ( { head : { ...PR22812 , '.changeset/console-bbbb.md' : changeset ( [ [ CONSOLE , 'patch' ] ] ) } } ) , yesWidening ) ;
4350+ assert (
4351+ consolePatch . result . verdict === 'enforce' && consolePatch . exitCode === 1 && consolePatch . result . offenders . some ( ( o ) => o . packages . includes ( CONSOLE ) ) ,
4352+ `the manifest moved with the console graded \`patch\` under \`yes\` is REFUSED, naming the console — got ${ consolePatch . result . verdict } ` ,
4353+ ) ;
4354+ const manifestOnly = { 'sdui.manifest.json' : PR22812 [ 'sdui.manifest.json' ] , '.changeset/console-bbbb.md' : changeset ( [ [ CONSOLE , 'patch' ] ] ) } ;
4355+ const onlyPatch = levelOf ( copyRepo ( { head : manifestOnly } ) , yesWidening ) ;
4356+ assert (
4357+ onlyPatch . touched . packages . join ( ) === CONSOLE && onlyPatch . result . verdict === 'enforce' && onlyPatch . exitCode === 1 ,
4358+ `a manifest-only diff with the console at \`patch\` is refused on the console alone — got ${ JSON . stringify ( onlyPatch . touched ) } / ${ onlyPatch . result . verdict } ` ,
4359+ ) ;
4360+ const ungraded = levelOf ( copyRepo ( { head : Object . fromEntries ( Object . entries ( PR22812 ) . filter ( ( [ path ] ) => path !== '.changeset/console-bbbb.md' ) ) } ) , yesWidening ) ;
4361+ assert (
4362+ ungraded . result . verdict === 'enforce' && ungraded . exitCode === 1 ,
4363+ `the manifest moved with no console changeset at all (spec \`patch\` only) stays red — got ${ ungraded . result . verdict } ` ,
4364+ ) ;
4365+
4366+ // `Clause-②: no` is unaffected: the axis stands down whatever it sees.
4367+ const no = levelOf ( copyRepo ( { head : { ...PR22812 , '.changeset/console-bbbb.md' : changeset ( [ [ CONSOLE , 'patch' ] ] ) } } ) , declaredNo ) ;
4368+ assert ( no . result . verdict === 'not-declared' && no . exitCode === 0 , `the manifest moved under \`Clause-②: no\` is unaffected — got ${ no . result . verdict } / ${ no . exitCode } ` ) ;
4369+
4370+ // The record moves with every regeneration but ships nothing.
4371+ const recordOnly = levelOf (
4372+ copyRepo ( { head : { 'scripts/sdui-manifest.record.json' : '{"objectuiSha":"bbbb"}\n' , '.changeset/console-bbbb.md' : changeset ( [ [ CONSOLE , 'patch' ] ] ) } } ) ,
4373+ yesWidening ,
4374+ ) ;
4375+ assert (
4376+ recordOnly . touched . packages . length === 0 && recordOnly . touched . unreadable . length === 0 && recordOnly . result . verdict === 'clean' ,
4377+ `control: the record alone moves no published file — got ${ JSON . stringify ( recordOnly . touched ) } / ${ recordOnly . result . verdict } ` ,
4378+ ) ;
4379+
4380+ // #4690 on this leg: a build script that is THERE but yields no copy is
4381+ // a reading not made, reported beside the verdict rather than read as
4382+ // "copies nothing"; and an unparseable destination manifest is unreadable.
4383+ const blind = levelOf ( copyRepo ( { head : manifestOnly , build : '#!/usr/bin/env bash\ninstall -m 644 sdui.manifest.json packages/console/dist/\n' } ) , yesWidening ) ;
4384+ assert (
4385+ blind . touched . unreadable . includes ( 'scripts/build-console.sh' ) && renderLevel ( blind . result ) . stdout . join ( '\n' ) . includes ( 'scripts/build-console.sh' ) ,
4386+ `a build script the reader yields no copy from is REPORTED and printed, never silent — got ${ JSON . stringify ( blind . touched ) } ` ,
4387+ ) ;
4388+ const broken = levelOf ( copyRepo ( { head : { ...manifestOnly , 'packages/console/package.json' : '{ "name": \n' } } ) , yesWidening ) ;
4389+ assert (
4390+ broken . touched . unreadable . includes ( 'packages/console' ) && ! broken . touched . packages . includes ( CONSOLE ) ,
4391+ `an unparseable console manifest lands the copy in \`unreadable\`, never in neither set — got ${ JSON . stringify ( broken . touched ) } ` ,
4392+ ) ;
4393+ }
4394+
42284395 } finally {
42294396 for ( const dir of repos ) rmSync ( dir , { recursive : true , force : true } ) ;
42304397 }
0 commit comments