# Research — Settings Foundation (Workspace + Optional Tenant Override) (097) ## Decisions ### Decision 1 — Canonical capability registry + role mapping - **Chosen**: Add new workspace capabilities as constants in `App\Support\Auth\Capabilities`, and map them in `App\Services\Auth\WorkspaceRoleCapabilityMap`. - `Capabilities::WORKSPACE_SETTINGS_VIEW` - `Capabilities::WORKSPACE_SETTINGS_MANAGE` - **Rationale**: The repo already enforces “no raw strings” and uses `Capabilities::isKnown()` checks in `WorkspaceCapabilityResolver`. - **Alternatives considered**: - Using raw strings (rejected: violates RBAC-UX-006). ### Decision 2 — Workspace RBAC-UX enforcement for Filament actions - **Chosen**: Use `App\Support\Rbac\WorkspaceUiEnforcement` for Filament page actions. - **Rationale**: This helper already implements the exact semantics required by the constitution: - non-member → `abort(404)` (deny-as-not-found) - member missing capability → `abort(403)` - defense-in-depth server-side guard via `before()`. - **Alternatives considered**: - Ad-hoc `abort()` checks in each action (rejected: inconsistent and easier to regress). ### Decision 3 — Audit logging sink + stable action IDs - **Chosen**: - Workspace-scoped audit entries: `App\Services\Audit\WorkspaceAuditLogger` (writes `audit_logs` with `workspace_id`, `tenant_id = null`). - Stable action identifiers: extend `App\Support\Audit\AuditActionId` enum with two new cases: - `WorkspaceSettingUpdated = 'workspace_setting.updated'` - `WorkspaceSettingReset = 'workspace_setting.reset'` - **Rationale**: The repo already has a stable-action-id convention and tests around audit redaction and scoping; using the existing audit logger preserves sanitizer behavior (no secrets). - **Alternatives considered**: - Introducing a new audit sink (rejected: violates “use existing audit sinks” precedent and increases inconsistency risk). - Using un-enumed string action IDs (possible, but rejected in favor of stronger standardization). ### Decision 4 — Storage model for workspace defaults + tenant overrides - **Chosen**: Use two tables (scope-pure) rather than a single polymorphic table: - `workspace_settings` (workspace-owned; includes `workspace_id`; no `tenant_id`) - `tenant_settings` (tenant-owned; includes `workspace_id` and `tenant_id` NOT NULL) - **Rationale**: Aligns with the constitution’s scope/ownership rule: - “Workspace-owned tables MUST include workspace_id and MUST NOT include tenant_id.” - “Tenant-owned tables MUST include workspace_id and tenant_id as NOT NULL.” - **Alternatives considered**: - Single `workspace_settings` table with nullable `tenant_id` (rejected: risks violating the constitution and blurs ownership). ### Decision 5 — Resolver precedence + caching - **Chosen**: - Precedence: tenant override → workspace override → system default. - Caching: request-local only, implemented in-memory inside the resolver (no cross-request cache store in v1). - **Rationale**: Matches the clarified spec requirements and mirrors existing request-local caching patterns (e.g., capability resolver). - **Alternatives considered**: - Cross-request cache with TTL (rejected for v1: adds invalidation complexity and isn’t required). ## Notes on existing repo patterns (evidence) - Canonical capability registry exists: `App\Support\Auth\Capabilities`. - Workspace capability checks and request-local caching exist: `App\Services\Auth\WorkspaceCapabilityResolver`. - Workspace RBAC-UX enforcement helper exists: `App\Support\Rbac\WorkspaceUiEnforcement`. - Workspace audit logger exists (with sanitizer): `App\Services\Audit\WorkspaceAuditLogger`.