3.6 KiB
3.6 KiB
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 inApp\Services\Auth\WorkspaceRoleCapabilityMap.Capabilities::WORKSPACE_SETTINGS_VIEWCapabilities::WORKSPACE_SETTINGS_MANAGE
- Rationale: The repo already enforces “no raw strings” and uses
Capabilities::isKnown()checks inWorkspaceCapabilityResolver. - Alternatives considered:
- Using raw strings (rejected: violates RBAC-UX-006).
Decision 2 — Workspace RBAC-UX enforcement for Filament actions
- Chosen: Use
App\Support\Rbac\WorkspaceUiEnforcementfor 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().
- non-member →
- Alternatives considered:
- Ad-hoc
abort()checks in each action (rejected: inconsistent and easier to regress).
- Ad-hoc
Decision 3 — Audit logging sink + stable action IDs
- Chosen:
- Workspace-scoped audit entries:
App\Services\Audit\WorkspaceAuditLogger(writesaudit_logswithworkspace_id,tenant_id = null). - Stable action identifiers: extend
App\Support\Audit\AuditActionIdenum with two new cases:WorkspaceSettingUpdated = 'workspace_setting.updated'WorkspaceSettingReset = 'workspace_setting.reset'
- Workspace-scoped audit entries:
- 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; includesworkspace_id; notenant_id)tenant_settings(tenant-owned; includesworkspace_idandtenant_idNOT 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_settingstable with nullabletenant_id(rejected: risks violating the constitution and blurs ownership).
- Single
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.