7575 * none of which writes, and before any write.
7676 * A mode the package's `installModes` does not permit answers
7777 * `422 INSTALL_MODE_NOT_PERMITTED`, before any write.
78+ * Either mode reads the body's `withheldMaps` — the connector header
79+ * and environment maps a package export withheld whole (#28640
80+ * ruling ③A) — accepts the package, and names each map in its
81+ * answer's `withheldMaps` and in one `warn` line; a list the export's
82+ * declaration refuses answers `PLUGIN_MANIFEST_INVALID` (400 inline,
83+ * 502 cloud), before any write.
7884 *
7985 * [ADR-0126 §6 wall 3] A managed RE-install over an installed package
8086 * asks the security plugin's access upgrade check
@@ -187,6 +193,9 @@ import {
187193 unmanagedInstallValueCarries ,
188194 type InstallMode ,
189195} from '@objectstack/spec/kernel' ;
196+ // [#28640 ruling ③A] The export's list of the connector maps it withheld whole,
197+ // read by both install modes through its one declaration.
198+ import { PackageExportManifestSchema , type PackageExportManifest } from '@objectstack/spec/api' ;
190199// [#21762] ADR-0087 D1's protocol handshake, its brand predicate and the ONE
191200// answer both package-install doors give its refusal. Imported from the
192201// producer, never restated: `POST /api/v1/packages` answers through the same
@@ -602,6 +611,57 @@ function isPlainObject(value: unknown): value is Record<string, any> {
602611 return value !== null && typeof value === 'object' && ! Array . isArray ( value ) ;
603612}
604613
614+ /** One entry of an export's `withheldMaps` ({@link PackageExportManifestSchema}). */
615+ type WithheldMap = NonNullable < PackageExportManifest [ 'withheldMaps' ] > [ number ] ;
616+
617+ /**
618+ * [#28640 ruling ③A] The connector maps the export that produced this body
619+ * withheld whole — its `withheldMaps`, parsed through the export's own
620+ * declaration ({@link PackageExportManifestSchema}), by reference. Read by
621+ * BOTH install modes before any write, so each answer names every map that
622+ * arrives incomplete instead of letting it read as one the package never had.
623+ *
624+ * An absent list is `[]`: a body that is not an export, or an export that
625+ * withheld nothing. A list the declaration refuses is refused here, the way
626+ * an invalid `installModes` is ({@link judgeInstallMode}): it can only have
627+ * been written by hand, and guessing which maps it meant is the silent answer
628+ * the list exists to replace. The import never refuses a package FOR an
629+ * incomplete map (the ruling: accept, and name each one).
630+ */
631+ function judgeWithheldMaps (
632+ manifest : Record < string , any > ,
633+ manifestId : string ,
634+ invalidManifestStatus : 400 | 502 ,
635+ ) : { ok : true ; withheldMaps : WithheldMap [ ] } | { ok : false ; status : number ; error : { code : string ; message : string } } {
636+ const declared = PackageExportManifestSchema . shape . withheldMaps . safeParse ( manifest . withheldMaps ) ;
637+ if ( ! declared . success ) {
638+ const [ issue ] = declared . error . issues ;
639+ return {
640+ ok : false ,
641+ status : invalidManifestStatus ,
642+ error : {
643+ code : 'PLUGIN_MANIFEST_INVALID' ,
644+ message : `Package '${ manifestId } ' carries an invalid \`withheldMaps\`: ${ issue ?. message ?? 'refused by its declaration' } `
645+ + `${ issue && issue . path . length > 0 ? ` (at \`withheldMaps.${ issue . path . join ( '.' ) } \`)` : '' } . `
646+ + 'Send the list as the package export wrote it, or omit it.' ,
647+ } ,
648+ } ;
649+ }
650+ return { ok : true , withheldMaps : declared . data ?? [ ] } ;
651+ }
652+
653+ /**
654+ * [#28640 ruling ③A] The operator's line for an install that received
655+ * withheld maps: each one named, once, at `warn` — the connector is installed
656+ * and runs, without the headers or environment variables the export held back.
657+ */
658+ function withheldMapsLogLine ( manifestId : string , mode : InstallMode , withheldMaps : readonly WithheldMap [ ] ) : string {
659+ const named = withheldMaps . map ( ( m ) => `${ m . type } '${ m . name } ' ${ m . path } ` ) . join ( ', ' ) ;
660+ return `[MarketplaceInstallLocal] installed ${ manifestId } ${ mode } with ${ withheldMaps . length } connector map(s) the `
661+ + `package export withheld whole, each now absent: ${ named } . The connector sends none of those headers or `
662+ + 'environment variables until each map is supplied again where the connector is authored.' ;
663+ }
664+
605665/**
606666 * [ADR-0131 D6] The package manifest an install body DECLARES — where each of
607667 * the two single-package shapes this door accepts carries it: a compiled
@@ -1925,6 +1985,15 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
19251985 } , UNRUNNABLE_REFUSAL_STATUS ) ;
19261986 }
19271987
1988+ // 1d'. [#28640 ruling ③A] THE MAPS THE EXPORT WITHHELD, read for both
1989+ // modes before any write: each answer names every connector map
1990+ // that arrives incomplete ({@link judgeWithheldMaps}). Accepted,
1991+ // never refused for, as the ruling has it; only a list the export's
1992+ // declaration refuses is.
1993+ const withheld = judgeWithheldMaps ( manifest , manifestId , inlineManifest ? 400 : 502 ) ;
1994+ if ( ! withheld . ok ) return c . json ( { success : false , error : withheld . error } , withheld . status as any ) ;
1995+ const { withheldMaps } = withheld ;
1996+
19281997 // ── [ADR-0131 D6] The `unmanaged` install: a copy, not a registration.
19291998 // The package's metadata is copied ONCE into a new writable base in
19301999 // the environment ledger by the import verb, and the steps below —
@@ -1940,6 +2009,7 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
19402009 version,
19412010 versionId : resolvedVersionId ,
19422011 userId,
2012+ withheldMaps,
19432013 } ) ;
19442014 }
19452015
@@ -2308,6 +2378,7 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
23082378 // The door is named only when it can store one of them: a listing that
23092379 // says none is writable (a walled posture) gets the other places.
23102380 const door = described ?. writable === undefined || unsetRefs . some ( ( r ) => described . writable ! . has ( r ) ) ;
2381+ if ( withheldMaps . length > 0 ) ctx . logger ?. warn ?.( withheldMapsLogLine ( manifestId , 'managed' , withheldMaps ) ) ;
23112382
23122383 return c . json ( {
23132384 success : true ,
@@ -2333,6 +2404,10 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
23332404 // that declares none, so that answer's key set is unchanged.
23342405 ...( datasources !== undefined ? { datasources } : { } ) ,
23352406 ...( unsetRefs . length > 0 ? { credentialNote : installedCredentialNote ( unsetRefs , { door } ) } : { } ) ,
2407+ // [#28640 ruling ③A] Each connector map the package export
2408+ // withheld whole, so it arrived absent. Omitted when there are
2409+ // none, so that answer's key set is unchanged.
2410+ ...( withheldMaps . length > 0 ? { withheldMaps } : { } ) ,
23362411 upgradedFrom : conflict === 'marketplace' ? 'previous-marketplace-version' : null ,
23372412 // [ADR-0146 D2, #28410] Names installed dependents already held,
23382413 // undeclared, that this version now ships (step 2d). Omitted when
@@ -3538,14 +3613,21 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
35383613 * either spelling, and the copy does not keep, with the package's
35393614 * reason — is the verb's own
35403615 * list, relayed as the verb states it, so the installer reads why the
3541- * vendor locked an item before editing it.
3616+ * vendor locked an item before editing it;
3617+ * - `withheldMaps` — every connector map the package export withheld
3618+ * whole, read by {@link handleInstall} before the verb runs — is on the
3619+ * answer and on a `warn` line: the copy carries each map absent.
35423620 */
35433621 private installUnmanaged = async (
35443622 c : any ,
35453623 ctx : PluginContext ,
3546- args : { manifest : Record < string , any > ; manifestId : string ; version : string ; versionId : string ; userId : string | null } ,
3624+ args : {
3625+ manifest : Record < string , any > ; manifestId : string ; version : string ; versionId : string ; userId : string | null ;
3626+ /** [#28640 ruling ③A] The connector maps the export withheld ({@link judgeWithheldMaps}). */
3627+ withheldMaps : readonly WithheldMap [ ] ;
3628+ } ,
35473629 ) : Promise < Response > => {
3548- const { manifest, manifestId, version, versionId, userId } = args ;
3630+ const { manifest, manifestId, version, versionId, userId, withheldMaps } = args ;
35493631 let protocol : UnmanagedImporter | undefined ;
35503632 try { protocol = ctx . getService < UnmanagedImporter > ( 'protocol' ) ; } catch { /* no protocol service */ }
35513633 if ( ! protocol || typeof protocol . importUnmanagedPackage !== 'function' ) {
@@ -3598,6 +3680,7 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
35983680 + `${ installedFrom . packageId } ${ installedFrom . version ? `@${ installedFrom . version } ` : '' } `
35993681 + `${ userId ? ` by ${ userId } ` : '' } . Nothing is registered under '${ manifestId } '; the copy follows no upgrade.` ,
36003682 ) ;
3683+ if ( withheldMaps . length > 0 ) ctx . logger ?. warn ?.( withheldMapsLogLine ( manifestId , 'unmanaged' , withheldMaps ) ) ;
36013684 const declaresSeeds = unmanagedInstallValueCarries ( packageBody . data ) ;
36023685 return c . json ( {
36033686 success : true ,
@@ -3613,6 +3696,10 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
36133696 copiedCount : result . copiedCount ,
36143697 copied : result . copied ,
36153698 strippedLocks : result . strippedLocks ,
3699+ // [#28640 ruling ③A] Each connector map the package export
3700+ // withheld whole, so its copy carries it absent. Omitted when
3701+ // there are none, so that answer's key set is unchanged.
3702+ ...( withheldMaps . length > 0 ? { withheldMaps : [ ...withheldMaps ] } : { } ) ,
36163703 ...( declaresSeeds
36173704 ? {
36183705 seeded : {
0 commit comments