One page in a group of event pages came out of the tier assignments with no tier value, and nothing threw. First, how we got there.

The group needed tier-aware navigation. Our first plan sketched a feature catalog, a checks layer, and a table mapping organizations to allowed features. That is a familiar reflex: someone says “put these behind a plan” and a new subsystem starts to feel inevitable. Before building it, we looked for whether an existing structure already decided how the interface should present those pages.

It did. An earlier navigation change had given every page a minPkg field. The nav builder used that field to render an upgrade prompt instead of a live link when an organization’s package did not cover the page. That was already the right UI mechanism, even though it came from a navigation change and not a pricing project.

Each item in the navigation list corresponded to a page in the same site structure. For the interface question, “what tier should this page advertise” and “what tier should this navigation item show” were the same question, and the structure already held the answer. Server-side checks were still needed wherever an action itself required protection.

What we wrote instead of a new system

The UI change was a small set of guarded assignments in the site-structure setup, one per page, each assigning a tier value to .minPkg:

if (pages.example) pages.example.minPkg = 'tier-name';

That avoided a second navigation table or UI catalog that would have to stay in sync with the first. The work stayed on a prototype branch, not a production release, so the ordering problem below surfaced before it reached users.

The guard that skipped a page that didn’t exist yet

Most of the assignments worked the first time. The page that got no value was the one that wasn’t static. The base site structure added it later, through a separate feature-registration call. Our tier-assignment block ran before that call, so the if (pages.example) guard found nothing and skipped it quietly. The later registration then created the page without a navigation tier. The page came into existence after its own UI rule had already been attempted.

The guard is what made it silent. Without it the line would have thrown on an undefined page object and we would have seen it at once. With it, the line did nothing, and the page simply rendered as a live link.

The fix was to apply the same tier assignment again after the feature-registration call, when the page object existed.

A separate server-side feature-access mechanism also existed, for actions that don’t map cleanly onto one navigation page. Its checks are intentionally independent of the navigation field. Where a page and a protected action overlap, the UI and server paths still have to agree on the business rule, and neither replaces the other.

What did not ship

This stayed on an experimental branch and did not become a production entitlement system. It shows a reusable navigation field found before a new subsystem got built, and an ordering defect caught before release. It does not show that the prototype became the platform’s final access model.

Before approving a new gating system, check whether an existing field already controls how each page is presented. Where configuration is applied declaratively, confirm that entries added later by separate registration calls actually received it. Keep the server-side checks for anything that needs real protection.