Although ITS & PS are known to be tightly-coupled, the current code seems to be more than that.
Current ITS/PS look more like conjoined twins, instead of working side-by-side and still being two identities.
One of the tricky stuff is that, PS_xxx is defined under ITS scope.
(https://git.trustedfirmware.org/TF-M/trusted-firmware-m.git/tree/secure_fw/partitions/internal_trusted_storage/CMakeLists.txt#n56)
Which relies on linking ITS library to get its own definition from ITS.
ps_encrypted_object.c & PS_ENCRYPTION for example:
(https://git.trustedfirmware.org/TF-M/trusted-firmware-m.git/tree/secure_fw/partitions/protected_storage/ps_encrypted_object.c#n15)
PS_ENCRYPTION is set to ON by default (config/config_default.cmake)
Source code, in ITS, will *see* the definition because of the $<$<BOOL:${PS_ENCRYPTION}>:PS_ENCRYPTION> statement in CMakeLists.txt
However, PS does not have the same definition in its CMakeLists.txt.
Theoretically, Source code under PS scope would have compiling error when it compiles ps_encrypted_object.c, because the code uses the field encapsulated by PS_ENCRYPTION, which is defined in ps_object_defs.h.
Magically, PS_ENCRYPTION is defined when it compiles ps_encrypted_object.c.
Another example is TFM_PARTITION_PROTECTED_STORAGE
It's being used in both ITS & PS, and the tfm_ps_init() is almost identical to the code in the TFM_PARTITION_PROTECTED_STORAGE scope in tfm_its_init().