## Summary - amend the operator UI constitution and related SpecKit templates for the new UI/UX governance rules - add Spec 168 artifacts plus the tenant governance aggregate implementation used by the tenant dashboard, banner, and baseline compare landing surfaces - normalize Filament action surfaces around clickable-row inspection, grouped secondary actions, and explicit action-surface declarations across enrolled resources and pages - fix post-suite regressions in membership cache priming, finding workflow state refresh, tenant review derived-state invalidation, and tenant-bound backup-set related navigation ## Commit Series - `docs: amend operator UI constitution` - `spec: add tenant governance aggregate contract` - `feat: add tenant governance aggregate contract` - `refactor: normalize filament action surfaces` - `fix: resolve post-suite state regressions` ## Testing - `vendor/bin/sail artisan test --compact` - Result: `3176 passed, 8 skipped (17384 assertions)` ## Notes - Livewire v4 / Filament v5 stack remains unchanged - no provider registration changes; `bootstrap/providers.php` remains the relevant location - no new global-search resources or asset-registration changes in this branch Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #199
55 KiB
TenantPilot Constitution
Core Principles
Inventory-first, Snapshots-second
- All modules MUST operate primarily on Inventory as “last observed” state.
- Inventory is the source of truth for what TenantPilot last observed; Microsoft Intune remains the external truth.
- Snapshots/Backups MUST be explicit actions (manual or scheduled) and MUST remain immutable.
Read/Write Separation by Default
- Analysis, reporting, and monitoring features MUST be read-only by default.
- Any write/change function (restore, remediation, promotion) MUST include preview/dry-run, explicit confirmation, audit logging, and tests.
- High-risk policy types default to
preview-onlyrestore unless explicitly enabled by a feature spec + tests.
Single Contract Path to Graph
- All Microsoft Graph calls MUST go through
GraphClientInterface. - Object types and endpoints MUST be modeled first in the contract registry (
config/graph_contracts.php). - Feature code MUST NOT hardcode “quick endpoints” or bypass contracts.
- Unknown/missing policy types MUST fail safe (preview-only / no Graph calls) rather than guessing endpoints.
Deterministic Capabilities
- Backup/restore/risk/support flags MUST be derived deterministically from config/contracts via a Capabilities Resolver.
- The resolver output MUST be programmatically testable (snapshot/golden tests) so config changes cannot silently break behavior.
Proportionality First (PROP-001)
- New structure, layering, persistence, or semantic machinery MUST be justified by current release truth, current operator workflow, and a concrete reason a narrower implementation is insufficient.
- Code MUST NOT become more generic, more layered, or more persistent than the current product actually needs.
- Reviews MUST reject speculative generalization framed only as future flexibility.
No Premature Abstraction (ABSTR-001)
- New factories, registries, resolvers, strategy systems, interfaces, extension-point frameworks, type registries, or orchestration pipelines MUST NOT be introduced before at least two real concrete cases require them.
- Test convenience alone is not sufficient justification for a new abstraction.
- Narrow abstractions are allowed when required for security, tenant isolation, auditability, compliance evidence, or queue/job execution correctness.
No New Persisted Truth Without Source-of-Truth Need (PERSIST-001)
- New tables, persisted entities, or stored artifacts MUST represent real product truth that survives independently of the originating request, run, or view.
- Persisted storage is justified only when at least one of these is true: it is a source of truth, has an independent lifecycle, must be audited independently, must outlive its originating run/request, is required for permissions/routing/compliance evidence, or is required for stable operator workflows over time.
- Convenience projections, UI helpers, speculative artifacts, derived summaries, and temporary semantic wrappers MUST remain derived unless current-release operator workflows require independent persistence.
- Release 2/3 entities MUST NOT be fully built in Release 1 unless they are foundational and already exercised by the shipped workflow.
No New State Without Behavioral Consequence (STATE-001)
- New states, statuses, reason codes, lifecycle labels, and semantic categories MUST change operator action, workflow routing, permission or policy enforcement, lifecycle behavior, persistence truth, audit responsibility, retention behavior, or retry/failure handling.
- Presentation-only distinctions MUST remain derived labels rather than persisted domain state.
- Reason code families MUST NOT expand unless each added value has a distinct system or operator consequence.
UI Semantics Must Not Become Their Own Framework (UI-SEM-001)
- Badges, explanation text, trust/confidence labels, detail cards, and status summaries MUST remain lightweight presentation helpers unless they are proven product contracts.
- New UI semantics MUST NOT require mandatory presenter, badge, explanation, taxonomy, or multi-step interpretation pipelines by default.
- Direct mapping from canonical domain truth to UI is preferred over intermediate semantic superstructures.
- Presentation helpers SHOULD remain optional adapters, not mandatory architecture.
V1 Prefers Explicit Narrow Implementations (V1-EXP-001)
- For V1 and early product maturity, direct implementation, local mapping, explicit per-domain logic, small focused helpers, derived read models, and minimal UI adapters are preferred.
- Generic platform engines, meta-frameworks, universal resolver systems, workflow frameworks, and broad semantic taxonomies are disfavored until real variance proves them necessary.
- The burden of proof is always on the broader abstraction.
One Truth, Few Layers (LAYER-001)
- A single domain truth MUST NOT be redundantly modeled across model fields, service result objects, presenters, UI summaries, explanation builders, badge taxonomies, run context wrappers, and persisted mirror entities without clear necessity.
- Prefer one canonical truth with thin adapters.
- Any new layer MUST replace an existing layer or prove why the existing layer cannot serve the need.
- Additive semantic layering is discouraged; absorption is preferred over accumulation.
Spec Discipline Over Slice Proliferation (SPEC-DISC-001)
- Related semantic, taxonomy, and presentation-contract changes SHOULD be grouped into one coherent spec instead of many micro-specs that each add classes, enums, DTOs, and tests.
- Every spec MUST explicitly state whether it introduces a new source of truth, persisted entity, abstraction, state, or cross-cutting framework.
- If the answer is yes, the spec MUST explain why the addition is necessary now.
Tests Must Protect Business Truth (TEST-TRUTH-001)
- Testing is mandatory, but test growth MUST follow business truth rather than indirection created for its own sake.
- Tests MUST prioritize domain behavior, permissions, isolation, lifecycle correctness, and operator-critical outcomes.
- Large dedicated test surfaces for thin presentation indirection SHOULD be avoided.
- If a pattern creates more test burden than product certainty, the pattern SHOULD be simplified.
Enterprise Complexity Is Allowed Only Where Risk Demands It (RISK-COMP-001)
- Heavier architecture is explicitly legitimate for workspace or tenant isolation, RBAC and policy enforcement, auditability, immutable history and snapshot truth, queue/job execution legitimacy, provider credential safety, retention/compliance evidence, and operator-critical lifecycle correctness.
- Badge systems, explanation builders, trust/confidence overlays, presentation taxonomies, generic provider frameworks without real provider variance, speculative export/report/review infrastructure, UI meta-governance frameworks, and derived helper entities promoted into persisted truth are high-risk overproduction zones and require extra restraint.
Mandatory Bloat Check for New Specs (BLOAT-001)
- Any spec that introduces a new enum or status family, DTO/envelope/presenter layer, persisted entity or table, interface/contract/registry/resolver, cross-domain UI framework, or taxonomy/classification system MUST include a proportionality review.
- That review MUST answer:
- What current operator problem does this solve?
- Why is existing structure insufficient?
- Why is this the narrowest correct implementation?
- What ownership cost does this create?
- What alternative was intentionally rejected?
- Is this current-release truth or future-release preparation?
- Specs that cannot answer these questions clearly MUST NOT merge.
Default Bias (BIAS-001)
- Default codebase bias is: derive before persist, map before frameworkize, localize before generalize, simplify before extend, replace before layer, explicit before generic, and present directly before interpreting recursively.
Workspace Isolation is Non-negotiable
- Workspace membership is an isolation boundary. If the actor is not entitled to the workspace scope, the system MUST respond as deny-as-not-found (404).
- Workspace is the primary session context. Tenant-scoped routes/resources MUST require an established workspace context.
- Workspace context switching is separate from Filament Tenancy (Managed Tenant switching).
Tenant Isolation is Non-negotiable
- Every tenant-plane read/write MUST be tenant-scoped.
- Cross-tenant views (MSP/Platform) MUST be explicit, access-checked, and aggregation-based (no ID-based shortcuts).
- Tenantless canonical views (e.g., Monitoring/Operations) MUST enforce tenant entitlement before revealing records.
- Prefer least-privilege roles/scopes; surface warnings when higher privileges are selected.
- Tenant membership is an isolation boundary. If the actor is not entitled to the tenant scope, the system MUST respond as deny-as-not-found (404).
Scope & Ownership Clarification (SCOPE-001)
- The system MUST enforce a strict ownership model:
- Workspace-owned objects define standards, templates, and global configuration (e.g., Baseline Profiles, Notification Targets, Alert Routing Rules, Framework/Control catalogs).
- Tenant-owned objects represent observed state, evidence, and operational artifacts for a specific tenant (e.g., Inventory, Backups/Snapshots, OperationRuns for tenant operations, Drift/Findings, Exceptions/Risk Acceptance, EvidenceItems, StoredReports/Exports).
- Workspace-owned objects MUST NOT directly embed or persist tenant-owned records (no “copying tenant data into templates”).
- Tenant-owned objects MUST always be bound to an established workspace + tenant scope at authorization time.
Database convention:
- Tenant-owned tables MUST include workspace_id and tenant_id as NOT NULL.
- Workspace-owned tables MUST include workspace_id and MUST NOT include tenant_id.
- Exception: OperationRun MAY have tenant_id nullable to support canonical workspace-context monitoring views; however, revealing any tenant-bound runs still MUST enforce entitlement checks to the referenced tenant scope.
- Exception: AlertDelivery MAY have tenant_id nullable for workspace-scoped, non-tenant-operational artifacts (e.g.,
event_type=alerts.test). Tenant-bound delivery records still MUST enforce tenant entitlement checks, and tenantless delivery rows MUST NOT contain tenant-specific data. - Exception:
managed_tenant_onboarding_sessionsMAY keeptenant_idnullable as a workspace-scoped workflow-coordination record. It begins before tenant identification, may later reference a tenant for authorization continuity and resume semantics, and MUST always enforce workspace entitlement plus tenant entitlement once a tenant reference is attached. This exception is specific to onboarding draft workflow state and does not create a general precedent for workspace-owned domain records.
RBAC & UI Enforcement Standards (RBAC-UX)
RBAC Context — Planes, Roles, and Auditability
- The platform MUST maintain two strictly separated authorization planes:
- Tenant/Admin plane (
/admin): authenticated Entra users (users). - Tenant-context routes (
/admin/t/{tenant}/...) are tenant-scoped. - Workspace-context canonical routes (
/admin/..., e.g. Monitoring/Operations) are tenantless by URL but MUST still enforce workspace + tenant entitlement before revealing tenant-owned records. - Platform plane (
/system): authenticated platform users (platform_users), authorization is platform-scoped.
- Tenant/Admin plane (
- Cross-plane access MUST be deny-as-not-found (404) (not 403) to avoid route enumeration.
- Tenant role semantics MUST remain least-privilege:
- Readonly: view-only; MUST NOT start operations and MUST NOT mutate data.
- Operator: MAY start allowed tenant operations; MUST NOT manage credentials, settings, members, or perform destructive actions.
- Manager: MAY manage tenant configuration and start operations; MUST NOT manage tenant memberships (Owner-only).
- Owner: MAY manage memberships and all tenant configuration; Owner-only “danger zone” actions MUST remain Owner-only.
- The system MUST prevent removing or demoting the last remaining Owner of a tenant.
- All access-control relevant changes MUST write
AuditLogentries with stable action IDs, and MUST be redacted (no secrets).
RBAC-UX-001 — Server-side is the source of truth
- UI visibility / disabled state is never a security boundary.
- Every mutating action (create/update/delete/restore/archive/force-delete), every operation start, and every credential/
config change MUST enforce authorization server-side via
Gate::authorize(...)or a Policy method. - Any missing server-side authorization is a P0 security bug.
RBAC-UX-002 — Deny-as-not-found for non-members
- Tenant and workspace membership (and plane membership) are isolation boundaries.
- If the current actor is not a member of the current workspace OR the current tenant (or otherwise not entitled to the workspace/tenant scope), the system MUST respond as 404 (deny-as-not-found) for tenant-scoped routes/actions/resources.
- This applies to Filament resources/pages under tenant routing (
/admin/t/{tenant}/...), Global Search results, and all action endpoints (Livewire calls included).
RBAC-UX-003 — Capability denial is 403 (after membership is established)
- Within an established workspace + tenant scope, missing permissions are authorization failures.
- If the actor is a workspace + tenant member, but lacks the required capability for an action, the server MUST fail with 403.
- The UI may render disabled actions, but the server MUST still enforce 403 on execution.
RBAC-UX-004 — Visible vs disabled UX rule
- For tenant members: actions SHOULD be visible but disabled when capability is missing.
- Disabled actions MUST provide helper text explaining the missing permission.
- For non-members: actions MUST behave as not found (404) and SHOULD NOT leak resource existence.
- Exception: highly sensitive controls (e.g., credential rotation) MAY be hidden even for members without permission.
RBAC-UX-005 — Destructive confirmation standard
- All destructive-like actions MUST require confirmation.
- Delete/force-delete/archive/restore/remove membership/role downgrade/credential rotation/break-glass enter/exit MUST use
->requiresConfirmation()and SHOULD include clear warning text. - Confirmation is UX only; authorization still MUST be server-side.
RBAC-UX-006 — Capability registry is canonical
- Capabilities MUST be centrally defined in a single canonical registry (constants/enum).
- Feature code MUST reference capabilities only via the registry (no raw string literals).
- Role → capability mapping MUST reference only registry entries.
- CI MUST fail if unknown/unregistered capabilities are used.
RBAC-UX-007 — Global search must be tenant-safe
- Global search MUST be context-safe (workspace-context vs tenant-context).
- Non-members MUST never learn about resources in other tenants (no results, no hints).
- If a result exists but is not accessible, it MUST be treated as not found (404 semantics).
- In workspace-context (no active tenant selected), Global Search MUST NOT return tenant-owned results.
- It MAY search workspace-owned objects only (e.g., Tenants list entries, Baseline Profiles, Alert Rules/Targets, workspace settings).
- If tenant-context is active, Global Search MUST be scoped to the current tenant only (existing rule remains).
RBAC-UX-008 — Regression guards are mandatory
- The repo MUST include RBAC regression tests asserting at least:
- Readonly cannot mutate or start operations.
- Operator can run allowed operations but cannot manage configuration.
- Manager/Owner behave according to the role matrix.
- The repo SHOULD include an automated “no ad-hoc authorization” guard that blocks new status/permission mappings sprinkled
across
app/Filament/**, pushing patterns into central helpers.
Operations / Run Observability Standard
- Every long-running or operationally relevant action MUST be observable, deduplicated, and auditable via Monitoring → Operations.
- An action MUST create/reuse a canonical
OperationRunand execute asynchronously when any of the following applies:- It can take > 2 seconds under normal conditions.
- It performs remote/external calls (e.g., Microsoft Graph).
- It is queued or scheduled.
- It is operationally relevant for troubleshooting/audit (“what ran, who started it, did it succeed, what failed?”).
- Actions that are DB-only and typically complete in < 2 seconds MAY skip
OperationRun. - OPS-EX-AUTH-001 — Auth Handshake Exception:
- OIDC/SAML login handshakes MAY perform synchronous outbound HTTP (e.g., token exchange) without an
OperationRun. - Rationale: interactive, session-critical, and not a tenant-operational “background job”.
- Guardrail: outbound HTTP for auth handshakes is allowed only on
/auth/*endpoints and MUST NOT occur on Monitoring/Operations pages.
- OIDC/SAML login handshakes MAY perform synchronous outbound HTTP (e.g., token exchange) without an
- If an action is security-relevant or affects operational behavior (e.g., “Ignore policy”), it MUST write an
AuditLogentry including actor, tenant, action, target, before/after, and timestamp. - The
OperationRunrecord is the canonical source of truth for Monitoring (status, timestamps, counts, failures), even if implemented by multiple jobs/steps (“umbrella run”). - “Single-row” runs MUST still use consistent counters (e.g.,
total=1,processed=0|1) and outcome derived from success/failure. - Monitoring pages MUST be DB-only at render time (no external calls).
- Start surfaces MUST NOT perform remote work inline; they only: authorize, create/reuse run (dedupe), enqueue work, confirm + “View run”.
Operations UX — 3-Surface Feedback (OPS-UX-055) (NON-NEGOTIABLE)
If a feature creates/reuses OperationRun, it MUST use exactly three feedback surfaces — no others:
- Toast (intent only / queued-only)
- A toast MAY be shown only when the run is accepted/queued (intent feedback).
- The toast MUST use
OperationUxPresenter::queuedToast($operationType)->send(). - Feature code MUST NOT craft ad-hoc operation toasts.
- A dedicated dedupe message MUST use the presenter (e.g.,
alreadyQueuedToast(...)), notNotification::make().
- Progress (active awareness only)
- Live progress MUST exist only in:
- the global active-ops widget, and
- Monitoring → Operation Run Detail.
- These surfaces MUST show only active runs (
queued|running) and MUST never show terminal runs. - Determinate progress is allowed ONLY when
summary_counts.totalandsummary_counts.processedare valid numeric values. - Determinate progress MUST be clamped to 0–100. Otherwise render indeterminate + elapsed time.
- The widget MUST NOT show percentage text (optional
processed/totalis allowed).
- Terminal DB Notification (audit outcome only)
- Each run MUST emit exactly one persistent terminal DB notification when it becomes terminal.
- Delivery MUST be initiator-only (no tenant-wide fan-out).
- Completion notifications MUST be
OperationRunCompletedonly. - Feature code MUST NOT send custom completion DB notifications for operations (no
sendToDatabase()for completion/abort).
Canonical navigation:
- All “View run” links MUST use the canonical helper and MUST point to Monitoring → Operations → Run Detail.
OperationRun lifecycle is service-owned (OPS-UX-LC-001)
Any change to OperationRun.status or OperationRun.outcome MUST go through OperationRunService (canonical transition method).
This is the only allowed path because it enforces normalization, summary sanitization, idempotency, and terminal notification emission.
Forbidden outside OperationRunService:
$operationRun->update(['status' => ...])/$operationRun->update(['outcome' => ...])$operationRun->status = .../$operationRun->outcome = ...- Query-based updates that transition
status/outcome
Allowed outside the service:
- Updates to
context,message,reason_codethat do not changestatus/outcome.
Summary counts contract (OPS-UX-SUM-001)
operation_runs.summary_countsis the canonical metrics source for Ops-UX.- All keys MUST come from
OperationSummaryKeys::all()(single source of truth). - Values MUST be flat numeric-only; no nested objects/arrays; no free-text.
- Producers MUST NOT introduce new keys without:
- updating
OperationSummaryKeys::all(), - updating the spec canonical list,
- adding/adjusting tests.
- updating
Ops-UX regression guards are mandatory (OPS-UX-GUARD-001)
The repo MUST include automated guards (Pest) that fail CI if:
- any direct
OperationRunstatus/outcome transition occurs outsideOperationRunService, - jobs emit DB notifications for operation completion/abort (
OperationRunCompletedis the single terminal notification), - deprecated legacy operation notification classes are referenced again.
These guards MUST fail with actionable output (file + snippet).
Scheduled/system runs (OPS-UX-SYS-001)
- If a run has no initiator user, no terminal DB notification is emitted (initiator-only policy).
- Outcomes remain auditable via Monitoring → Operations / Run Detail.
- Any tenant-wide alerting MUST go through the Alerts system (not
OperationRunnotifications). - Active-run dedupe MUST be enforced at DB level (partial unique index/constraint for active states).
- Failures MUST be stored as stable reason codes + sanitized messages; never persist secrets/tokens/PII/raw payload dumps in failures or notifications.
- Graph calls are allowed only via explicit user interaction and only when delegated auth is present; never as a render side-effect (restore group mapping is intentionally DB-only).
- Monitoring → Operations is reserved for
OperationRun-tracked operations. - Scheduled/queued operations MUST use locks + idempotency (no duplicates).
- Graph throttling and transient failures MUST be handled with backoff + jitter (e.g., 429/503).
Operator-Facing UI/UX Constitution v1 (UI-CONST-001)
Purpose and scope
- This section governs operator-facing admin UI semantics across TenantPilot / TenantAtlas.
- It defines allowed surface types, allowed interaction models, primary/secondary/destructive action hierarchy, list/detail/queue semantics, scope and context signals, canonical navigation and naming rules, visibility of critical operational truth, scanability and density rules, exception handling, and review and enforcement requirements.
- It does not govern branding, colors, typography, spacing tokens, marketing or landing pages, implementation details without UX effect, purely cosmetic copy changes, or backend architecture except where backend design would create false UI mental models.
- This section is governance, not a style guide. Its purpose is to prevent ambiguity, operator risk, and UI drift before they spread through the product.
Surface Taxonomy (UI-SURF-001)
Every new admin surface MUST be assigned exactly one surface type before implementation. Ad-hoc interaction models are forbidden.
CRUD / List-first Resource
- Purpose: scan, find, open, and selectively mutate many business records.
- Primary behavior: Browse -> Open -> Decide / Mutate.
- Primary model: one-click inspect/open. Full-row click is the default; identifier click is allowed only when full-row click conflicts with another dominant row mechanism.
- Secondary actions: at most one inline non-destructive shortcut; everything else belongs in overflow.
- Destructive actions: never inline beside inspect; only in overflow or the detail header; confirmation is mandatory.
- Explicit View/Inspect: forbidden when row click or identifier click already opens the same destination.
Queue / Review Surface
- Purpose: triage items, inspect them in context, decide, and continue working through the queue.
- Primary behavior: Inspect in context -> Decide -> Continue.
- Primary model: explicit Inspect using a slide-over, inline detail pane, or same-page inspect.
- Secondary actions: only queue-relevant actions belong in the row.
- Destructive actions: inline is allowed only when the destructive decision is part of the real queue work; irreversibility or high risk still requires confirmation.
- Row click: forbidden by default.
- Explicit View/Inspect: required unless the detail is already visible inline.
History / Audit Surface
- Purpose: inspect immutable history, events, and evidence without losing chronology.
- Primary behavior: Inspect event -> Follow trace -> Return to history context.
- Primary model: explicit Inspect, preferably in a slide-over or same-page detail.
- Secondary actions: related navigation only.
- Destructive actions: normally none.
- Row click: forbidden.
- Explicit View/Inspect: required.
Config-lite Resource
- Purpose: manage small, low-cardinality configuration where edit is effectively the detail surface.
- Primary behavior: Open config -> Adjust.
- Primary model: edit-as-inspect.
- Secondary actions: minimal and usually limited to Edit or overflow.
- Destructive actions: overflow or detail header only.
- Row click: allowed when it opens Edit directly and no separate View surface exists.
- Explicit View/Inspect: forbidden.
Read-only Registry / Report Surface
- Purpose: inspect, compare, reference, and export immutable or mostly read-only artifacts.
- Primary behavior: Scan -> Open detail -> Reference / Export.
- Primary model: row click or identifier click to detail.
- Secondary actions: optional single inline non-destructive shortcut when it serves the operator flow.
- Destructive actions: normally none; if they exist they belong in detail only.
- Explicit View/Inspect: forbidden when a functional one-click open already exists.
Detail-first Operational Surface
- Purpose: fully understand one operational record, including state, truth, context, and next steps.
- Primary behavior: Read -> Understand -> Act / Navigate.
- Primary model: dedicated detail page or dedicated operational page.
- Secondary actions: header actions and related-link groups.
- Destructive actions: detail header or grouped header actions only, always with confirmation.
- Row click and explicit View/Inspect: not applicable.
Hard Rules (UI-HARD-001)
Primary inspect model
- Every list surface MUST expose exactly one primary inspect/open model.
- A surface MUST NOT offer row click, identifier click, and explicit View/Inspect for the same destination as parallel primary models.
- CRUD / List-first and Read-only Registry / Report surfaces MUST provide an obvious one-click open path.
- Queue / Review and History / Audit surfaces MUST use explicit Inspect rather than row-click navigation.
Row-click semantics
- Full-row click is the default for CRUD / List-first and Read-only Registry / Report surfaces.
- Identifier-only click is allowed only when full-row click would conflict with another dominant row behavior such as selection-heavy interaction, expand/collapse, drag/sort, or another primary row mechanism.
- When row click is enabled, the row MUST feel consistent. Silent split behavior inside the same row is forbidden.
- Edit-as-inspect is allowed only for Config-lite resources.
View and Inspect actions
- Explicit View MUST NOT exist when the same destination is already opened through row click or identifier click.
- Explicit Inspect is the default only for Queue / Review, History / Audit, and explicitly catalogued exceptions.
- View and Inspect MUST NOT be treated as interchangeable labels. If the interaction preserves context and behaves unlike ordinary navigation, it is Inspect, not View.
Action hierarchy
- Every surface MUST distinguish between the primary inspect/open action, secondary safe actions, destructive actions, and long-running workflow launches.
- Standard CRUD and Read-only Registry rows MUST NOT exceed the primary open interaction plus one inline safe shortcut.
- All other secondary actions MUST move to overflow.
- Long-running workflow launches such as sync, compare, verify, generate, consent, setup, or retry SHOULD live in list headers or detail headers rather than in every row.
Destructive actions
- Destructive actions MUST NOT appear inline beside the primary inspect interaction on standard CRUD, Config-lite, or Read-only Registry surfaces.
- Destructive actions MUST live in overflow or the detail header.
- Destructive actions MUST use confirmation.
- High-risk or high-volume destructive bulk actions SHOULD use typed confirmation.
- The Queue Decision exception applies only when the destructive decision is part of the actual queue work.
Overflow and More
- Overflow actions MUST follow one product-wide pattern per surface class.
- Mixed labeled-overflow versus icon-only overflow patterns inside the same surface class are forbidden unless an approved exception documents why.
- Empty
ActionGroupand emptyBulkActionGroupare forbidden. - Placeholder UI added only to satisfy a contract or slot is forbidden.
Bulk actions
- Bulk actions are allowed only when they are safe enough, materially faster than row-by-row execution, and genuinely fit the surface.
- A surface with no real bulk need MUST NOT render bulk UI.
- Bulk destructive actions follow the same protection rules as row destructive actions, with stricter confirmation and review expectations.
Row label length and action budget
- Inline row action labels MUST stay short and SHOULD be one or two words.
- Long workflow labels belong in overflow, headers, or detail surfaces.
- Standard list rows MUST NOT become control centers for onboarding recovery, provider management, consent flows, RBAC setup, diagnostics, and destructive lifecycle actions all at once.
Scope and context semantics
- Scope chips, tenant pills, and similar context signals MUST correspond to real scoping behavior.
- A scope signal MUST NOT be shown when it neither scopes the displayed data nor materially changes the action targets.
- Remembered context is allowed only when labeled clearly as reference context rather than active scope.
- Cross-panel navigation MUST NOT imply that the operator remains inside the same logical scope when that is not true.
Canonical navigation and terminology
- Every domain object MUST have one canonical collection noun and one canonical singular noun.
- The same domain object MUST NOT use competing primary nouns across shells.
- The Operations domain MUST use one canonical collection noun. Parallel primary nouns such as Runs beside Operations are forbidden.
- Cross-panel navigation is allowed only when it lands on a canonical surface, uses stable nouns, and keeps back navigation clear.
Visibility of critical operational truth
- Critical operational truth MUST be visible by default.
- It MUST NOT be hidden only in default-off columns, tooltips, helper text, overflow menus, or detail pages when list decisions depend on it.
- Lifecycle truth, operability truth, health truth, execution outcome, trust/confidence, and next action MUST remain separate semantic dimensions.
- One badge, column, or label MUST NOT collapse multiple truth dimensions into a generic status.
Row density and scanability
- Standard CRUD lists MUST remain scanable.
- Outside Queue / Review and History / Audit exceptions, each row MAY contain at most one multi-line explanatory column and at most one prose-heavy explanatory context.
- Standard CRUD rows MUST NOT carry more than one sentence of flowing prose.
- Next-step prose belongs in detail, inspect, or queue surfaces, not in ordinary CRUD rows.
Custom abstractions
- Custom UI abstractions MAY document and validate, but they MUST NOT create declaration-only safety that diverges from real behavior.
- Contract systems MUST NOT force placeholder UI.
- Behavior matters more than declaration. If declared conformance and rendered behavior differ, the surface is non-conformant.
- A feature MUST NOT ship when its implemented interaction semantics contradict its declared surface type.
Exception Model (UI-EX-001)
Only catalogued exception types are allowed. Every exception MUST be named in the spec, reference its exception type, include a reason block, be called out explicitly in the PR, and carry at least one dedicated test.
Queue Decision Exception
- Allowed when per-item decision-making is the real queue work.
- Guardrails: Inspect remains available unless detail is already inline; irreversible decisions require confirmation; unrelated maintenance actions do not join the row.
History In-place Inspect Exception
- Allowed when leaving the page would break chronology or traceability.
- Guardrails: explicit Inspect is mandatory; row click is forbidden; generic mutation rails are forbidden.
Config-lite Edit-as-Inspect Exception
- Allowed when a separate View surface would add no value.
- Guardrails: no parallel View surface; no high-risk destructive flow as the default entry point.
Read-only Shortcut Exception
- Allowed for exactly one dominant non-destructive shortcut.
- Guardrails: inspect/open remains dominant; only one shortcut exists; the shortcut does not compete with the primary open path.
Cross-panel Canonical Route Exception
- Allowed when only one canonical surface makes sense.
- Guardrails: nouns stay stable; shell transition is explicit; back navigation is clear; scope signals remain truthful.
Filament UI — Action Surface Contract (NON-NEGOTIABLE)
For every new or modified Filament Resource, RelationManager, or Page:
Required surfaces
- List/Table MUST define Header Actions, Row Actions, Bulk Actions, and Empty-State CTA(s).
- Every table MUST provide a record inspection affordance that matches its surface type.
- Accepted forms are
recordUrl()row click, a primary linked column, or an explicit row action when the taxonomy requires Inspect. - CRUD / List-first, Config-lite, and Read-only Registry surfaces MUST NOT render a redundant View action when the same destination is already available through row click or identifier click.
- Queue / Review and History / Audit surfaces MAY use a lone explicit Inspect action because context-preserving inspect is the primary interaction.
- View/Detail MUST define header actions and MUST keep destructive actions grouped and confirmed.
- View/Detail MUST be sectioned using Infolists, Sections, Cards, Tabs, or equivalent composable structure.
- Create/Edit MUST provide consistent Save and Cancel UX.
Grouping and safety
- Standard CRUD and Read-only Registry rows MUST NOT exceed inspect/open plus one inline safe shortcut.
- Queue / Review rows MAY expose inline decision actions only when allowed by UI-EX-001.
- Everything else MUST move to
ActionGroup::make()or the detail header. - Bulk actions MUST be grouped via
BulkActionGrouponly when the surface has a real bulk use case. - Empty
ActionGroupandBulkActionGroupare forbidden. - Destructive actions MUST NOT be primary and MUST require confirmation; typed confirmation MAY be required for large or high-risk bulk changes.
- Relevant mutations MUST write an audit log entry.
RBAC enforcement
- Non-member access MUST abort(404) and MUST NOT leak existence.
- Members without capability MAY see disabled actions with helper text, but server-side execution MUST still abort(403).
- Central tenant and workspace UI enforcement helpers MUST be used for gating.
Behavior over declaration
- Every spec MUST include both a UI/UX Surface Classification and a UI Action Matrix.
- Custom action-surface contracts are legitimate only when they validate rendered behavior, not only declarations or slot counts.
- A change is not Done unless the implemented interaction semantics conform to the declared surface type or an approved exception documents and tests the deviation.
Filament UI — Layout & Information Architecture Standards (UX-001)
Goal: operator-facing Filament screens MUST feel enterprise-grade, legible, and decisive.
Page layout
- Create/Edit MUST default to a Main/Aside layout using a 3-column grid with
Main=columnSpan(2)andAside=columnSpan(1). - All fields MUST live inside Sections or Cards. Naked root-level inputs are forbidden.
- Main content carries domain definition and working content. Aside carries status and meta such as scope, owner, timestamps, or version labels.
- Related data MUST render as separate sections, tabs, or subordinate surfaces rather than as one long unstructured form or detail page.
View pages
- View/Detail MUST be a read-only surface built with Infolists or an equivalent read-first structure, not disabled edit forms.
- Status-like values MUST render via BADGE-001 semantics.
- Long text MUST read like prose, not like disabled textarea output.
Empty states
- Empty lists and tables MUST show a specific title, a one-sentence explanation, and exactly one primary CTA.
- When records exist, that primary CTA moves to the header and MUST NOT be duplicated in the empty state shell.
Actions and flows
- Pages SHOULD expose at most one primary header action and one secondary header action; all others belong in groups.
- Multi-step or high-risk flows MUST use a wizard or an equivalent staged flow with preview and confirmation.
- Destructive actions remain non-primary and confirmed.
Table defaults
- Tables SHOULD provide search when the dataset can grow, a meaningful default sort, and filters for core dimensions.
- Standard CRUD tables MUST stay scanable and MUST NOT rely on row prose to communicate next steps.
- Critical operational truth that informs list decisions MUST be default-visible.
Enforcement
- Shared layout builders such as
MainAsideForm,MainAsideInfolist, andStandardTableDefaultsSHOULD be reused where available. - A change is not Done unless UX-001 is satisfied or an approved exception documents why not.
Operator-facing UI Naming Standards (UI-NAMING-001)
Goal: operator-facing actions, runs, notifications, audit prose, and navigation MUST use one clear domain vocabulary.
Naming model
- Operator-facing copy MUST distinguish Scope, Source/Domain, Operation, and Target Object.
- Scope terms such as Workspace and Tenant describe execution context and MUST NOT become the primary action label unless they are the actual target object.
- Source/domain terms such as Intune or Entra are secondary and lead only when same-screen disambiguation genuinely requires them.
Primary labels
- Primary buttons, header actions, and menu actions MUST use Verb + Object.
- Preferred examples are
Sync policies,Sync groups,Capture baseline,Compare baseline,Restore policy,Run review, andExport review pack. - Implementation-first labels such as
Sync from tenant,Sync from Intune,Run tenant sync now, orStart inventory refresh from providerare forbidden.
Canonical nouns and routes
- Every domain object MUST keep one canonical collection noun and one canonical singular noun.
- Cross-shell or cross-panel navigation MUST preserve the same noun.
- Operations is the canonical collection noun for run records. Runs MUST NOT appear as a competing primary collection noun.
Run, notification, and audit semantics
- Visible run titles MUST use the same domain vocabulary as the initiating action and SHOULD remain concise noun phrases such as
Policy sync,Baseline capture,Baseline compare,Policy restore, andTenant review. - Notifications MUST use either
{Object} {state}or{Operation} {result}and remain short. - Audit prose MUST use the same operator-facing language as the initiating action.
- The same user-visible action MUST keep the same domain vocabulary across button labels, modal titles, run titles, notifications, audit prose, and related navigation.
Verb standard
- Preferred verbs are
Sync,Capture,Compare,Restore,Review,Export,Open,Archive,Resolve,Reopen, andAssign. Start,Execute,Trigger, andPerformSHOULD be avoided unless the domain specifically requires them.RunMAY be used only when the object is itself run-like, such asRun review; it MUST NOT become the fallback verb for everything.
Current binding decision
- The Policies screen primary action MUST be
Sync policies. - The Policies screen modal title MUST be
Sync policies. - The Policies screen success toast MUST be
Policy sync queued. - The visible run label for that action MUST be
Policy sync. - The audit prose for that action MUST be
{actor} queued policy sync.
Operator Surface Principles (OPSURF-001)
Goal: operator-facing surfaces MUST optimize for the operator's working question instead of raw implementation visibility.
Operator-first default surfaces
/adminis operator-first.- Default-visible content MUST use operator language, clear scope, and actionable status communication.
- Raw implementation details MUST NOT be default-visible on primary operator surfaces.
Progressive disclosure for diagnostics
- Diagnostic detail MAY exist, but it MUST be secondary and explicitly revealed.
- JSON payloads, raw IDs, internal field names, provider error details, and low-level technical metadata belong in diagnostics surfaces rather than the primary content region.
- Operators MUST NOT need to parse raw payloads to understand current state or next action.
Distinct truth dimensions
- When the domain has execution outcome, data completeness, governance result, lifecycle or readiness state, operability truth, health truth, trust/confidence, or next action semantics, the surface MUST keep them explicit instead of collapsing them into one ambiguous status.
- If multiple truth dimensions are summarized, the default-visible UI MUST label each dimension clearly.
Explicit mutation scope
- Every state-changing action MUST communicate before execution whether it affects TenantPilot only, the Microsoft tenant, or simulation only.
- Mutation scope MUST be understandable from nearby action copy, helper text, preview, or confirmation.
Safe execution
- Dangerous actions MUST follow a consistent safety flow: configuration, safety checks or simulation, preview, hard confirmation where required, then execution.
- One-click high-blast-radius actions are forbidden unless an approved exception documents replacement safeguards.
Explicit workspace and tenant context
- Workspace and tenant context MUST remain explicit in navigation, action copy, and page semantics.
- Tenant surfaces MUST NOT silently expose workspace-wide actions.
- Canonical workspace views that operate on tenant-owned records MUST make both workspace and tenant context legible before the operator acts.
Critical truth visibility and scanability
- Critical operational truth MUST be default-visible wherever the list or summary surface is used to prepare decisions.
- Standard CRUD surfaces MUST preserve scanability and MUST avoid collapsing multiple truth dimensions into one generic badge or one prose-heavy row.
Page contract requirement
- Every new or materially refactored operator-facing page MUST define the primary persona, surface type, primary operator question, default-visible information, diagnostics-only information, status dimensions used, mutation scope, primary actions, and dangerous actions.
- The page contract MUST live in the governing spec and stay in sync with implementation.
Spec Scope Fields (SCOPE-002)
- Every feature spec MUST declare Scope, Primary Routes, Data Ownership, and RBAC requirements.
- Canonical-view specs MUST define the default filter behavior when tenant context is active and the entitlement checks that prevent cross-tenant leakage.
Enforcement Model (UI-REVIEW-001)
Spec review requirements
- Every spec that changes an operator-facing surface MUST answer: surface type, primary inspect/open model, row-click rule, whether explicit View/Inspect exists or is forbidden, where secondary actions live, where destructive actions live, canonical collection route, canonical detail route, scope signals and their exact meaning, canonical noun, critical truth visible by default, and whether an exception type is used.
- Missing any of those answers makes the spec incomplete.
PR review requirements
- A PR MUST NOT pass when it introduces more than one primary inspect model, redundant View beside row click, destructive inline actions beside inspect on standard lists, empty overflow or bulk groups, long workflow labels in dense rows, misleading scope chips, drifting domain nouns, hidden critical operational truth, or undocumented exceptions without dedicated tests.
Guard tests
- Repository guards SHOULD validate: declared surface type, conformant primary inspect model, absence of redundant View actions, presence of explicit Inspect on Queue / Review and History / Audit surfaces, absence of empty
ActionGrouporBulkActionGroup, correct placement of destructive actions, truthful scope signals, stable canonical nouns across shells, and dedicated tests for every approved exception.
Immediate Retrofit Priorities
Wave 1 - Interaction normalization
- First fixes target redundant row click plus View, destructive row actions on standard lists, empty overflow or bulk groups, and rows that have become pseudo-control centers.
- First-slice focus surfaces are Tenants, Workspaces, Policies, Alert Deliveries, and other CRUD-first list surfaces with the same drift pattern.
- Wave 1 is done only when each surface has exactly one primary inspect model, destructive actions are protected, and placeholder groups are gone.
Wave 2 - Scope, nouns, and truth
- Then fix scope and context leaks, stabilize canonical nouns, make cross-panel transitions explicit, move critical operational truth to default-visible regions, and reduce prose-heavy dense rows.
Wave 3 - Enforcement
- Then move the constitution into repo enforcement, require the PR checklist, anchor guard tests, and trim old declaration-only action-surface checks until behavior is the governing truth.
Appendix A - One-page Condensed Constitution
- Every admin surface has one surface type.
- Every list has exactly one primary inspect/open model.
- CRUD and Registry surfaces use one-click open.
- Queue and Audit surfaces use explicit Inspect.
- Edit-as-inspect exists only for Config-lite resources.
- Standard lists expose at most one inline safe shortcut.
- Destructive actions never sit openly beside inspect on standard lists.
- Overflow is standardized per surface class and is never empty.
- Bulk exists only when it is genuinely useful.
- Scope chips must be truthful.
- Domain nouns are canonical and stable.
- Critical operational truth is default-visible.
- Semantic truth dimensions are not collapsed into a generic status.
- Standard lists stay scanable.
- Exceptions are catalogued, justified, and tested.
- Features with ambiguous interaction semantics do not ship.
Appendix B - Feature Review Checklist
- Surface type is declared.
- Primary inspect/open model is defined.
- Row-click rule is decided.
- View/Inspect is correctly present or correctly forbidden.
- Edit-as-inspect is used only when allowed.
- Secondary actions are grouped correctly.
- Destructive actions are placed correctly.
- Overflow is not empty.
- Bulk is justified.
- Inline labels are short.
- Scope signals are truthful.
- Canonical nouns stay consistent.
- Critical truth is visible.
- Scanability is preserved.
- Exceptions are documented and tested.
Appendix C - Red Flags for Future PRs
- Row click and View open the same destination.
- A row becomes a control center.
- Archive or Delete sits openly beside View or Inspect on a standard list.
- More menus or bulk menus are empty.
- Scope chips have no real scope effect.
- Runs and Operations are used as competing primary collection nouns.
- Long workflow labels live in dense tables.
- Edit is used as default inspect even though a true View surface exists.
- Queue surfaces throw the operator out of context through row click.
- Critical health or operability truth is hidden by default.
- A contract claims conformance while the rendered UI behaves differently.
Data Minimization & Safe Logging
- Inventory MUST store only metadata + whitelisted
meta_jsonb. - Payload-heavy content belongs in immutable snapshots/backup storage, not Inventory.
- Logs MUST not contain secrets/tokens; monitoring MUST rely on run records + error codes (not log parsing).
Badge Semantics Are Centralized (BADGE-001)
- Status-like badges (status/outcome/severity/risk/availability/boolean signals) MUST render via
BadgeCatalog/BadgeRenderer. - Filament resources/pages/widgets/views MUST NOT introduce ad-hoc status-like badge mappings (use a
BadgeDomaininstead). - Introducing or changing a status-like value MUST include updating the relevant badge mapper and adding/updating tests for the mapping.
- Tag/category chips (e.g., type/platform/environment) are not status-like and are not governed by BADGE-001.
Filament Native First / No Ad-hoc Styling (UI-FIL-001)
- Admin and operator-facing surfaces MUST use native Filament components, existing shared UI primitives, and centralized design patterns first.
- If Filament already provides the required semantic element, feature code MUST use the Filament-native component instead of a locally assembled replacement.
- Preferred native elements include
x-filament::badge,x-filament::button,x-filament::icon, and Filament Forms, Infolists, Tables, Sections, Tabs, Grids, and Actions.
Forbidden local replacements
- Feature code MUST NOT hand-build badges, pills, status chips, alert cards, or action buttons from raw
<span>,<div>, or<button>markup plus Tailwind classes when Filament-native or shared project primitives can express the same meaning. - Feature code MUST NOT introduce page-local visual status languages for status, risk, outcome, drift, trust, importance, or severity.
- Feature code MUST NOT make local color, border, rounding, or emphasis decisions for semantic UI states using ad-hoc classes such as
bg-danger-100,text-warning-900,border-dashed, orrounded-fullwhen the same state can be expressed through Filament props or shared primitives.
Shared primitive before local override
- If the same UI pattern can recur, it MUST use an existing shared primitive or introduce a new central primitive instead of reassembling the pattern inside a Blade view.
- Central badge and status catalogs remain the canonical source for status semantics; local views MUST consume them rather than re-map them.
Upgrade-safe preference
- Update-safe, framework-native implementations take priority over page-local styling shortcuts.
- Filament props such as
color,icon, and standard component composition are the default mechanism for semantic emphasis. - Publishing or emulating Filament internals for cosmetic speed is not acceptable when native composition or shared primitives are sufficient.
Exception rule
- Ad-hoc markup or styling is allowed only when all of the following are true:
- native Filament components cannot express the required semantics,
- no suitable shared primitive exists,
- and the deviation is justified briefly in code and in the governing spec or PR.
- Approved exceptions MUST stay layout-neutral, use the minimum local classes necessary, and MUST NOT invent a new page-local status language.
Review and enforcement
- Every UI review MUST answer:
- which native Filament element or shared primitive was used,
- why an existing component was insufficient if an exception was taken,
- and whether any ad-hoc status or emphasis styling was introduced.
- UI work is not Done if it introduces ad-hoc status styling or framework-foreign replacement components where a native Filament or shared UI solution was viable.
Incremental UI Standards Enforcement (UI-STD-001)
- UI consistency is enforced incrementally, not by recurring cleanup passes.
- New tables, filters, and list surfaces MUST follow established Filament-native standards from the first implementation.
- Deviations MUST be explicit and justified in the spec or PR.
- Canonical standards live in
docs/product/standards/and are the source of truth for:- Table UX (column tiers, sort, search, toggle, pagination, persistence, empty states)
- Filter UX (persistence, soft-delete, date range, enum sourcing, defaults)
- Actions UX (row/bulk/header actions, grouping, destructive safety)
- Guard tests enforce critical constraints automatically; the list surface review checklist catches the rest.
- A new spec that adds or modifies a list surface MUST reference the review checklist (
docs/product/standards/list-surface-review-checklist.md).
Spec-First Workflow
- For any feature that changes runtime behavior, include or update
specs/<NNN>-<slug>/withspec.md,plan.md,tasks.md, andchecklists/requirements.md. - New work branches from
devusingfeat/<NNN>-<slug>(spec + code in the same PR).
Quality Gates
- Changes MUST be programmatically tested (Pest) and run via targeted
php artisan test .... - Run
./vendor/bin/sail bin pint --dirtybefore finalizing.
Governance
Scope, Compliance, and Review Expectations
- This constitution applies across the repo. Feature specs may add stricter constraints but not weaker ones.
- Restore semantics changes require: spec update, checklist update (if applicable), and tests proving safety.
- Specs and PRs that introduce new persisted truth, abstractions, states, DTO/presenter layers, or taxonomies MUST include the proportionality review required by BLOAT-001.
- Review and approval MUST favor simplification, replacement, and absorption over additive semantic layering.
- Future-release preparation alone is not sufficient justification for new persistence or frameworkization unless security, tenant isolation, auditability, compliance evidence, or queue correctness already require it.
Amendment Procedure
- Propose changes as a PR that updates
.specify/memory/constitution.md. - The PR MUST include a short rationale and list of impacted templates/specs.
- Amendments MUST update Last Amended date.
Versioning Policy (SemVer)
- PATCH: clarifications/typos/non-semantic refinements.
- MINOR: new principle/section or materially expanded guidance.
- MAJOR: removing/redefining principles in a backward-incompatible way.
Version: 2.0.0 | Ratified: 2026-01-03 | Last Amended: 2026-03-28