@@ -57,7 +57,7 @@ import { expandViewContainer, isAggregatedViewContainer } from '@objectstack/spe
5757 */
5858export interface PackagedBaseRefusalHost {
5959 isSealedManagedItem : ObjectStackProtocolImplementation [ 'isSealedManagedItem' ] ;
60- isOverlayableManagedItem : ObjectStackProtocolImplementation [ 'isOverlayableManagedItem ' ] ;
60+ overlayBaseShippers : ObjectStackProtocolImplementation [ 'overlayBaseShippers ' ] ;
6161 isWritablePackage : ObjectStackProtocolImplementation [ 'isWritablePackage' ] ;
6262 packagedBaseRefusal : ObjectStackProtocolImplementation [ 'packagedBaseRefusal' ] ;
6363 packagedArtifactOwner : ObjectStackProtocolImplementation [ 'packagedArtifactOwner' ] ;
@@ -220,13 +220,12 @@ export type PackagelessWriteOperation = 'save' | 'publish' | 'delete' | 'rollbac
220220 *
221221 * [ADR-0146 D11] The closure of the package-less write onto a packaged item:
222222 * a write naming no package (`packageId` absent, `null` or empty) onto an item
223- * a code package ships, of a type whose registry entry opens an overlay
224- * channel ({@link PackagedBaseRefusalHost.isOverlayableManagedItem}: the five
225- * `allowEnvironmentOverlay` types), is refused `403 NOT_OVERRIDABLE` — the
226- * code and status the sealed write already answers, so no door gains a code.
227- * Every other type's packaged item is sealed already
228- * ({@link refusePackagedBaseOverride}), so after this no package-less write
229- * reaches a packaged item at all.
223+ * of one of the five overlay types that a read-only code package ships
224+ * ({@link PackagedBaseRefusalHost.overlayBaseShippers}, the reading
225+ * `os migrate packageless-overlays` classifies by), is refused
226+ * `403 NOT_OVERRIDABLE` — the code and status the sealed write already
227+ * answers, so no door gains a code. Every other type's packaged item is
228+ * sealed already ({@link refusePackagedBaseOverride}).
230229 *
231230 * The sentence names the fix the writer has: the base's paired `.custom`
232231 * (`?package=<base>.custom`), where the customization is a declared override,
@@ -235,32 +234,45 @@ export type PackagelessWriteOperation = 'save' | 'publish' | 'delete' | 'rollbac
235234 * moved yet is still served (the read path is not this function's); only its
236235 * package-less write is refused, by this same sentence.
237236 *
238- * `null` when the write names a package, or the item is no packaged item of an
239- * overlay type: a name no package ships is environment metadata and keeps its
240- * package-less write (ADR-0146 D11, "is not touched").
237+ * `null` when the write names a package; when no read-only code package ships
238+ * the name (environment metadata keeps its package-less write, ADR-0146 D11
239+ * "is not touched"); and when no base that ships it has a pair at all
240+ * (`customizationPackageId` refuses every one of their ids): the ceremony
241+ * lists such a row `no_pair` and cannot move it, and D11 closes only what it
242+ * has migrated — closing it would leave that customization no write door.
241243 */
242244export function packagelessOverlayWriteRefusal (
243- this : Pick < PackagedBaseRefusalHost , 'isOverlayableManagedItem' | 'packagedArtifactOwner '> ,
245+ this : Pick < PackagedBaseRefusalHost , 'overlayBaseShippers ' > ,
244246 request : { type : string ; name : string ; packageId ?: string | null } ,
245247 operation : PackagelessWriteOperation ,
246248) : Error | null {
247249 if ( typeof request . packageId === 'string' && request . packageId . length > 0 ) return null ;
248- if ( ! this . isOverlayableManagedItem ( request . type , request . name ) ) return null ;
250+ const shippers = this . overlayBaseShippers ( request . type , request . name ) ;
251+ const pairs = shippers . flatMap ( ( base ) => {
252+ const derived = customizationPackageId ( base ) ;
253+ return derived . ok ? [ { base, pair : derived . value } ] : [ ] ;
254+ } ) ;
255+ if ( pairs . length === 0 ) return null ;
249256 const type = PLURAL_TO_SINGULAR [ request . type ] ?? request . type ;
250257 const item = `'${ type } /${ request . name } '` ;
251- const owner = this . packagedArtifactOwner ( request ) ;
252- const pairId = typeof owner === 'string' ? customizationPackageId ( owner ) : undefined ;
253- const pair = pairId ?. ok ? `'${ pairId . value } '` : "the base's paired .custom package" ;
254- const query = pairId ?. ok ? ` (?package=${ pairId . value } )` : '' ;
255- const shipped = typeof owner === 'string' ? `ships in '${ owner } '` : 'ships in an installed package' ;
256- const declared = typeof owner === 'string' ? `overrides: { package: '${ owner } ', basedOn }` : 'overrides: { package, basedOn }' ;
257- const ceremony = 'A package-less customization stored before this release is still served; run '
258- + `\`os migrate packageless-overlays\` first to move it into ${ pair } .` ;
258+ const shipped = `ships in ${ shippers . map ( ( id ) => `'${ id } '` ) . join ( ' and ' ) } ` ;
259+ const sole = pairs . length === 1 ? pairs [ 0 ] ! : undefined ;
260+ // One base: its pair, by name. Several: the pair of the base the writer
261+ // customizes, each named — never guessed (the ceremony lists such a row).
262+ const into = sole
263+ ? `'${ sole . pair } ' (?package=${ sole . pair } )`
264+ : `the pair of the package it customizes (${ pairs . map ( ( p ) => `'${ p . pair } '` ) . join ( ' or ' ) } , as ?package=<that pair>)` ;
265+ const declared = sole ? `overrides: { package: '${ sole . base } ', basedOn }` : 'overrides: { package: <that base>, basedOn }' ;
266+ const ceremony = sole
267+ ? 'A package-less customization stored before this release is still served; run '
268+ + `\`os migrate packageless-overlays\` first to move it into '${ sole . pair } '.`
269+ : 'A package-less customization stored before this release is still served; run '
270+ + '`os migrate packageless-overlays` first: it lists a name several packages ship for you to place.' ;
259271 const fix = operation === 'delete'
260- ? `Remove it from ${ pair } ${ query } . ${ ceremony } `
272+ ? `Remove it from ${ into } . ${ ceremony } `
261273 : operation === 'rollback'
262- ? `Its customization belongs in ${ pair } : run \`os migrate packageless-overlays\` to move the row there, then roll it back.`
263- : `${ operation === 'publish' ? 'Publish' : 'Save' } it into ${ pair } ${ query } , declaring ${ declared } . ${ ceremony } ` ;
274+ ? `Its customization belongs in ${ into } : run \`os migrate packageless-overlays\` to move the row there, then roll it back.`
275+ : `${ operation === 'publish' ? 'Publish' : 'Save' } it into ${ into } , declaring ${ declared } . ${ ceremony } ` ;
264276 const what = operation === 'rollback' ? 'a rollback of its package-less row' : 'a write naming no package' ;
265277 const err = new Error (
266278 `${ item } ${ shipped } , and ${ what } onto a packaged item is closed: a customization of an installed package `
0 commit comments