Paid For With Blood
Starwind Builder's historical protectedTable abstraction kept a local shadowSettings cache over a storage-backed settings group.
It also allowed the normal subscription handler to be replaced.
That flexibility created a hole: when the handler was overridden entirely, the normal path that refreshed the shadow cache no longer ran.
Commit 25c3661 added an alternate subscription whose sole job was clearing shadowSettings when the underlying group changed.
H3 Pattern: ProtectedTable makes the settings/runtime split visible, but a custom subscription still owns invalidation. The abstraction does not remove the dependency graph.
What went wrong
The abstraction made two behaviors look independent:
- what happens when storage changes;
- how the local cache remains coherent.
They were not independent.
The cache's correctness depended on lifecycle behavior hidden inside the default subscription path.
Replace the handler, and you replaced part of the cache protocol too.
The rule
A cache is not complete until its invalidation contract is explicit.
Ask:
- What makes this value stale?
- Who observes that event?
- Does every customization path preserve invalidation?
- Does
nilmean absent, uncached, or intentionally cached absence? - Can consumers tell whether the value is authoritative or derived?
If those answers live only in a metatable's fallback behavior, the abstraction is carrying too much hidden state.
See State Ownership and Invalidation and Clever Proxies and Magic Tables.