<!--
Sync Impact Report

- Version change: 1.10.0 → 1.11.0
- Modified principles:
  - None
- Added sections:
  - Operator-facing UI Naming Standards (UI-NAMING-001)
- Removed sections: None
- Templates requiring updates:
  - ✅ .specify/templates/plan-template.md
  - ✅ .specify/templates/spec-template.md
  - ✅ .specify/templates/tasks-template.md
  - N/A: .specify/templates/commands/ (directory not present in this repo)
- Follow-up TODOs:
  - None.
-->

# 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-only` restore 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.

### 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.

### 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.
- 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 `AuditLog` entries 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 `OperationRun` and execute asynchronously when any of the following applies:
  1. It can take > 2 seconds under normal conditions.
  2. It performs remote/external calls (e.g., Microsoft Graph).
  3. It is queued or scheduled.
  4. 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.
- If an action is security-relevant or affects operational behavior (e.g., “Ignore policy”), it MUST write an `AuditLog` entry
  including actor, tenant, action, target, before/after, and timestamp.
- The `OperationRun` record 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:

1) 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(...)`), not `Notification::make()`.

2) 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.total` and `summary_counts.processed` are 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/total` is allowed).

3) 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 `OperationRunCompleted` only.
- 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_code` that do not change `status`/`outcome`.

### Summary counts contract (OPS-UX-SUM-001)

- `operation_runs.summary_counts` is 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:
  1) updating `OperationSummaryKeys::all()`,
  2) updating the spec canonical list,
  3) adding/adjusting tests.

### Ops-UX regression guards are mandatory (OPS-UX-GUARD-001)

The repo MUST include automated guards (Pest) that fail CI if:
- any direct `OperationRun` status/outcome transition occurs outside `OperationRunService`,
- jobs emit DB notifications for operation completion/abort (`OperationRunCompleted` is 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 `OperationRun` notifications).
- 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).

### Filament UI — Action Surface Contract (NON-NEGOTIABLE)

For every new or modified Filament Resource / RelationManager / Page:

Required surfaces
- List/Table MUST define: Header Actions, Row Actions, Bulk Actions, and Empty-State CTA(s).
- Inspect affordance (List/Table): Every table MUST provide a record inspection affordance.
  - Accepted forms: clickable rows via `recordUrl()` (preferred), a dedicated row “View” action, or a primary linked column.
  - Rule: Do NOT render a lone “View” row action button. If View is the only row action, prefer clickable rows.
- View/Detail MUST define Header Actions (Edit + “More” group when applicable).
- View/Detail MUST be sectioned (e.g., Infolist Sections / Cards); avoid long ungrouped field lists.
- Create/Edit MUST provide consistent Save/Cancel UX.

Grouping & safety
- Max 2 visible Row Actions (typically View/Edit). Everything else MUST be in an ActionGroup “More”.
- Bulk actions MUST be grouped via BulkActionGroup.
- RelationManagers MUST follow the same action surface rules (grouped row actions, bulk actions where applicable, inspection affordance).
- Destructive actions MUST NOT be primary and MUST require confirmation; typed confirmation MAY be required for large/bulk changes.
- Relevant mutations MUST write an audit log entry.

RBAC enforcement
- Non-member access MUST abort(404) and MUST NOT leak existence.
- Member without capability: UI visible but disabled with tooltip; server-side MUST abort(403).
- Central enforcement helpers (tenant/workspace UI enforcement) MUST be used for gating.

Spec / DoD gates
- Every spec MUST include a “UI Action Matrix”.
- A change is not “Done” unless the Action Surface Contract is met OR an explicit exemption exists with documented reason.
- CI MUST run an automated Action Surface Contract check (test suite and/or command) that fails when required surfaces are missing.

### Filament UI — Layout & Information Architecture Standards (UX-001)

Goal: Demo-level, enterprise-grade admin UX. These rules are NON-NEGOTIABLE for new or modified Filament screens.

Page layout
- Create/Edit MUST default to a Main/Aside layout using a 3-column grid with `Main=columnSpan(2)` and `Aside=columnSpan(1)`.
- All fields MUST be inside Sections/Cards. No “naked” inputs at the root schema level.
- Main contains domain definition/content. Aside contains status/meta (status, version label, owner, scope selectors, timestamps).
- Related data (assignments, relations, evidence, runs, findings, etc.) MUST render as separate Sections below the main/aside grid (or as tabs/sub-navigation), never mixed as unstructured long lists.

View pages
- View/Detail MUST be a read-only experience using Infolists (or equivalent), not disabled edit forms.
- Status-like values MUST render as badges/chips using the centralized badge semantics (BADGE-001).
- Long text MUST render as readable prose (not textarea styling).

Empty states
- Empty lists/tables MUST show: a specific title, one-sentence explanation, and exactly ONE primary CTA in the empty state.
- When non-empty, the primary CTA MUST move to the table header (top-right) and MUST NOT be duplicated in the empty state.

Actions & flows
- Pages SHOULD expose at most 1 primary header action and 1 secondary action; all other actions MUST be grouped (ActionGroup / BulkActionGroup).
- Multi-step or high-risk flows MUST use a Wizard (e.g., capture/compare/restore with preview + confirmation).
- Destructive actions MUST remain non-primary and require confirmation (RBAC-UX-005).

Table work-surface defaults
- Tables SHOULD provide search (when the dataset can grow), a meaningful default sort, and filters for core dimensions (status/severity/type/tenant/time-range).
- Tables MUST render key statuses as badges/chips (BADGE-001) and avoid ad-hoc status mappings.

Enforcement
- New resources/pages SHOULD use shared layout builders (e.g., `MainAsideForm`, `MainAsideInfolist`, `StandardTableDefaults`) to keep screens consistent.
- A change is not “Done” unless UX-001 is satisfied OR an explicit exemption exists with a documented rationale in the spec/PR.

### Operator-facing UI Naming Standards (UI-NAMING-001)

Goal: operator-facing actions, run labels, notifications, audit prose, and related UI copy MUST use consistent,
enterprise-grade product language.

Naming model
- Operator-facing copy MUST distinguish four layers: Scope, Source/Domain, Operation, and Target Object.
- Scope terms (`Workspace`, `Tenant`) describe execution context and MUST NOT be used as the primary action label unless they are the actual target object.
- Source/Domain terms (`Intune`, `Entra`, `Teams`, future providers) are secondary and MUST NOT lead the primary label unless the current screen presents competing sources that need explicit disambiguation.

Primary action labels
- Primary buttons, header actions, and menu actions MUST use `Verb + Object`.
- Preferred examples: `Sync policies`, `Sync groups`, `Capture baseline`, `Compare baseline`, `Restore policy`, `Run review`, `Export review pack`.
- Forbidden examples: `Sync from tenant`, `Backup tenant`, `Compare tenant`, `Sync from Intune`, `Run tenant sync now`, `Start inventory refresh from provider`.

Domain vocabulary
- Operator-facing copy MUST prefer product-domain objects such as `policies`, `groups`, `baseline`, `findings`, `review pack`, `alerts`, and `operations`.
- Primary operator-facing copy MUST NOT use implementation-first terms such as `provider`, `gateway`, `resolver`, `collector`, `contract registry`, or `job dispatch`.
- Source/domain details MAY appear in modal descriptions, helper text, run metadata, audit metadata, and notifications when needed for precision.

Run, notification, and audit semantics
- Visible run titles MUST use the same domain vocabulary as the initiating action and SHOULD be concise noun phrases such as `Policy sync`, `Baseline capture`, `Baseline compare`, `Policy restore`, and `Tenant review`.
- Notifications MUST use either `{Object} {state}` or `{Operation} {result}` and remain short, e.g. `Policy sync queued`, `Policy sync completed`, `Policy sync failed`, `Baseline compare detected drift`.
- Audit prose MUST use the same operator-facing language, e.g. `{actor} queued policy sync`, `{actor} captured baseline`, `{actor} reopened finding`.
- The same user-visible action MUST keep the same domain vocabulary across button labels, modal titles, run titles, notifications, and audit prose.

Verb standard
- Preferred verbs are `Sync`, `Capture`, `Compare`, `Restore`, `Review`, `Export`, `Open`, `Archive`, `Resolve`, `Reopen`, and `Assign`.
- `Start`, `Execute`, `Trigger`, and `Perform` SHOULD be avoided for operator-facing copy unless there is a deliberate domain reason.
- `Run` MAY be used only when the object is itself run-like, such as `Run review` or `Run compare`; it MUST NOT be the generic fallback verb for all operations.

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`.

Spec Scope Fields (SCOPE-002)

- Every feature spec MUST declare:
  - Scope: workspace | tenant | canonical-view
  - Primary Routes
  - Data Ownership: workspace-owned vs tenant-owned tables/records impacted
  - RBAC: membership requirements + capability requirements
- For canonical-view specs, the spec MUST define:
  - Default filter behavior when tenant-context is active (e.g., prefilter to current tenant)
  - Explicit entitlement checks that prevent cross-tenant leakage

### 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 `BadgeDomain` instead).
- 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.

### 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>/` with `spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`.
- New work branches from `dev` using `feat/<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 --dirty` before finalizing.

## Governance

### Scope & Compliance
- 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.

### 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**: 1.11.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-03-10
