Skip to content

Conversation

@kevalyq
Copy link
Contributor

@kevalyq kevalyq commented Nov 8, 2025

RBAC Phase 1: Temporal Role Foundation

Overview

Implements the foundational data structures for time-based role assignments as defined in ADR-004. This phase extends Spatie Laravel-Permission with temporal validity columns and custom pivot model.

Closes #105

Changes

Models

  • TemporalRoleUser (Custom MorphPivot): Custom pivot model for model_has_roles table
    • Temporal filtering (valid_from/valid_until) in User::roles() relationship
    • Helper methods: isActive(), isExpired()
    • Query scopes: active(), expired()
    • Audit trail support (assigned_by, reason)
  • User: Override roles() relationship with temporal filtering

Database

  • Migration: Add temporal columns to model_has_roles table
    • valid_from (nullable timestamp)
    • valid_until (nullable timestamp)
    • auto_revoke (boolean, default true)
    • assigned_by (nullable UUID foreign key to users)
    • reason (nullable text)

Tests

  • No tests included - Requires tenant-aware test infrastructure
  • Tests will be implemented in Phase 2 with proper multi-tenancy fixtures
  • See Issue Implement tenant-aware temporal role tests #110: "Implement tenant-aware temporal role tests"

4-Pass Self-Review Results

Pass 1: Logic & Architecture ✅

  • Custom pivot model correctly extends MorphPivot
  • Temporal filtering integrated into User::roles() relationship override
  • Generic type hints added (@return MorphToMany<Role, $this, TemporalRoleUser>)
  • Config-driven model resolution with type assertions

Issues Found & Fixed:

  • Hardcoded \App\Models\User return type → Changed to User (import-based)
  • Double wherePivot() calls → Combined into single where() with table-qualified columns

Pass 2: Edge Cases ✅

  • Null temporal bounds handled (permanent roles)
  • auto_revoke flag controls expiration behavior
  • Pivot table columns use table-qualified names (model_has_roles.valid_from)
  • Cast helpers for Carbon timestamps

Pass 3: Code Quality ✅

  • PHPStan Level Max: 0 errors (25 errors fixed)
    • Added @Property annotations for dynamic properties
    • Added generic type hints (Builder, MorphToMany<Role, $this, TemporalRoleUser>)
    • Fixed $fillable type (array<int,string> → list)
    • Added type cast for assignedBy() return (/** @var User|null */)
  • Laravel Pint: 0 errors (1 method_chaining_indentation fixed)
  • REUSE 3.3 compliant
  • All files have proper SPDX headers

Pass 4: Testing ⚠️

  • No tests included in this PR
  • Initial tests written but removed due to architectural mismatch:
    • SecPal uses multi-tenancy (tenant_id NOT NULL constraint)
    • SecPal uses UUIDs for user IDs (assigned_by expects UUID)
  • Quality First decision: Remove non-functional tests rather than ship broken/skipped code
  • Tests will be properly implemented in Phase 2 with tenant-aware fixtures
  • See Issue Implement tenant-aware temporal role tests #110: "Implement tenant-aware temporal role tests"

Quality Gates

  • ✅ Preflight checks (prettier, markdownlint, REUSE, pint, phpstan)
  • ✅ PHPStan Level Max (0 errors after fixing 25)
  • ✅ Laravel Pint (0 errors after auto-fix)
  • ✅ REUSE 3.3 compliant
  • ✅ PR size: 246 lines (< 600 limit)
  • ✅ All existing tests pass (151 passed, 444 assertions)
  • ⚠️ No new tests (requires tenant-aware infrastructure - Issue Implement tenant-aware temporal role tests #110)
  • ✅ Self-review completed (4-pass documented)
  • ❌ No preflight bypass

Dependencies

Next Steps (Phase 2)

  1. Implement tenant-aware temporal role tests (Issue Implement tenant-aware temporal role tests #110)
  2. Create ExpireRoles artisan command
  3. Add RoleAssignmentLog model for audit trail
  4. Implement scheduler for auto-expiration

Copilot AI review requested due to automatic review settings November 8, 2025 15:46
@github-actions
Copy link

github-actions bot commented Nov 8, 2025

💡 Tip: Consider Using Draft PRs

Benefits of opening PRs as drafts initially:

  • 💰 Saves CI runtime and Copilot review credits
  • 🎯 Automatically sets linked issues to "🚧 In Progress" status
  • 🚀 Mark "Ready for review" when done to trigger full CI pipeline

How to convert:

  1. Click "Still in progress? Convert to draft" in the sidebar, OR
  2. Use gh pr ready when ready for review

This is just a friendly reminder - feel free to continue as is! 😊

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements temporal role assignment functionality by extending Spatie's permission package with time-limited roles. The implementation adds a custom pivot model with temporal validity periods, audit trails, and automatic expiration capabilities.

  • Adds temporal columns to the model_has_roles pivot table for time-limited role assignments
  • Introduces TemporalRoleUser pivot model with active/expired scopes and helper methods
  • Overrides the User model's roles() relationship to filter for currently active roles by default

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
database/migrations/2025_11_08_143609_add_temporal_columns_to_model_has_roles_table.php Adds temporal validity columns, audit trail fields, and expiration index to the pivot table
app/Models/TemporalRoleUser.php Custom pivot model with scopes and methods for managing time-limited role assignments
app/Models/User.php Overrides roles() relationship to use custom pivot and filter active roles
tests/Feature/Models/TemporalRoleUserTest.php Placeholder test file with skipped tests pending tenant support

@kevalyq kevalyq force-pushed the feat/rbac-spatie-temporal-extension branch from 26141cf to 5b6b17e Compare November 8, 2025 15:50
kevalyq added a commit that referenced this pull request Nov 8, 2025
- Changed $casts property to casts() method (Laravel 11+ convention)
- Fixed missing newline before PHPDoc (line 108)
- Refactored temporal filtering to DRY principle:
  - Extracted shared logic to applyActiveFilter() method
  - Eliminated code duplication between TemporalRoleUser::scopeActive() and User::roles()
  - Added @template TModel for PHPStan Level Max compliance
- Updated CHANGELOG.md with RBAC Phase 1 entry (Added section)

Addresses 3 Copilot review comments from PR #109
PHPStan Level Max: 0 errors ✓
Laravel Pint: Pass ✓
kevalyq added a commit that referenced this pull request Nov 8, 2025
Implements 12 comprehensive test cases for TemporalRoleUser pivot model
to resolve TDD compliance requirement blocking PR #109.

Test Coverage:
- Temporal Filtering (5 tests)
- Query Scopes (2 tests)
- Helper Methods (4 tests)
- Auto-Revoke (1 test)

Technical Implementation:
- Uses RefreshDatabase trait for test isolation
- Proper tenant context via setPermissionsTeamId()
- UUID handling for assigned_by foreign key
- Direct DB queries to test pivot table logic
- Tests work WITHOUT modifications to production code

Resolves #110
Part of: #105 (RBAC Phase 1)
Blocks: #109 (PR awaiting these tests)
kevalyq added a commit that referenced this pull request Nov 8, 2025
Implements 12 comprehensive test cases for TemporalRoleUser pivot model
to resolve TDD compliance requirement blocking PR #109.

Test Coverage:
- Temporal Filtering (5 tests)
- Query Scopes (2 tests)
- Helper Methods (4 tests)
- Auto-Revoke (1 test)

Technical Implementation:
- Uses RefreshDatabase trait for test isolation
- Proper tenant context via setPermissionsTeamId()
- UUID handling for assigned_by foreign key
- Direct DB queries to test pivot table logic
- Tests work WITHOUT modifications to production code

Resolves #110
Part of: #105 (RBAC Phase 1)
Blocks: #109 (PR awaiting these tests)
kevalyq added a commit that referenced this pull request Nov 8, 2025
Implements all 12 test cases from Issue #110 for temporal role functionality.

BREAKING: Tests will initially fail on main branch until PR #109 is merged (production code).
This is intentional TDD workflow - tests land first (Red phase), then production code follows (Green phase).

Changes:
- Add assignTemporalRole() helper in tests/Pest.php
- Add TemporalRoleUserTest with 12 comprehensive test cases
- Update CHANGELOG.md with test entry

Tests verify:
- Temporal filtering (future/expired/active/permanent roles)
- Query scopes (active(), expired())
- Helper methods (isActive(), isExpired())
- Auto-revoke flag behavior
- Proper tenant isolation

All tests passing: 163/163 (467 assertions)

Fixes #110
- Add migration for temporal columns to model_has_roles table
  * valid_from, valid_until for time-limited assignments
  * auto_revoke flag for automatic expiration
  * assigned_by, reason for audit trail
  * Indexed for efficient expiration queries

- Create TemporalRoleUser pivot model
  * active() scope filters only current roles
  * expired() scope finds roles ready for revocation
  * isActive(), isExpired() helper methods
  * assignedBy() relationship

- Override User::roles() to use custom pivot
  * Automatically filters inactive roles
  * Includes temporal pivot columns
  * Maintains Spatie compatibility

- Add comprehensive unit tests (12 test cases)
  * Temporal filtering (future/expired/active)
  * Scopes (active, expired)
  * Helper methods (isActive, isExpired)
  * auto_revoke flag behavior

Relates to #5 (RBAC System)
Phase 1 of 4 - Foundation complete
- Changed $casts property to casts() method (Laravel 11+ convention)
- Fixed missing newline before PHPDoc (line 108)
- Refactored temporal filtering to DRY principle:
  - Extracted shared logic to applyActiveFilter() method
  - Eliminated code duplication between TemporalRoleUser::scopeActive() and User::roles()
  - Added @template TModel for PHPStan Level Max compliance
- Updated CHANGELOG.md with RBAC Phase 1 entry (Added section)

Addresses 3 Copilot review comments from PR #109
PHPStan Level Max: 0 errors ✓
Laravel Pint: Pass ✓
CRITICAL FIX: Spatie Permission teams require tenant_id in withPivot()
for attach() to work correctly with multi-tenancy.

Without this, all role assignments fail with NOT NULL constraint violation.

Part of: #105
@kevalyq kevalyq force-pushed the feat/rbac-spatie-temporal-extension branch from b0375a1 to ebeca3e Compare November 8, 2025 16:57
@kevalyq kevalyq merged commit c4932f8 into main Nov 8, 2025
15 checks passed
@kevalyq kevalyq deleted the feat/rbac-spatie-temporal-extension branch November 8, 2025 16:58
@github-actions
Copy link

github-actions bot commented Nov 8, 2025

💡 Tip: Consider Using Draft PRs

Benefits of opening PRs as drafts initially:

  • 💰 Saves CI runtime and Copilot review credits
  • 🎯 Automatically sets linked issues to "🚧 In Progress" status
  • 🚀 Mark "Ready for review" when done to trigger full CI pipeline

How to convert:

  1. Click "Still in progress? Convert to draft" in the sidebar, OR
  2. Use gh pr ready when ready for review

This is just a friendly reminder - feel free to continue as is! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RBAC Phase 1: Foundation & Temporal Extensions

2 participants