Setting protect: true in resource options marks a resource as undeletable. Any operation that would delete it, including a replacement, fails with an error naming the resource and instructing that protection be removed first. pulumi destroy on a stack containing a protected resource fails without deleting anything.
Clearing it requires an update that sets protect: false and applies, before the destroy. pulumi state unprotect URN does the same without a code change, and pulumi state unprotect --all clears the whole stack, which is the fast path for tearing down an environment.
Pulumi also supports retainOnDelete: true, which is different: the resource is removed from state on delete but left alive in the cloud. That is the right option for adopting a resource into another stack or for handing ownership to another team, and the wrong one when the intent is protection, because the resource then becomes an untracked orphan. Since Pulumi 3.x these can also be applied in bulk through transformations, which is how a whole project enforces protection on stateful resource types.