Menu has a clear owner, generated endpoints, permission catalog, dual-ORM store, and cross-instance invalidation skeleton. Tree integrity, change atomicity, audit, and recovery are not yet commercial-GA grade.
1. Security model
Management endpoints authenticate and require create/update/delete/view. Navigation authenticates and filters by role-module visibility, without a Menu management permission.
2. Global blast radius
SysModule is not tenant-scoped. Every management write changes the platform-wide catalog and removes the global menu cache tag. Role grants are tenant-owned, but structure, labels, links, and enabled flags are shared.
Menu management must therefore be treated as a high-risk platform permission, not ordinary tenant configuration.
3. Display-filter boundary
// ① Navigation decides only whether to show an entry to this user.var navigation = await menus.GetNavigationAsync(currentUser, cancellationToken);
// ② The target command declares its own resource and action.public sealed record ApproveInvoice(string InvoiceId) : ICommand<Result>, IAuthorizedRequest{ public ResourceDescriptor Resource { get; } = new("billing", "invoice"); public AuthorizationAction Action { get; } = AuthorizationAction.Approve;}
// ③ A manually constructed URL still reaches the authorization pipeline.Never treat LinkUrl as trusted policy input or node presence as backend authorization.
4. Admin-shortcut risk
Any case-insensitive role name admin receives all enabled catalog rows. This is an application convention, not an explicit permission grant. IdP mapping or tenant-custom role names could trigger it unexpectedly.
GA should choose an unforgeable platform claim, explicit super-admin capability, or a normal Authorization decision, then fix casing, tenant, and audit semantics in tests.
5. Input and link safety
Current writes do not validate LinkUrl scheme, host, relative form, open redirects, or values such as javascript:. Icon is also unrestricted. A client must encode values and use an allowlist; the safer server rule is an internal relative route.
Render Name and Description as text, never untrusted HTML.
6. Existing automated evidence
| Test | What it currently proves |
|---|---|
MenuQueryHandlerTests | Detail success and not-found |
MenuInfrastructureArchitectureTests | ORM neutrality, Store-only queries, fail-closed adapter |
AuthorizationMenuPersistenceOwnershipArchitectureTests | ownership, narrow ports, generated model/seed manifests, migration shape |
IdentitySeedDataTests | menu/permission seed references and asset rules |
FeatureDefinitionSeedStepTests | feature assets resolve referenced modules |
PortRepositoryParityTests | equal Store behavior on SqlSugar and EF Core |
ApiShellHostBuildSmokeTests | unavailable Store without a database |
This evidence does not cover tree algorithms or write business rules.
7. Required test pyramid
Fix deterministic algorithms with fast tests, then prove infrastructure behavior with real databases and two API instances.
8. Operational diagnosis
When a menu is missing, inspect in order:
- authentication success;
- expected role names in
ICurrentUser.User.Roles; - module Codes returned by Authorization;
- exact SysModule Code match;
- Enabled/non-deleted target and ancestors;
- root reachability of every ancestor;
- IsMenu=true along the navigation path;
- stale cache on the current instance.
9. Cache diagnosis
# Find sync handling and invalidation failures.rg -n "Menu cache invalidated|Failed to invalidate menu cache" ./logs
# Confirm tag, TTL, and event resource in source.rg -n "MenuCacheTags|CachePolicy.Medium|LocalResourceTypes.Cache" src/Platform/Menu -g '*.cs'
# Database diagnosis must inspect Code, ParentId, Enabled, IsMenu, and IsDeleted together.The consumer suppresses errors, so message consumption alone does not prove cache removal.
10. Integrity scan
A production scanner should report blank/duplicate Code, blank Name, orphan ParentId, self-parenting, cycles, excessive depth, unreachable enabled nodes, menu children under non-menu parents, duplicate sibling OrderSort, unsafe LinkUrl, and grants referencing absent Codes.
It should report and produce a reviewed repair plan, not silently rewrite the global catalog.
11. Release checklist
- Back up SysModule and role-module-permission relations.
- Compare seed assets with database state.
- Verify 401/403/success for all nine endpoints.
- Run parity on every supported ORM.
- Verify broadcast invalidation with two API instances.
- Simulate notifier and cache failures.
- Exercise deep, cyclic, and orphan data.
- Restrict links to allowed internal routes.
- Prepare Code migration and rollback scripts.
- Smoke-test normal, role-less, and super-admin users after release.
12. Commercial GA gaps
In priority order:
- transactional tree validators and defensive cycle/depth reads;
- atomic reparent, cascade delete, and deterministic ordering;
- immutable Code or an explicit migration workflow;
- full URL/Name/Icon/Scope validation;
- database-to-cache outbox and retry;
- ResourceId filtering and failed-event delivery;
- optimistic concurrency and affected-row checks;
- platform management audit, approval, and rollback;
- a product decision for tenant overrides/localization;
- consumer contracts, dual-instance tests, and fault injection gates.
The Architecture Hub Menu GA backlog contains concrete work items. This page describes current implementation and acceptance direction only.
13. Deployment judgment
The module can be used in a controlled internal setting with trusted platform administrators, controlled seeds, shallow acyclic data, and a tolerated 15-minute cache fallback. Do not claim GA for tenant self-service, strict audit, zero staleness, or complex visual tree editing yet.
14. Global sweeps
# Current automated evidence.rg -n "Menu" tests -g '*.cs'
# Cycle, transaction, outbox, and audit gaps; expect no substantive Menu matches.rg -n "visited|MaxDepth|UnitOfWork|Outbox|Audit" src/Platform/Menu -g '*.cs'
# Every generated endpoint and resource-authorized request.rg -n "GenerateEndpoint|IAuthorizedRequest" src/Platform/Menu -g '*.cs'