@@ -75,6 +75,7 @@ import { afterEach, describe, expect, it } from 'vitest';
7575// would close a dependency cycle turbo rejects outright.
7676import { assertEngineDeleteDispatch , assertEngineUpdateDispatch , assertEngineFindOnePredicate } from '@objectstack/metadata-core' ;
7777import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel' ;
78+ import { SharingConfigSchema } from '@objectstack/spec/ui' ;
7879import { ObjectStackProtocolImplementation } from './protocol.js' ;
7980
8081interface Row {
@@ -847,4 +848,56 @@ describe('org-scoped anonymous form intake changes the anonymous doors cannot se
847848 } ) ;
848849 expect ( on . success ) . toBe ( true ) ;
849850 } ) ;
851+
852+ // A package's shipped form is part of the env-wide definition, not a layer
853+ // of its own beneath it. A schema-parsed `false` on the artifact (the schema
854+ // defaults `enabled` to false) is an explicit withdrawal, so it fails closed;
855+ // and the env-wide definition is the administrator's switch, so an env-wide
856+ // save may open a form the package ships closed.
857+ describe ( 'single: a package-shipped form' , ( ) => {
858+ const LINK = '/forms/walled-intake' ;
859+ // As the loader serves it: parsed, `enabled` never switched on.
860+ const shipped = {
861+ name : 'task.intake_form' , label : 'Intake' , object : 'task' , viewKind : 'form' ,
862+ config : { sharing : SharingConfigSchema . parse ( { allowAnonymous : true , publicLink : LINK } ) } ,
863+ _packageId : 'showcase' ,
864+ } ;
865+
866+ function makePackageProtocol ( ) {
867+ const { engine, rows } = makeStubEngine ( ) ;
868+ engine . registry . listItems = ( type : string ) => ( type === 'view' ? [ shipped ] : [ ] ) ;
869+ engine . registry . getArtifactItem = ( type : string , name : string ) =>
870+ ( type === 'view' && name === shipped . name ? shipped : undefined ) ;
871+ const services = new Map < string , unknown > ( [ [ 'tenancy' , { defaultOrgId : async ( ) => 'org_a' } ] ] ) ;
872+ const protocol = new ObjectStackProtocolImplementation ( engine , ( ) => services , 'env_prod' ) as any ;
873+ return { protocol, rows } ;
874+ }
875+
876+ it ( 'the parsed artifact carries an explicit `false` that keeps the link' , ( ) => {
877+ expect ( shipped . config . sharing ) . toMatchObject ( { enabled : false , allowAnonymous : true , publicLink : LINK } ) ;
878+ } ) ;
879+
880+ it ( 'a schema-parsed `false` is a withdrawal: an org-scoped save that opens it is refused' , async ( ) => {
881+ const { protocol, rows } = makePackageProtocol ( ) ;
882+ await expect ( protocol . saveMetaItem ( {
883+ type : 'view' , name : 'task.intake_form' , item : FORM_VIEW ( true ) , organizationId : 'org_a' ,
884+ } ) ) . rejects . toMatchObject ( { code : 'NOT_OVERRIDABLE' , status : 403 , organizationId : 'org_a' } ) ;
885+ expect ( orgRows ( rows ) . filter ( ( r ) => r . org === 'org_a' ) ) . toEqual ( [ ] ) ;
886+ } ) ;
887+
888+ it ( 'the env-wide definition is the switch: an env-wide save opens it, and the env-wide list serves that body' , async ( ) => {
889+ const { protocol } = makePackageProtocol ( ) ;
890+ expect ( ( await protocol . saveMetaItem ( { type : 'view' , name : 'task.intake_form' , item : FORM_VIEW ( true ) } ) ) . success )
891+ . toBe ( true ) ;
892+ // The env-wide layer the anonymous doors read beneath an organization.
893+ const envWide : any = await protocol . getMetaItems ( { type : 'view' } ) ;
894+ const named = ( envWide . items as any [ ] ) . filter ( ( v ) => v ?. name === 'task.intake_form' ) ;
895+ expect ( named ) . toHaveLength ( 1 ) ;
896+ expect ( named [ 0 ] . config . sharing ) . toMatchObject ( { enabled : true , allowAnonymous : true , publicLink : LINK } ) ;
897+ // So an organization overlay that keeps it open is no longer refused.
898+ expect ( ( await protocol . saveMetaItem ( {
899+ type : 'view' , name : 'task.intake_form' , item : FORM_VIEW ( true ) , organizationId : 'org_a' ,
900+ } ) ) . success ) . toBe ( true ) ;
901+ } ) ;
902+ } ) ;
850903} ) ;
0 commit comments