You are right that the two events are not the same kind of signal, and the place that gets it wrong is narrower than it looks, which I think makes it worth stating precisely.
The Specification already ties the requirement to your reason. It says a registry that permits identityRegistry to change MUST emit DomainIdentityRegistryUpdated, because the field decides whether the agent-existence check applies at all. So the normative half is there and it is deliberate. What is wrong is the Security Considerations line, which tells implementers to monitor that event for the same reason they monitor DomainProgramUpdated. Same action, different reason, and flattening them loses the part that matters.
The difference is that zero is ambiguous and a program key is not.
identityRegistry == address(0) // no registry on this chain
identityRegistry == address(0) // the check was turned off
A current programKey interprets itself. You read it, you compare it to what you saw before, and if you have no prior baseline you take today as the baseline and you have lost nothing going forward. A current identityRegistry of zero does not interpret itself. Check 2 is conditional on the declaration and that was deliberate, since verdicts are meant to be consumed on spoke chains that need not host an Identity Registry at all. But it means a domain that never declared one and a domain that declared one and withdrew it are the same state read.
I want to be careful not to overstate it, because the history is recoverable. Anyone can backfill DomainIdentityRegistryUpdated from the registry’s deployment block and separate the two cases: no log at all means never declared, a nonzero followed by a zero means withdrawn. So the correct claim is not that the fact is lost. It is that the fact is not in state, only in logs, and logs are not available to an on-chain consumer at all and are not guaranteed retrievable from a node once history expiry lands. For programKey a state read is a valid fallback. For identityRegistry it is not. That is the sentence Security Considerations should carry, and I will make that edit after today’s editor call rather than move the head while it is on the agenda.
Your reading of the immutable surface is right and worth stating in the stronger form, with one qualification I should not skip. In the reference registry verifier, registrar and maxRootAge are written once in registerDomain and have no setter anywhere, and revokeDomain only clears active with no reactivation path. But IPolicyDomainRegistry is a RECOMMENDED companion and the ERC imposes nothing about setters, so a conforming registry may add them. For the Domain record as the reference implements it:
| Field |
Mutable |
What a change means |
Current value self-interpreting |
programKey |
yes |
interpreter change |
yes |
identityRegistry |
yes |
relaxation or tightening |
no, zero is ambiguous |
active |
one way |
revocation |
yes |
verifier, registrar, maxRootAge |
no |
n/a |
n/a |
That is the Domain record and not the whole enforcement surface. The policy root is mutable too, through updateRoot, and it gates check 8 by way of isRootAcceptable. I left it out because it is the one piece already designed to move, but a relying party tracking what can change under it has to track that as well.
The open question your point raises is whether any of this should be normative rather than a property of one implementation. Right now a relying party reading an arbitrary conforming registry cannot rely on a single row of that table.
Your closing point is the one I would keep. Drift detection tells you the interpreter moved. It does not tell you whether the move was legitimate, and it says nothing at all about a domain narrowing what it checks while the interpreter sits still. The second half is the one I had not reached. Both are better arguments for the fidelity record staying openable than the one I made, since a record you can open is a record you can disagree with, and neither of those two questions can be answered by comparing hashes.