Governed operator lists (organizations, grants, people, billing×4) gain
server-side search, status filters, and 50-row pages with true totals
from count(*) OVER(); state is URL-addressable, out-of-range pages
clamp,
and no-match is distinct from true-empty.
People is the eighth flat sidebar entry: /operator/persons lists persons
newest-joined first (excluding the reserved system person), rows linking
to the existing detail.
Billing gains an operator invoice detail at
/operator/billing/invoices/{invoiceID} reusing the member projection;
open invoices past due present as Overdue (derived, filterable, stored
status untouched); all four views lead with the linked organization and
mute object IDs.
Grants filter over the derived Live/Superseded/Inactive state, the SQL
HAVING predicate pinned to the Go derivation by test. Embedded lists
(org composite ledger, Tier changes) adopt the shared controls under
namespaced params with sibling-state-preserving URLs and scoped htmx
swaps that hold the viewport.
Review corrections: blocked ladder Delete renders disabled with tooltip
and mutations fire toasts; collapse triggers paint their open state;
sections use outside headings; plan topology drops the orphan-product
check; domains policy collapses behind a disclosure.
44 KiB
operator-panel-navigation Specification
Purpose
TBD - created by archiving change fix-operator-panel-tabs. Update Purpose after archive.
Requirements
Requirement: Operator panel groups capabilities under a three-layer information architecture
The operator panel SHALL organize all capabilities under exactly three conceptual groups: Catalog, Runtime, and Integration. Catalog contains configuration entities edited rarely with wide blast radius (org_types, products, entitlement_sets, plan_ladders). Runtime contains per-organization operational surfaces (organizations, persons, grants, billing read-views). Integration contains system-specific surfaces (FedWiki sites today; other integrations later).
The groups govern URL grouping, conceptual categorization, breadcrumb derivation, and the one-way catalog → runtime → integration dependency direction. Capabilities SHALL NOT appear at the operator panel's top level outside one of these three groups; the conceptual grouping is carried by each page's machine-readable IA-position declaration.
Sidebar presentation is a flat list of exactly eight section entries and nothing else — no children, no indentation, no hairline-separated or headed groups (maintainer decisions 2026-08-23: the sidebar names the sections; the sections navigate themselves; People joined as the eighth entry with the directory). The render order SHALL be: Overview, People, Organizations, Grants, Billing, Products, Domains, Integrations. Second-level surfaces SHALL be reached in-page, not from the sidebar: person detail from the People directory's rows, org_types from the Organizations page's header affordance, the billing read-views from the Billing wrapper's in-page pill row, entitlement_sets and plan_ladders from the Products page's header affordances, and provider surfaces from the Integrations home's provider rows. The conceptual layer lives in the IA declarations; a second-level page SHALL mark its section's sidebar entry active.
Scenario: Catalog capabilities are reachable from within their task surfaces
- WHEN an operator views the Organizations page or the Products page
- THEN the Organizations page carries an affordance to
org_types, and the Products page carries affordances toentitlement_setsandplan_ladders - AND none of them appear in the sidebar
- AND their IA-position declarations still carry the Catalog conceptual group
Scenario: The sidebar is a flat list of section entries
- WHEN an operator views the operator panel sidebar
- THEN it renders exactly the eight entries Overview, People, Organizations, Grants, Billing, Products, Domains, Integrations in that order
- AND no child entries, indentation, or hairline-separated group blocks are rendered
Scenario: Sections navigate themselves
- WHEN an operator views an operator section with sub-surfaces
- THEN the sub-surfaces are reachable from within the section (the Billing wrapper's pill row, header affordances on the section's page, or the section's own rows)
- AND not from sidebar children
Scenario: Second-level pages mark their section active
- WHEN an operator is on a second-level page (for example person detail, org types, a billing view, or entitlement sets)
- THEN the sidebar marks that section's single entry (People, Organizations, Billing, or Products) as active, and no other
Requirement: Operator panel exposes a curated landing surface at /operator
The operator panel SHALL render a curated landing surface at /operator that answers "what is this deployment doing right now?" rather than defaulting to the first capability tab. The landing surface SHALL be the default destination when an operator navigates to /operator without further path or query parameters. The surface SHALL present, coarsest-first: a lookup affordance, an at-a-glance overview region, a recent-activity timeline, and a system-status region.
The landing-surface lookup affordance SHALL be a text input that accepts a person name (substring), an email address (exact), or an organization slug (exact). Submitting the input SHALL resolve the entry to one of: /operator/persons/{personID} (when a person match is unique), /operator/organizations/{orgID} (when an org slug matches), or an inline disambiguation listing (when multiple persons match a name substring). Resolution SHALL be performed server-side; no client-side autocomplete is required.
The at-a-glance region SHALL present headline metrics spanning the deployment's membership, organizations, catalog, billing, and entitlement-delivery state, each sourced from its owning module's query layer. The organizations headline SHALL count deliberately created organizations: active organizations whose type is neither the auto-created personal type nor a reserved type. Personal orgs exist one-per-member by structural convention (an organizations count that includes them tracks the people count and reads as an error), and the negative-space predicate means team, enterprise, and deployment-defined types all count without code changes while the reserved system org never does. Billing coverage SHALL include both commitment and receivables. Commitment SHALL render as monthly-normalized recurring money across active subscriptions: monthly prices at face value, yearly prices at one-twelfth, each times item quantity; trialing subscriptions SHALL be excluded from the money and stated in the caption. Receivables SHALL render as a count of invoices in open status (issued and awaiting payment) with the outstanding balance in the caption. Money values SHALL be summed per currency with the headline showing the largest currency bucket; when other currency buckets exist the caption SHALL acknowledge them, and cross-currency conversion is explicitly out of scope for this surface. The headline row is reserved for core capability state: per-feature counts (such as domain claims) SHALL NOT render as headline tiles — feature moderation surfaces are reached from the sidebar, not the landing row. Where a count's owning capability has a browse surface, its tile SHALL link there. Count sources SHALL degrade independently: a failed count query SHALL be logged and rendered as an explicit unavailable marker (an em dash) — NEVER as a zero, since a zero read off a broken query invites exactly the wrong operational conclusion — and SHALL NOT fail the page. Any count presented as "currently delivering" SHALL derive from pool_provisions.status (delivery state); it SHALL NOT be computed from grants.status alone, which is an issuance ledger, not current state.
The recent-activity section SHALL be a single unified timeline that interleaves grant events, transition events, invoice events, and payment events from across the operator panel, ordered by event timestamp (newest first). Each rendered event row SHALL identify the organization (and person, where applicable) and link to the originating organization composite, the originating person detail, or the relevant billing page based on the event type. The section is one timeline because an operator's mental model of "what's been happening" is temporal, not domain-partitioned — an invoice payment that lands near the grant transition that triggered it is more useful adjacent than separated. Money amounts in event rows SHALL render as formatted currency (symbol/code and decimal placement per the console's standard money formatting), never as raw integer minor units.
The system-status region SHALL list every registered integration provider (registry-driven, not a hardcoded subset). For each provider it SHALL show two honestly-labeled facts: the registry status (the operator-managed lifecycle value, labeled so it cannot read as a health-check result) and a configuration-readiness signal derived from the same required-key resolution the settings pages perform (a provider with any required key unresolved is visibly not configured). The region's copy SHALL NOT describe this state as "connected" or as being "read live" — the console performs no connectivity checks and the status is a stored value. The region SHALL report integration outbox depth split into pending, retrying, and dead-letter buckets, and its caption SHALL name which integrations' queued work the outbox actually covers, so an all-quiet outbox cannot imply health for integrations that do not enqueue through it. Only the dead-letter bucket — work that has exhausted retries — SHALL receive alarm styling; pending and retrying are normal draining states.
The landing surface's heading hierarchy SHALL descend without skipping levels (H1, then H2, …). The operator shell (page title, navbar brand) SHALL render the configured deployment name (deployment-name, defaulting to the generic product name), identically to the member surface, so the application presents one identity across surfaces; with the default value the shell remains deployment-neutral for screenshots and first impressions. The landing surface SHALL use the console's standard bordered-card presentation; it SHALL NOT introduce bespoke elevation or hover-motion card treatments absent from the rest of the console.
Scenario: Navigating to /operator shows the landing surface
- WHEN an authenticated operator navigates to
/operator - THEN the panel renders the curated landing surface
- THEN the landing surface includes an organization lookup entry point
- THEN the landing surface includes an at-a-glance region of headline metrics
- THEN the landing surface surfaces a single recent-activity section interleaving grants, transitions, invoices, and payments
- THEN the landing surface includes a system-status region
Scenario: Landing surface is the documented default
- WHEN an operator follows an internal link to
/operatorfrom anywhere in the panel - THEN the landing surface renders instead of an arbitrary capability tab
Scenario: Lookup input resolves a unique person match
- WHEN an operator submits the lookup input with a value that matches exactly one person (by email or name substring)
- THEN the panel issues a 302 redirect to
/operator/persons/{personID}for that match
Scenario: Lookup input resolves an organization slug
- WHEN an operator submits the lookup input with a value that matches an organization slug exactly
- THEN the panel issues a 302 redirect to
/operator/organizations/{orgID}for that organization
Scenario: Lookup input renders a disambiguation list on ambiguous match
- WHEN an operator submits the lookup input with a value that matches more than one person by name substring
- THEN the panel renders a disambiguation listing showing each candidate person
- THEN each candidate row links to
/operator/persons/{personID}for that person
Scenario: Lookup input reports no match
- WHEN an operator submits the lookup input with a value that matches no person or organization
- THEN the panel renders the landing surface with a visible no-match message
- THEN no redirect occurs
Scenario: Recent activity timeline interleaves event types
- WHEN an operator views
/operatorand any recent grant, transition, invoice, or payment events exist - THEN the "Recent activity" section renders one row per event ordered by event timestamp (newest first), interleaving event types
- THEN each row identifies the organization (and person, where applicable) and its event type
- THEN each row links to the originating organization composite, person detail, or relevant billing page based on event type
Scenario: Activity money renders as formatted currency
- WHEN the recent-activity timeline renders an invoice or payment event carrying a money amount
- THEN the amount renders as formatted currency with its currency named
- THEN no row renders a raw integer of minor units (e.g. "3600 usd")
Scenario: Organizations headline excludes auto-created personal orgs
- WHEN an operator views
/operatoron a deployment holding personal orgs, team orgs, and the reserved system org - THEN the organizations tile counts only active organizations that are neither
personal-typed nor reserved-typed - THEN its caption states the exclusion so the number is self-interpreting
Scenario: Billing commitment renders as monthly-normalized recurring money
- WHEN an operator views
/operatorwith active subscriptions on monthly and yearly prices - THEN the recurring-money headline sums monthly prices at face value and yearly prices at one-twelfth, times item quantity, over active subscriptions only
- THEN trialing subscriptions appear in the caption count but contribute nothing to the money
- WHEN the active subscriptions bill in more than one currency
- THEN the headline shows the largest currency bucket and the caption acknowledges the other buckets
Scenario: Billing coverage includes a receivables headline
- WHEN an operator views
/operator - THEN the at-a-glance region includes a tile counting invoices in
openstatus - THEN the tile links to
/operator/billing/invoices - THEN its caption states the outstanding balance and what was counted (issued and awaiting payment)
Scenario: People caption carries the 30-day trend
- WHEN an operator views
/operator - THEN the People tile's caption states how many persons joined in the last 30 days
Scenario: Delivering-grants headline counts deliberate grants
- WHEN an operator views
/operator - THEN the delivering-grants headline counts deliberately issued (non-default) delivering grants — the set an operator acts on
- THEN its caption names how many delivering signup defaults were excluded, so the full delivering picture stays one glance away
Scenario: Headline row carries no per-feature counts
- WHEN an operator views
/operator - THEN the at-a-glance region renders no domain-claims tile or other per-feature count
- THEN domain moderation remains reachable from the sidebar's Domains entry
Scenario: Landing cards use the console's standard idiom
- WHEN the landing surface renders
- THEN its cards render with the console's standard bordered-card treatment
- THEN no card carries a bespoke elevation (shadow) or hover-motion treatment
Scenario: A failed count degrades to an unavailable marker, not a zero
- WHEN an operator views
/operatorand one metric's backing query fails - THEN that tile renders an em dash with an unavailable caption instead of a value
- THEN every other tile renders its real value
- THEN the page returns 200 and the failure is logged
Scenario: Delivering count reflects provisions, not the grant ledger
- WHEN a grant row is
activeingrants.statusbut has no active row inpool_provisions - THEN the "delivering" headline count does not include it
Scenario: System region lists all registered providers with honest state
- WHEN an operator views
/operatorwith multiple integration providers registered - THEN the system-status region lists every registered provider with its registry status under a label that cannot read as a health check
- THEN a provider with an unresolved required key shows a visible not-configured signal
- THEN the region's copy makes no "connected" or "read live" claim
Scenario: Outbox caption names its coverage
- WHEN an operator views the system-status region's outbox report
- THEN the caption names which integrations' queued work the outbox covers
- THEN an empty outbox does not present itself as evidence that uncovered integrations are healthy
Scenario: Only dead-letter outbox work raises alarm styling
- WHEN the integration outbox contains pending and retrying entries but no dead-letter entries
- THEN the outbox report renders without alarm styling
- WHEN the integration outbox contains at least one dead-letter entry
- THEN the dead-letter bucket renders with alarm styling
Scenario: Shell renders the configured deployment name
- WHEN any operator page renders
- THEN the page title and navbar brand render the configured deployment name, matching the member surface
- THEN with no name configured, the shell renders the generic product name and stays deployment-neutral
Scenario: Landing heading hierarchy has no skips
- WHEN an operator views
/operator - THEN the document outline descends H1 → H2 (→ H3) with no skipped levels
Requirement: Per-organization composite view is addressable at /operator/organizations/{orgID}
The operator panel SHALL provide a per-organization composite view at the route /operator/organizations/{orgID} that co-locates enrollment state, grant history, billing summary, member roster, and grant actions for one organization. The route SHALL be bookmarkable and reload-stable. The composite view SHALL be reachable from the Runtime group's organization browse surface.
The billing-summary section SHALL show: the current subscription status and plan (if any), the next renewal date (if any), the most recent invoice (date, amount, paid status), and the outstanding balance across unpaid invoices. The section SHALL link out to /operator/billing/accounts for the full billing view.
The members section SHALL list all persons enrolled at the organization, sorted alphabetically by display name. Each row SHALL link to the person's resolved-detail view at /operator/persons/{personID}.
Scenario: Per-org composite view is bookmarkable
- WHEN an operator loads
/operator/organizations/{orgID}directly via URL - THEN the page renders the composite view for that organization without requiring prior navigation through the panel
Scenario: Composite view co-locates runtime work
- WHEN an operator views
/operator/organizations/{orgID} - THEN the page surfaces the organization's enrollment state, grant history, billing summary, and member roster on the same page
- THEN grant actions (issue, extend, revoke) are reachable from the same page without navigating to a different top-level capability
Scenario: Composite view exposes a billing summary
- WHEN an operator views
/operator/organizations/{orgID}for an organization with billing activity - THEN the page renders a billing-summary section showing current subscription status, next renewal date, most recent invoice (date, amount, paid status), and outstanding balance
- THEN the section links to the full billing view at
/operator/billing/accounts
Scenario: Composite view exposes a member roster
- WHEN an operator views
/operator/organizations/{orgID}for an organization with enrolled members - THEN the page renders a members section listing each enrolled person sorted alphabetically by display name
- THEN each member row links to
/operator/persons/{personID}for that person
Requirement: Per-organization composite view is the sole UI entry point for grant actions
The operator panel SHALL expose grant action affordances (issue, extend, revoke) only from the per-organization composite view. The global Grants surface in the Runtime group SHALL be read-only — it presents grants for browse/audit/lookup but exposes no action affordances. Underlying HTTP handlers (e.g. the simple RevokeGrant, the legacy CreateGrant issuance path) MAY remain reachable by direct URL for programmatic callers but SHALL NOT be surfaced as operator UI affordances.
Grant issuance SHALL be exposed as a single product-based form on the per-organization composite view. The form's product picker SHALL list published products available to the organization, including internal wrap products, which SHALL be visually marked as internal in the picker; the picker SHALL NOT be partitioned into intent-labeled variants by catalog shape (no separate "plan" picker and "non-plan" picker). Whether the resulting conferral is position-bearing (occupies a ladder rung) SHALL be derived by the entitlement-conferral capability's confer primitive from the product's catalog shape (billing.product_conferral_shapes) — the operator SHALL NOT select or declare this behavior on the form. The form SHALL carry uniform bounds — an optional quantity and an optional valid_until — available for any product regardless of catalog shape, and a grant_reason select restricted to the fixed domain excluding 'default'. The full semantics of grant issuance (conferral derivation, the uniform-bounds contract, the grant_reason domain, and expiration scheduling) are specified by the plan-enrollment-administration capability's "Operator grant issuance" requirement; this requirement governs only the composite view's role as the sole UI entry point and the single-form shape of that entry point, not conferral behavior itself.
Grant revocation SHALL be enacted by the decree-first, source-resolved revoke behavior owned by the plan-enrollment-administration capability's "Operator grant revocation" requirement: the entry point sets the grant's revoked_* ledger fields and then calls entitlements.end_conferral resolved by the grant's own source, ending only the provision that grant itself delivered. Ending a provision SHALL NOT re-mint the org-type default as a side effect — re-application of the org default, when desired, is a separate ReapplyDefaultsForPool call (owned by auto-provisioning, per plan-transitions). This requirement governs only the composite view's role as the sole UI entry point for revocation, not the revoke semantics themselves; no Revoke affordance SHALL be surfaced outside the per-organization composite view.
Scenario: Per-org view exposes revoke using the source-resolved behavior
- WHEN an operator views
/operator/organizations/{orgID} - THEN each active grant for the organization has a Revoke affordance
- THEN invoking Revoke sets the grant's
revoked_*fields and callsend_conferralresolved by the grant's source, ending only that grant's own provision (end_conferralitself never re-applies the org default; the entry point then performs the guarded baseline restoration owned byplan-enrollment-administration, which runs only when the default ladder is left vacant)
Scenario: Per-org view exposes a single issuance form for any published product
- WHEN an operator views
/operator/organizations/{orgID} - THEN the page exposes exactly one grant issuance form
- THEN the form's product picker lists published products available to the organization, with internal wrap products marked as internal
- THEN no separate plan-typed and non-plan-typed issuance forms exist
- THEN the form exposes uniform bounds (
quantity,valid_until) and agrant_reasonselect over the fixed domain excluding'default', regardless of which product is picked
Scenario: Per-org view exposes extension
- WHEN an operator views
/operator/organizations/{orgID} - THEN each active grant has an Extend affordance that issues an extend-as-replace with new bounds (a new lineaged grant consumed by
confer, per theplan-enrollment-administrationcapability), never mutating the incumbent grant in place
Scenario: Global Grants surface exposes no action affordances
- WHEN an operator views the global Grants surface in the Runtime group
- THEN the grant rows render in a read-only listing
- THEN no Issue, Extend, or Revoke affordance appears on the surface
Requirement: Every operator page declares its IA position for breadcrumb derivation
Every operator panel page SHALL declare its position in the IA as a positional tuple of the form (group, capability) or (group, capability, instance), where group is one of catalog, runtime, or integration, capability is the capability slug (e.g. products, organizations), and instance is present when the page is scoped to one entity (e.g. an organization's composite view). The visual rendering of breadcrumbs is out of scope for this specification.
Scenario: Capability index page declares its IA position
- WHEN an operator loads
/operator/products - THEN the page declares its IA position as
(catalog, products)
Scenario: Instance page declares its IA position
- WHEN an operator loads
/operator/organizations/{orgID} - THEN the page declares its IA position as
(runtime, organizations, {orgID})
Requirement: Browse routes reserve query-parameter real estate for filters
Browse routes under the operator panel (e.g. /operator/organizations, /operator/products, /operator/plan-ladders) SHALL reserve the query parameters ?q=<search> and ?<facet>=<value> (where <facet> is a capability-appropriate field such as org_type, lifecycle_status) as the canonical shape for future filter additions. Browse routes SHALL NOT use these parameter names for unrelated purposes. Implementing visible filter controls is out of scope for this specification; the reservation establishes URL contract only.
Scenario: Reserved parameter names are not reused
- WHEN a new feature is added to a browse route
- THEN the feature SHALL NOT use
q,org_type,lifecycle_status, or other established facet parameter names for any purpose other than search and filtering
Scenario: Unknown filter params do not error
- WHEN an operator loads a browse route with a reserved-but-unimplemented filter param (e.g.
/operator/organizations?q=acme) - THEN the route renders without error
- THEN the unimplemented filter is ignored rather than rejected
Requirement: Operator panel uses MPA routing with hx-boost for in-panel navigation
The operator panel SHALL serve each top-level capability at its own route under /operator/<capability> and return full HTML responses (not HTMX fragments) from those routes. In-panel navigation SHALL use hx-boost applied at the layout shell so navigations produce real browser history entries and bookmarkable URLs while avoiding full-document reparse.
The set of top-level capability routes SHALL include:
/operator(curated landing surface — see the landing-surface requirement)/operator/persons(runtime browse directory — seeoperator-people-directory) and/operator/persons/{personID}(per-person detail — see the person-detail requirement)/operator/organizations(runtime browse) and/operator/organizations/{orgID}(per-org composite — see the composite-view requirement)/operator/grants(runtime browse, read-only — see the grant-action-consolidation requirement)/operator/billing/{accounts,subscriptions,invoices,payments}(runtime read-views) and/operator/billing/invoices/{invoiceID}(per-invoice detail — seeoperator-billing-views)/operator/org-types,/operator/products,/operator/entitlement-sets,/operator/plan-ladders(catalog)/operator/products/{productID}(catalog, per-product composite — see the addressable-composite requirement)/operator/plan-ladders/{ladderID}(catalog, per-ladder composite — see the addressable-composite requirement)/operator/entitlement-sets/{setID}(catalog, per-entitlement-set composite — see the addressable-composite requirement)/operator/integrations/fedwiki(integration; the legacy/operator/fedwiki-sitespath 301-redirects here)
Scenario: Top-level capability route is bookmarkable
- WHEN an operator loads any top-level capability route (e.g.
/operator/products) directly via URL - THEN the page renders the full capability surface
- THEN no prior navigation through
/operatoris required
Scenario: Unknown operator paths stay in the operator shell
- WHEN an operator loads a
/operator/*path no page claims (e.g. a truncated or mistyped URL) - THEN the response SHALL be HTTP 404 rendered inside the operator shell with a link back to the operator overview
- AND it SHALL NOT fall through to the member-facing dashboard
Scenario: In-panel navigation uses hx-boost
- WHEN an operator clicks any in-panel navigation link
- THEN the navigation is intercepted by
hx-boostand the response replaces the layout's main content region - THEN the browser's URL bar and history reflect the destination route
- THEN a manual page reload at that URL renders the same page
Scenario: Toast container and modal survive boosted navigation
- WHEN an operator triggers a
HX-Trigger: showSuccessToastresponse on a POST that redirects to a different capability - THEN the success toast renders after the boosted navigation completes
- THEN the toast container and the confirm-action modal are outside the boost swap target so they are not destroyed by the swap
Requirement: Legacy ?tab= URLs redirect for one release
The operator panel SHALL handle legacy /operator?tab=<tab-id> URLs by issuing a 301 redirect to the equivalent top-level capability route for the duration of one release. After that release the shim SHALL be removed and /operator?tab=<anything> SHALL render the landing surface (the unknown filter param is ignored per the reserved-filter-params requirement).
Scenario: Known ?tab= redirects to capability route
- WHEN an operator loads
/operator?tab=organizations - THEN the panel issues a 301 redirect to
/operator/organizations
Scenario: ?tab=people redirects to the landing surface
- WHEN an operator loads
/operator?tab=people - THEN the panel issues a 301 redirect to
/operator(since persons is lookup-only — no top-level browse exists)
Scenario: Unknown ?tab= renders landing
- WHEN an operator loads
/operator?tab=<unknown-id>after the shim is removed - THEN the landing surface renders without error
- THEN the
tabparameter is ignored per the reserved-filter-params requirement
Requirement: Operator panel exposes a plan-topology overview under the Catalog group
The plan-topology overview is no longer a separate destination: the Plan Ladders surface at /operator/plan-ladders SHALL open with the topology overview (the cross-ladder grid and structural-health strip, per the operator-topology-overview capability, whose requirements hold unchanged at this home) as its map-first view, with ladder management beneath it. The overview's IA position is the Catalog conceptual group via the Plan Ladders page's declaration. /operator/plan-topology SHALL NOT be a route: with no production deployments there is no old URL to stay compatible with, so no redirect exists and the path receives the operator panel's ordinary 404 handling.
Scenario: Ladders surface opens map-first
- WHEN an authenticated operator navigates to
/operator/plan-ladders - THEN the topology grid and health strip render as the surface's opening view
- AND ladder management (list, create) renders on the same surface beneath or behind the map
Scenario: The old topology URL is gone, not redirected
- WHEN an operator requests
/operator/plan-topology - THEN the panel answers with its ordinary 404 handling, not a redirect
Requirement: Provider admin surfaces resolve from the registry, not a hardcoded map
The operator panel SHALL resolve a provider resource's admin surface from the provider registry rather than from a hardcoded resource-key→surface map. Adding or removing a provider SHALL NOT require editing a navigation map in code. Provider surfaces are presented within the dedicated Integrations section (see "Operator panel exposes a dedicated Integrations section"); this requirement governs only how each surface link is resolved (from the registry), not where the section lives.
Scenario: Provider surface is registry-resolved
- WHEN the operator panel renders the admin-surface link for a provisioning provider
- THEN the link URL SHALL be obtained from the provider registry
- AND SHALL NOT be obtained from a hardcoded resource-key→surface map
Scenario: Adding a provider requires no navigation-map edit
- WHEN a new provisioning provider is registered
- THEN its admin surface SHALL become reachable without editing any hardcoded resource-key→surface map in code
Scenario: FedWiki surface resolves under its provider identity
- WHEN an operator opens FedWiki's admin surface
- THEN it SHALL be resolved via the registry for provider
fedwiki(resourcefedwiki_sites) - AND SHALL NOT be resolved via a literal
"sites"map entry
Requirement: Operator panel exposes a dedicated Integrations section
The operator panel SHALL present integrations as a dedicated, registry-driven section, distinct from the catalog and runtime groups. The sidebar SHALL render a single "Integrations" entry that links to a section landing at /operator/integrations; provider surfaces are reached from that landing's rows, not from sidebar children (maintainer decision 2026-08-23), and integration admin pages and settings pages SHALL mark the "Integrations" entry active. The landing SHALL be registry-driven and SHALL list every registered provider of every kind in one table — a payments provider appears alongside provisioning ones, never as a half-present entry — showing each provider's display name, status, kind, declared lifecycle operations, owned resource keys, a Manage link to its admin surface (when the provider declares one), and a Settings link to /operator/integrations/{slug}/settings (when the integration declares configuration — sourced from the adapter registry). Integration admin pages SHALL live under the /operator/integrations/ URL namespace, SHALL open with a heading identifying the integration, and SHALL cross-link to their settings page; settings pages SHALL cross-link back to the admin page. Adding or removing a provider, or an adapter with declared configuration, SHALL change the landing without any template or handler edit. Per-key configuration writes live on the linked settings pages, not on the landing; the landing's only write affordance is the unrecognized-override cleanup (see integration-settings), which SHALL be gated behind the shared confirm-action modal.
Scenario: Sidebar shows the Integrations section home
- WHEN an operator views any operator page
- THEN the sidebar SHALL include a single "Integrations" entry linking to
/operator/integrations - AND no per-provider sidebar entries render
- AND an integration admin page or settings page marks the "Integrations" entry active
Scenario: Integrations landing lists registered providers from the registry
- WHEN an operator opens
/operator/integrations - THEN the page SHALL list each registered provider of every kind with its display name, status, kind, declared lifecycle operations, and owned resource keys
- AND for FedWiki the row SHALL link to
/operator/integrations/fedwiki - AND a payments provider (e.g. Stripe) SHALL have a row even though it declares no admin surface
- AND the listing SHALL be derived from the provider registry, not a hardcoded list
Scenario: Adding a provider populates the section without code edits
- WHEN a second conforming provisioning provider is registered
- THEN it SHALL appear on the
/operator/integrationslanding with its Manage or Settings link - AND no operator template or page handler SHALL require editing for it to appear
Scenario: Empty state when no provisioning providers are registered
- WHEN
/operator/integrationsis opened and no provisioning provider is registered - THEN the page SHALL render an empty state rather than an error
Scenario: Settings links cover every configuring adapter
- WHEN an operator opens
/operator/integrations - THEN every registered integration adapter that declares configuration SHALL have a Settings link to
/operator/integrations/{slug}/settingson its row - AND an adapter that is not a provisioning provider (e.g. a payments integration) SHALL still get its link
- AND the links SHALL be derived from the adapter registry, not a hardcoded list
Requirement: Per-product, per-plan-ladder, and per-entitlement-set composite views are addressable
The operator panel SHALL provide a per-product composite view at /operator/products/{productID}, a per-plan-ladder composite view at /operator/plan-ladders/{ladderID}, and a per-entitlement-set composite view at /operator/entitlement-sets/{setID}. Each route SHALL render a full operator page (the operator shell), SHALL be bookmarkable and reload-stable, and SHALL be reachable from its capability's browse list via boosted in-panel navigation (a real <a href> under the shell's hx-boost), not via a fragment swap that leaves the URL unchanged. Each instance page SHALL declare its IA position as the (group, capability, instance) tuple — (catalog, products, {productID}), (catalog, plan-ladders, {ladderID}), and (catalog, entitlement-sets, {setID}).
The product composite SHALL co-locate, on the one URL, the product edit form, the product's prices view, and the purchasability/readiness panel including the Sync-to-Stripe action. The plan-ladder composite SHALL co-locate, on the one URL, the ladder edit form, the ladder's tier/rank manager, and the structural-invariant validation surface for that ladder. The entitlement-set composite SHALL co-locate, on the one URL, the entitlement-set edit form and the rules manager (rule list, add-rule form, and per-rule delete).
Scenario: Product composite is bookmarkable
- WHEN an operator loads
/operator/products/{productID}directly via URL - THEN the page renders the composite view for that product without requiring prior navigation through the panel
Scenario: Product composite is reachable from the browse list via boosted navigation
- WHEN an operator activates a product's link on the
/operator/productsbrowse list - THEN the navigation targets
/operator/products/{productID}as a real route underhx-boost - THEN the browser URL and history reflect
/operator/products/{productID} - THEN a manual reload at that URL renders the same composite page
Scenario: Product composite co-locates editing, prices, and readiness
- WHEN an operator views
/operator/products/{productID} - THEN the page surfaces the product edit form, the product's prices view, and the purchasability/readiness panel (including the Sync-to-Stripe action when applicable) on the same page
Scenario: Plan-ladder composite is bookmarkable
- WHEN an operator loads
/operator/plan-ladders/{ladderID}directly via URL - THEN the page renders the composite view for that ladder without requiring prior navigation through the panel
Scenario: Plan-ladder composite is reachable from the browse list via boosted navigation
- WHEN an operator activates a ladder's link on the
/operator/plan-laddersbrowse list - THEN the navigation targets
/operator/plan-ladders/{ladderID}as a real route underhx-boost - THEN the browser URL and history reflect
/operator/plan-ladders/{ladderID}
Scenario: Plan-ladder composite co-locates editing, tiers, and validation
- WHEN an operator views
/operator/plan-ladders/{ladderID} - THEN the page surfaces the ladder edit form, the tier/rank manager, and the structural-invariant validation surface for that ladder on the same page
Scenario: Entitlement-set composite is bookmarkable and co-locates editing and rules
- WHEN an operator loads
/operator/entitlement-sets/{setID}directly via URL - THEN the page renders the composite view for that entitlement set without requiring prior navigation through the panel
- THEN the page surfaces the entitlement-set edit form and the rules manager (rule list, add-rule form, and per-rule delete) on the same page
Scenario: Entitlement-set composite is reachable from the browse list via boosted navigation
- WHEN an operator activates an entitlement set's link on the
/operator/entitlement-setsbrowse list - THEN the navigation targets
/operator/entitlement-sets/{setID}as a real route underhx-boost - THEN the browser URL and history reflect
/operator/entitlement-sets/{setID}
Scenario: Instance pages declare their IA position
- WHEN an operator loads
/operator/products/{productID} - THEN the page declares its IA position as
(catalog, products, {productID}) - WHEN an operator loads
/operator/plan-ladders/{ladderID} - THEN the page declares its IA position as
(catalog, plan-ladders, {ladderID}) - WHEN an operator loads
/operator/entitlement-sets/{setID} - THEN the page declares its IA position as
(catalog, entitlement-sets, {setID})
Requirement: Landing surface foregrounds setup progress while setup is incomplete
While any required step of the operator setup checklist (see operator-setup-checklist) derives incomplete, the curated landing surface at /operator SHALL render a setup-progress region ahead of the metric tiles, summarizing which steps are complete and linking each incomplete step to the surface that completes it (or linking the checklist surface as a whole). Once every required step derives complete, the region SHALL NOT render, even when conditional steps remain. The region derives from the same predicates as the checklist, so the landing surface and the checklist never disagree.
Scenario: Fresh instance leads with setup, not zeros
- WHEN an operator visits
/operatoron a deployment where no checklist step is complete - THEN the setup-progress region renders ahead of the metric tiles
- AND it links the first incomplete steps to their completing surfaces
Scenario: Configured instance shows no setup region
- WHEN an operator visits
/operatoron a deployment where every required checklist step derives complete - THEN the landing surface renders without the setup-progress region
Requirement: Landing surface orders attention before totals and links its content
The curated landing surface SHALL order its regions with the operator's daily reads first: the setup-progress region (while required setup is incomplete, per operator-setup-checklist), then the at-a-glance metric tiles, then the system-status region (delivery queue and integration state), then recent activity — the counts an operator checks every day outrank the system cards (maintainer decision 2026-08-23), and attention-shaped content is still never the page's last region. Activity-feed entries SHALL link to the subject's surface wherever an addressable one exists (the organization, invoice, or grant the entry describes), so no feed entry is a dead end when a destination exists.
Scenario: Metric tiles render ahead of the system cards
- WHEN an operator views
/operator - THEN the at-a-glance tiles render before the system-status region
- AND the system-status region renders before recent activity, so attention-shaped content (queue state, unconfigured integrations) is not the last region on the page
Scenario: Feed entries link to their subjects
- WHEN the recent-activity feed shows an entry about an entity with an addressable operator surface
- THEN the entry links to that surface
Requirement: Person detail is addressable and cross-linked
The person-detail view at /operator/persons/{personID} SHALL be bookmarkable (no prior navigation required) and reachable from the People directory's rows, the landing-surface lookup affordance, and organization member tables. It SHALL include an organization-memberships section listing each organization the person belongs to, each row linking to that organization's composite view at /operator/organizations/{orgID}.
Scenario: Person detail is bookmarkable
- WHEN an operator loads
/operator/persons/{personID}directly via URL - THEN the page renders the resolved-detail view for that person
Scenario: Person detail is reachable from organization context
- WHEN an operator views an organization's member listing on
/operator/organizations/{orgID} - THEN each member row links to
/operator/persons/{personID}for that member
Scenario: Person detail exposes organization memberships
- WHEN an operator views
/operator/persons/{personID}for a person who belongs to one or more organizations - THEN the page renders an organization-memberships section listing each organization the person belongs to
- THEN each row links to
/operator/organizations/{orgID}for that organization