# Product Principles

> Permanent product principles that govern every spec, every UI decision, and every architectural choice.
> New specs must align with these. If a principle needs to change, update this file first.

**Last reviewed**: 2026-03-08

---

## Identity & Isolation

### Workspace-first context
Workspace is the primary session context. Every UI surface, every query, every action is workspace-scoped.
Non-members receive deny-as-not-found (404 semantics) — they never learn the resource exists.

### Tenant isolation (non-negotiable)
Every read/write is tenant-scoped. Cross-tenant views are explicit, access-checked, aggregation-based.
Non-member → 404. No cross-embedding of workspace-owned and tenant-owned data.

### SCOPE-001: Strict ownership model
- **Workspace-owned** = standards, templates, configuration, baselines
- **Tenant-owned** = observed state, evidence, artifacts, inventory

---

## Authorization & Safety

### Capability-first RBAC
Single canonical registry (`Capabilities.php`). No raw strings. CI fails on unknown capabilities.
UI visibility is never a security boundary — missing server-side auth is a P0 bug.

### Visible-but-disabled UX
Members see disabled actions with tooltip explaining the missing capability.
Non-members see nothing (404 semantics).

### Destructive actions require safe flows
All destructive actions → `requiresConfirmation()`. No exceptions.
Write operations require: preview/dry-run → confirmation → audit log → tests.
High-risk types default to `preview-only`.

---

## Operations & Observability

### 3-Surface Feedback (non-negotiable)
1. **Toast** — intent acknowledged
2. **Progress** — active work visible
3. **Terminal DB Notification** — audit record

No other feedback patterns. No silent mutations.

### OperationRun lifecycle is service-owned
All status/outcome transitions via `OperationRunService` only.
Summary counts via `OperationSummaryKeys::all()`. Flat numeric only.

### Enterprise-grade auditability
Every mutation has a trail. Backup created, restore attempted, policy change detected — logged, tenant-scoped, RBAC-respecting.

---

## Data & Architecture

### Inventory-first, Snapshots-second
- `InventoryItem` = last observed metadata
- `PolicyVersion.snapshot` = explicit immutable JSONB capture
- Intune remains external source of truth

### Single Contract Path to Graph
All MS Graph calls via `GraphClientInterface`. Endpoints modeled in `config/graph_contracts.php`.
No hardcoded "quick endpoints". Unknown types fail safe.

### Deterministic Capabilities
Backup/restore/risk flags derived deterministically from config via Capabilities Resolver.
Must be snapshot-testable.

### Data minimization & safe logging
Inventory = metadata only. No secrets in logs.
Monitoring relies on run records + error codes.

---

## UI & Information Architecture

### UX-001: Layout & IA Standards
Main/Aside layout. Sections required. View pages use Infolists.
Empty states with specific title + explanation + exactly 1 CTA.

### Action Surface Contract (non-negotiable)
Required surfaces per page type (list/view/create/edit).
Max 2 visible row actions. Destructive requires confirmation.
Every spec with UI changes must include a UI Action Matrix.

### Badge semantics centralized
All status badges via `BadgeCatalog` / `BadgeRenderer`. No ad-hoc badge mappings.

### Canonical navigation and terminology
Consistent naming, consistent routing, consistent mental model.
No competing terms for the same concept.

---

## Process

### Spec-first workflow
Runtime behavior changes require spec update first.
Every spec must declare: scope, primary routes, data ownership, RBAC requirements (SCOPE-002).

### Regression guards mandatory
RBAC regression tests per role. Ops-UX regression guards prevent direct status writes and ad-hoc notifications.
Architectural guard tests enforce code-level contracts.

---

## Filament v5 Alignment

### Non-negotiables
- Livewire v4.0+
- Panel providers in `bootstrap/providers.php`
- Global search requires Edit/View page or is disabled
- Prefer render hooks + CSS hooks over publishing internal views
- Heavy assets loaded on-demand (`loadedOnRequest()`)
