40 KiB
TenantPilot / TenantAtlas — Handover Document
Generated: 2026-03-06 · Branch:
dev· HEAD:da1adbdStack: Laravel 12 · Filament v5 · Livewire v4 · PostgreSQL 16 · Tailwind v4 · Pest 4
Executive Summary
- Product: Enterprise Intune Governance SaaS — backup, version-control, restore, drift detection, and observability for Microsoft Intune policy configurations across multiple tenants.
- Core value prop: Immutable JSONB policy snapshots, restore wizard with dry-run/preview, inventory-first drift detection against Golden Master baselines, workspace-scoped RBAC, and alert/evidence pipeline.
- 28+ Intune policy types supported (device config, settings catalog, compliance, app protection, conditional access, scripts, enrollment, endpoint security, update rings, etc.) — defined in config/tenantpilot.php.
- Maturity: Production-capable MVP with ~109 specs, 708 test files (582 Feature + 125 Unit + 1 Deprecation), extensive guard tests, and an architectural constitution document.
- Three Filament panels:
/admin(workspace-tenant context),/admin/t(tenant-scoped),/system(platform operator console). - Workspace-first multi-tenancy: All data is workspace-isolated. Tenants belong to workspaces. Non-members get 404 (deny-as-not-found).
- Capability-first RBAC: ~40+ capabilities in a canonical registry (app/Support/Auth/Capabilities.php), enforced server-side with UI enforcement helpers.
- Operations system: Unified
OperationRunmodel with 25+ run types, idempotent creation, stale-run detection, 3-surface feedback (toast → progress → DB notification). - Baseline/Drift engine: 4-spec progression (116→119) — meta-fidelity v1 → content-fidelity v1.5 → full evidence capture v2 → legacy drift cutover. All 4 specs implemented on
dev. - Key open areas: Formal release grouping (R1/R2/R3) exists only as brainstorming; missing: exception/risk-acceptance workflows, compliance evidence packs, cross-tenant promotion, MSP portfolio dashboard, change approval workflows.
- Biggest risk: No
.env.examplein repo; Dokploy deployment config is external; no formal CI pipeline config in repo.
Product & Roadmap Snapshot
What exists (fully implemented on dev)
| Domain | Status | Key Specs |
|---|---|---|
| Policy Sync (28+ types) | ✅ Implemented | 001–030 |
| Immutable PolicyVersions (JSONB) | ✅ Implemented | Core |
| BackupSets + BackupItems | ✅ Implemented | 005, 006, 016 |
| Restore Wizard (dry-run, preview, execute) | ✅ Implemented | 011, 023, 049 |
| Assignments backup/restore + group mapping | ✅ Implemented | 004, 006, 094 |
| Settings Catalog readable names | ✅ Implemented | 003, 045 |
| Inventory sync + coverage matrix | ✅ Implemented | 039–042 |
| Dependency graph (inventory_links) | ✅ Implemented | 042, 079 |
| OperationRun unified monitoring | ✅ Implemented | 053, 054, 055, 078 |
| Provider connections + gateway | ✅ Implemented | 061, 081, 089, 108 |
| Entra OIDC sign-in | ✅ Implemented | 063, 064 |
| Workspace + Tenant RBAC | ✅ Implemented | 062, 065, 066, 070–072 |
| Managed tenant onboarding wizard | ✅ Implemented | 073 |
| Verification checklist | ✅ Implemented | 074, 075, 084 |
| Backup scheduling + retention | ✅ Implemented | 032, 091 |
| Alerts v1 (Teams + Email) | ✅ Implemented | 099, 100 |
| Baseline governance (Golden Master) | ✅ Implemented | 101, 115–119 |
| Findings lifecycle + SLA | ✅ Implemented | 044, 111 |
| Permission posture + Entra admin roles | ✅ Implemented | 104, 105 |
| Review pack export (CSV+ZIP) | ✅ Implemented | 109 |
| Settings (workspace + tenant) | ✅ Implemented | 097, 098 |
| System console + control tower | ✅ Implemented | 113, 114 |
| Platform ops runbooks | ✅ Implemented | 113 |
| Unified badges + tag catalog | ✅ Implemented | 059, 060 |
| Legacy purge (bulk ops, old runs) | ✅ Implemented | 056, 086–088, 092 |
Roadmap (from brainstorming — specs/0800-future-features/brainstorming.md)
Priority order: MSP Portfolio + Alerting > Drift + Approvals > Standardisierung/Linting > Promotion DEV→PROD > Recovery Confidence
| Release | Theme | Items | Code exists? |
|---|---|---|---|
| R1 "Golden Master Governance" | Baseline drift as production feature | Baseline capture/compare/alerts, findings SLA, evidence capture | ✅ Specs 101, 111, 115–119 implemented |
| R1 cont. | Operations polish | OperationRun canonicalization, monitoring hub, action surfaces | ✅ Specs 078, 082, 090, 110 implemented |
| R2 "Tenant Reviews & Evidence" | Evidence packs, stored reports | Review packs, permission posture, Entra admin roles, stored reports | ✅ Partial — Specs 104, 105, 109 implemented; no formal "evidence pack" spec |
| R2 cont. | Exception/risk-acceptance workflow | Finding exceptions, risk acceptance tracking | ❌ Missing — Finding model has risk_accepted status but no exception entity |
| R2 cont. | Alert escalation + notification routing | Alert rules per event type, quiet hours, cooldown | ✅ Specs 099, 100 implemented |
| R3 "MSP Portfolio OS" | Cross-tenant compare, portfolio dashboard | Cross-tenant compare, MSP health dashboard, multi-workspace admin | ❌ Spec 043 exists (draft), no implementation |
| R3 cont. | Standardisierung / Policy linting | Policy quality checks, duplicate finder, orphaned items | ❌ Brainstorming only |
| Later | Change approval workflows | Approval gates before restore | ❌ Brainstorming only |
| Later | Recovery confidence | Automated restore tests, readiness report | ❌ Brainstorming only |
| Later | Script & secrets governance | Script diff/approval, secret scanning | ❌ Brainstorming only |
| Later | Security posture score | Blast radius, opt-in high-risk | ❌ Brainstorming only |
Current Feature Map
Implemented
| Feature | UI Entrypoint | Models | Key Jobs | DB Tables |
|---|---|---|---|---|
| Policy Sync | PolicyResource |
Policy, PolicyVersion |
SyncPoliciesJob, CapturePolicySnapshotJob |
policies, policy_versions |
| Backup Sets | BackupSetResource |
BackupSet, BackupItem |
AddPoliciesToBackupSetJob, RemovePoliciesFromBackupSetJob |
backup_sets, backup_items |
| Restore Wizard | RestoreRunResource |
RestoreRun |
ExecuteRestoreRunJob, RestoreAssignmentsJob |
restore_runs |
| Backup Scheduling | BackupScheduleResource |
BackupSchedule |
RunBackupScheduleJob, ApplyBackupScheduleRetentionJob |
backup_schedules |
| Inventory | InventoryItemResource, InventoryCoverage page |
InventoryItem, InventoryLink |
RunInventorySyncJob, ProviderInventorySyncJob |
inventory_items, inventory_links |
| Findings | FindingResource |
Finding |
CompareBaselineToTenantJob, GeneratePermissionPostureFindingsJob |
findings |
| Baselines | BaselineProfileResource, BaselineSnapshotResource, BaselineCompareLanding |
BaselineProfile, BaselineSnapshot, BaselineSnapshotItem, BaselineTenantAssignment |
CaptureBaselineSnapshotJob, CompareBaselineToTenantJob |
baseline_profiles, baseline_snapshots, baseline_snapshot_items, baseline_tenant_assignments |
| Alerts | AlertRuleResource, AlertDestinationResource, AlertDeliveryResource |
AlertRule, AlertDestination, AlertDelivery, AlertRuleDestination |
Jobs/Alerts/* |
alert_rules, alert_destinations, alert_deliveries, alert_rule_destinations |
| Operations Hub | Operations page, TenantlessOperationRunViewer |
OperationRun |
All domain jobs | operation_runs |
| Provider Connections | ProviderConnectionResource |
ProviderConnection, ProviderCredential |
ProviderConnectionHealthCheckJob |
provider_connections, provider_credentials |
| RBAC | ChooseWorkspace, ChooseTenant, NoAccess, BreakGlassRecovery |
Workspace, WorkspaceMembership, Tenant, TenantMembership, TenantRoleMapping |
RefreshTenantRbacHealthJob |
workspaces, workspace_memberships, tenants, tenant_memberships, tenant_role_mappings |
| Entra Groups | EntraGroupResource |
EntraGroup |
EntraGroupSyncJob |
entra_groups |
| Verification | TenantDiagnostics, TenantRequiredPermissions |
VerificationCheckAcknowledgement |
— | verification_check_acknowledgements |
| Permission Posture | Via FindingResource |
Finding, StoredReport |
GeneratePermissionPostureFindingsJob, ScanEntraAdminRolesJob |
findings, stored_reports |
| Review Packs | ReviewPackResource |
ReviewPack |
GenerateReviewPackJob |
review_packs |
| Settings | WorkspaceSettings page |
WorkspaceSetting, TenantSetting |
— | workspace_settings, tenant_settings |
| Audit Log | AuditLog page |
AuditLog |
— | audit_logs |
| System Console | /system/dashboard, /system/ops/runbooks |
PlatformUser |
— | platform_users |
Partial / In-Progress
| Feature | Status | Gap |
|---|---|---|
| Cross-tenant compare | Spec 043 drafted | No implementation code |
| Policy lifecycle | Spec 900 drafted | Addresses ghost / orphaned policies — not yet implemented |
| Exception/Risk acceptance | Finding has risk_accepted status |
No formal exception entity, no evidence attachment workflow |
Missing (no code, no spec beyond brainstorming)
- Change approval workflows / approval gates
- Policy linting / standardization engine
- Recovery confidence / automated restore tests
- Script & secrets governance
- Security posture scoring / blast radius
- MSP portfolio health dashboard
- Compliance light (formal compliance framework mapping)
Architecture & Principles (Non-Negotiables)
Source: .specify/memory/constitution.md (v1.12.0)
Core Principles
- Inventory-first, Snapshots-second —
InventoryItem= last observed meta;PolicyVersion.snapshot= explicit immutable JSONB capture. - Read/Write Separation — Analysis is read-only; writes require preview → dry-run → confirmation → audit log.
- Single Contract Path to Graph — All Microsoft Graph calls via
GraphClientInterface(app/Services/Graph/GraphClientInterface.php); endpoints modeled in config/graph_contracts.php (867 lines, 28+ type definitions). - Deterministic Capabilities — Backup/restore/risk flags derived from config via
CoverageCapabilitiesResolver. - Workspace Isolation — Non-member → 404; workspace is primary session context. Enforced via
DenyNonMemberTenantAccessmiddleware +EnsureFilamentTenantSelected. - Tenant Isolation — Every read/write must be tenant-scoped;
DerivesWorkspaceIdFromTenantconcern auto-fillsworkspace_idfrom tenant. - Operator Surface Principles —
/admindefaults are operator-first, diagnostics are progressively disclosed, status dimensions stay distinct, mutation scope is explicit before execution, and every materially changed operator page carries an explicit page contract.
RBAC-UX Rules
| Rule | Description |
|---|---|
| UX-001 | Server-side is source of truth (UI is never security boundary) |
| UX-002 | Non-members get 404 (deny-as-not-found) |
| UX-003 | Members without capability get 403 |
| UX-004 | Actions visible-but-disabled for members lacking capability; hidden for non-members |
| UX-005 | All destructive actions require ->requiresConfirmation() |
| UX-006 | Capabilities from canonical registry only (app/Support/Auth/Capabilities.php) |
| UX-007 | Global search must be tenant-safe |
| UX-008 | RBAC regression tests mandatory |
Operations UX
- 3-surface feedback: Toast (immediate) → Progress widget (polling) → DB notification (terminal).
- OperationRun lifecycle: Service-owned transitions only via
OperationRunService— no direct status writes. - Idempotent creation: Hash-based dedup with partial unique index.
Filament Standards
- Action Surface Contract: List/View/Create/Edit pages each have defined required surfaces (Spec 082, 090).
- Layout: Main/Aside layout, sections required, view pages use Infolists.
- Badge Semantics: Centralized via
BadgeCatalog/BadgeRenderer(Specs 059, 060). - No naked forms: Everything in sections/cards with proper enterprise IA.
Provider Gateway
ProviderGateway(app/Services/Providers/ProviderGateway.php) — facade over Graph client.ProviderConnectionResolverresolves default connection per tenant+provider pair with validation (scope, status, credentials).ProviderOperationStartGate— pre-flight checks before any Graph write.IntuneRbacWriteGate(app/Services/Hardening/IntuneRbacWriteGate.php) — freshness check before destructive Graph writes.
Data Model Overview
Core Tables
| Table | Key Columns | Notes |
|---|---|---|
workspaces |
id, name, slug, archived_at |
Primary isolation boundary |
workspace_memberships |
workspace_id, user_id, role |
Roles: Owner/Manager/Operator/Readonly |
tenants |
id, workspace_id, tenant_id, external_id, name, status, is_current |
SoftDeletes; external_id = Azure tenant GUID |
tenant_memberships |
tenant_id, user_id, role, created_by_user_id |
UUID PK |
users |
Standard + entra_id, entra_name, is_platform_superadmin |
SoftDeletes |
platform_users |
Separate from users |
/system panel auth guard |
Policy & Versioning
| Table | Key Columns | Notes |
|---|---|---|
policies |
id, workspace_id, tenant_id, external_id, policy_type, display_name, odata_type, metadata (JSONB), ignored_at |
One row per Intune policy per tenant |
policy_versions |
id, workspace_id, tenant_id, policy_id, version_number, snapshot (JSONB), metadata (JSONB), assignments (JSONB), content_hash, captured_at, capture_purpose |
Immutable snapshots; SoftDeletes |
backup_sets |
id, workspace_id, tenant_id, name, status, metadata (JSONB) |
SoftDeletes |
backup_items |
id, workspace_id, backup_set_id, policy_id, policy_version_id |
Links backup set to specific version |
restore_runs |
id, workspace_id, tenant_id, backup_set_id, operation_run_id, status, is_dry_run, idempotency_key, requested_items (JSONB), preview (JSONB), results (JSONB), group_mapping (JSONB) |
SoftDeletes |
Inventory & Dependencies
| Table | Key Columns | Notes |
|---|---|---|
inventory_items |
id, workspace_id, tenant_id, policy_type, external_id, display_name, meta_jsonb (JSONB), last_seen_at, last_seen_operation_run_id |
Current state from Graph |
inventory_links |
id, workspace_id, source_type, source_id (text), target_type, target_id (text), relationship_type |
Dependency graph edges |
Operations
| Table | Key Columns | Notes |
|---|---|---|
operation_runs |
id, workspace_id, tenant_id, user_id, type, status (queued/running/completed), outcome (pending/succeeded/partially_succeeded/blocked/failed), summary_counts (JSONB), failure_summary (JSONB), context (JSONB), started_at, completed_at |
Central run tracking for all async operations |
audit_logs |
id, workspace_id, tenant_id, user_id, action, details (JSONB) |
Immutable audit trail |
Baselines
| Table | Key Columns | Notes |
|---|---|---|
baseline_profiles |
id, workspace_id, name, status (draft/active/archived), capture_mode, scope_jsonb (JSONB), active_snapshot_id |
Golden Master definition |
baseline_snapshots |
id, baseline_profile_id, operation_run_id, tenant_id, version_label, item_count |
Point-in-time snapshot |
baseline_snapshot_items |
id, baseline_snapshot_id, policy_type, external_id, display_name, content_hash, meta_jsonb (JSONB), subject_key |
Individual policy item in snapshot |
baseline_tenant_assignments |
id, baseline_profile_id, tenant_id |
Which tenants are compared against this baseline |
Findings
| Table | Key Columns | Notes |
|---|---|---|
findings |
id, workspace_id, tenant_id, finding_type (drift/permission_posture/entra_admin_roles), severity, status, source, title, description, evidence_jsonb (JSONB), recurrence_key, fingerprint, first_seen_at, last_seen_at, times_seen, sla_days, due_at, evidence_fidelity, baseline_operation_run_id, current_operation_run_id |
Full lifecycle: new → triaged → in_progress → resolved/closed/risk_accepted |
Alerts
| Table | Key Columns | Notes |
|---|---|---|
alert_destinations |
id, workspace_id, type (teams_webhook/email), config (JSONB) |
Where alerts go |
alert_rules |
id, workspace_id, event_type, is_enabled, tenant_scope_mode, tenant_allowlist (JSONB), cooldown_seconds, quiet_hours_* |
When to alert |
alert_rule_destinations |
alert_rule_id, alert_destination_id, workspace_id |
M:N join with workspace scope |
alert_deliveries |
id, workspace_id, alert_rule_id, tenant_id, status, attempts, payload (JSONB) |
Delivery tracking with retry |
Provider
| Table | Key Columns | Notes |
|---|---|---|
provider_connections |
id, workspace_id, tenant_id, provider, external_tenant_id, is_default, status, scopes_granted (JSONB), last_health_check_at |
Graph API connection per tenant |
provider_credentials |
id, provider_connection_id, credential_type, client_id, client_secret (encrypted) |
Secrets storage |
Other
| Table | Notes |
|---|---|
stored_reports |
Permission posture + Entra admin roles reports, fingerprint dedup |
review_packs |
Exportable audit artifact (ZIP), signed download URLs |
backup_schedules |
Cron-based backup scheduling with retention keep_last |
entra_groups |
Cached directory groups for assignment mapping |
entra_role_definitions |
Cached Entra role definitions for admin roles evidence |
settings_catalog_definitions / settings_catalog_categories |
Settings catalog human-readable name mapping |
workspace_settings / tenant_settings |
Key-value settings with typed slices |
Operations & Observability
OperationRun System
- Central model:
OperationRun(app/Models/OperationRun.php) - Type registry:
OperationRunTypeenum (25+ types) (app/Support/OperationRunType.php) - Catalog:
OperationCatalog— labels, expected durations, allowed summary keys (app/Support/OperationCatalog.php) - Lifecycle service:
OperationRunService(877 lines) — idempotentensureRun(), stale detection, state transitions, summary normalization (app/Services/OperationRunService.php) - Status:
queued→running→completed - Outcome:
pending|succeeded|partially_succeeded|blocked|failed|cancelled(reserved) - Context: JSONB bag storing
selection_hash,policy_types,categories, etc.
Monitoring UI
- Operations page:
/admin/operations— workspace-scoped, tenantless canonical view (app/Filament/Pages/Monitoring/Operations.php) - Run viewer:
/admin/operations/{run}— detail view per run (app/Filament/Pages/Operations/TenantlessOperationRunViewer.php) - Audit log:
/admin/audit-log— immutable event log (app/Filament/Pages/Monitoring/AuditLog.php)
Scheduled Tasks (routes/console.php)
| Schedule | Frequency | Job |
|---|---|---|
| Backup schedule dispatch | Every minute | tenantpilot:schedules:dispatch |
| Directory groups dispatch | Every minute | tenantpilot:directory-groups:dispatch |
| Alerts dispatch | Every minute (no overlap) | tenantpilot:alerts:dispatch |
| Prune old operations | Daily | PruneOldOperationRunsJob |
| Reconcile adapter runs | Every 30 min | ReconcileAdapterRunsJob |
| Stored reports prune | Daily | stored-reports:prune |
| Review pack prune | Daily | tenantpilot:review-pack:prune |
| Baseline evidence prune | Daily | tenantpilot:baseline-evidence:prune |
| Entra admin roles scan | Daily (per tenant) | ScanEntraAdminRolesJob |
Security: Tenancy / RBAC / Auth
Three-panel architecture
| Panel | Path | Guard | Purpose |
|---|---|---|---|
| Admin | /admin |
web |
Workspace + Tenant management (main UI) |
| Tenant | /admin/t |
web |
Tenant-scoped views (within admin session) |
| System | /system |
platform |
Platform operator console (separate cookies) |
Auth flows
- Entra OIDC:
/auth/entra/redirect→/auth/entra/callback(via Socialite) - Admin consent:
/admin/consent/start→/admin/consent/callback(app registration) - RBAC delegated auth:
/admin/rbac/start→/admin/rbac/callback - Break-glass:
/systempanel withBreakGlassRecoverypage, config-gated TTL
Role hierarchy
| Level | Roles | Source |
|---|---|---|
| Workspace | Owner / Manager / Operator / Readonly | app/Support/Auth/WorkspaceRole.php |
| Tenant | Owner / Manager / Operator / Readonly | app/Support/TenantRole.php |
| Platform | Capabilities: access_system_panel, use_break_glass, console.*, ops.*, runbooks.* |
app/Support/Auth/PlatformCapabilities.php |
Capability resolver chain
Capabilities::all()— reflection-based registry of ~40+ tenant/workspace capabilitiesUiEnforcement(app/Support/Rbac/UiEnforcement.php) — builder pattern for Filament actions:forAction()->requireMembership()->requireCapability()->destructive()WorkspaceUiEnforcement— mirror for workspace-scoped actions- 12 Laravel Policy classes in app/Policies/
Workspace isolation enforcement
DerivesWorkspaceIdFromTenantconcern on models — auto-fillsworkspace_id- DB-level:
NOT NULL+ FK constraints onworkspace_idfor all tenant-owned tables (migration2026_02_14_220114) - Check constraints on
audit_logs(migration2026_02_14_220117) - Middleware:
ensure-workspace-selected,ensure-workspace-member,DenyNonMemberTenantAccess
Drift/Baseline/Findings: Current State & Gaps
Baseline Drift Engine (Specs 116–119, all merged to dev)
| Spec | Title | Status |
|---|---|---|
| 116 | Meta-fidelity + coverage guard | ✅ Merged |
| 117 | Content-fidelity via provider chain (v1.5) | ✅ Merged |
| 118 | Full content capture (v2) | ✅ Merged |
| 119 | Drift cutover — legacy drift removal | ✅ Merged |
Architecture
- Capture:
CaptureBaselineSnapshotJob→BaselineCaptureServicecaptures inventory metadata + optional full content evidence intobaseline_snapshot_items. - Compare:
CompareBaselineToTenantJob→BaselineCompareServicecompares snapshot against current tenant state.CurrentStateEvidenceProviderchain resolves best available evidence (content > meta). - Findings: Creates/updates/auto-resolves
Findingrecords withsource = 'baseline.compare',finding_type = 'drift'. - Evidence fidelity:
evidence_fidelityfield on findings tracks whether comparison was meta-level or content-level. - Guard test: tests/Feature/Guards/Spec116OneEngineGuardTest.php, tests/Feature/Guards/Spec118NoLegacyBaselineDriftGuardTest.php — prevent regression to legacy drift patterns.
Findings lifecycle
- Statuses:
new→acknowledged→triaged→in_progress→resolved/closed/risk_accepted→reopened - SLA:
FindingSlaPolicycomputesdue_atfrom workspacefindings.sla_dayssetting - Auto-resolve:
BaselineAutoCloseServiceresolves findings when drift disappears - Recurrence:
recurrence_key+times_seen+first_seen_at/last_seen_attrack repeating drift
Gaps
- Exception entity: No formal
FindingExceptionmodel — only therisk_acceptedstatus exists - Evidence pack: No formal evidence export spec as a standalone artifact (review pack covers partial ground)
- Cross-tenant compare: Spec 043 drafted, not implemented — uses
policy_type + normalized display_namematching
Testing & Tooling
Test suite
- Framework: Pest 4 with
RefreshDatabase - DB: SQLite
:memory:(via phpunit.xml) - Test files: 582 Feature + 125 Unit + 1 Deprecation = 708 total
- Infrastructure: tests/Pest.php (366 lines) — shared helpers:
createUserWithTenant(),fakeIdToken(),bindFailHardGraphClient(), etc.
Key test categories
| Category | Path | Count (approx) |
|---|---|---|
| Guard tests | tests/Feature/Guards/ |
14 files — enforce architectural rules via code scanning |
| RBAC tests | tests/Feature/Rbac/, tests/Feature/TenantRBAC/ |
~15 files |
| Baseline/Drift | tests/Feature/BaselineDriftEngine/, tests/Feature/Baselines/, tests/Feature/Drift/ |
~20 files |
| Operations | tests/Feature/Operations/, tests/Feature/Monitoring/ |
~10 files |
| Bulk operations | tests/Feature/Bulk*Test.php |
~20 files |
| Restore | tests/Feature/Restore*Test.php |
~15 files |
| Provider/Graph | tests/Feature/ProviderConnections/, tests/Feature/Graph/, tests/Unit/Graph* |
~15 files |
| Alerts | tests/Feature/Alerts/, tests/Unit/Alerts/ |
~10 files |
| Inventory | tests/Feature/Inventory/, tests/Unit/Inventory/ |
~15 files |
| Workspace isolation | tests/Feature/WorkspaceIsolation/ |
~5 files |
| Deprecation | tests/Deprecation/ |
1 file (is_platform_superadmin ban) |
Guard tests (architectural enforcement)
These tests scan source code to prevent regressions:
| Test | Enforces |
|---|---|
ActionSurfaceContractTest |
All resources have required action surfaces |
NoAdHocFilamentAuthPatternsTest |
No ad-hoc auth patterns in Filament |
NoAdHocStatusBadgesTest |
Use badge catalog, not ad-hoc badges |
NoLegacyRunsTest |
No legacy run patterns in codebase |
NoLegacyTenantGraphOptionsTest |
No legacy graph options usage |
NoTenantCredentialRuntimeReadsSpec081Test |
No runtime credential reads |
Spec116OneEngineGuardTest |
Single drift engine architecture |
Spec118NoLegacyBaselineDriftGuardTest |
No legacy baseline drift code |
Tooling
| Tool | Command | Notes |
|---|---|---|
| Run all tests | vendor/bin/sail artisan test --compact |
|
| Run specific file | vendor/bin/sail artisan test --compact tests/Feature/SomeTest.php |
|
| Filter test | vendor/bin/sail artisan test --compact --filter=testName |
|
| Pint formatter | vendor/bin/sail bin pint --dirty |
Default Laravel preset |
No .pint.json |
Uses default rules | |
| No PHPStan config found | UNKNOWN — check if integrated |
Deployment & Runbooks
Local development
# Start services
vendor/bin/sail up -d
# Run migrations
vendor/bin/sail artisan migrate
# Start queue worker (or use docker-compose queue service)
vendor/bin/sail artisan queue:work --tries=3 --timeout=300
# Build frontend assets
vendor/bin/sail npm run build
# Run tests
vendor/bin/sail artisan test --compact
Docker services (docker-compose.yml)
| Service | Image | Ports |
|---|---|---|
laravel.test |
PHP 8.4 Sail | 80, 5173 (Vite) |
queue |
Same image | queue:work --tries=3 --timeout=300 --sleep=3 --max-jobs=1000 |
pgsql |
PostgreSQL 16 | 5432 |
redis |
Redis 7 Alpine | 6379 |
Deployment: Dokploy (staging → production)
- No Dokploy config in repo — configuration lives on the VPS per Agents.md
- Two environments: Staging (mandatory gate) → Production
- Required env vars (partial — no
.env.example):- Standard Laravel:
APP_KEY,DB_*,REDIS_*,QUEUE_CONNECTION - Entra auth:
AZURE_AD_CLIENT_ID,AZURE_AD_CLIENT_SECRET,AZURE_AD_TENANT_ID - Feature flags:
BREAK_GLASS_ENABLED,ALLOW_ADMIN_MAINTENANCE_ACTIONS - Alerts:
TENANTPILOT_ALERTS_ENABLED - Baselines:
TENANTPILOT_BASELINE_FULL_CONTENT_CAPTURE_ENABLED - Write gate:
TENANTPILOT_INTUNE_WRITE_GATE_ENABLED
- Standard Laravel:
- Deploy checklist:
php artisan migrate,php artisan filament:assets, queue restart
Platform runbooks
FindingsLifecycleBackfillRunbookService(app/Services/Runbooks/FindingsLifecycleBackfillRunbookService.php) — safe backfill of findings lifecycle fields- Accessible at
/system/ops/runbookswith platform capabilities
Risks & Gaps (Top 10)
| # | Risk | Location | Impact | Suggested Action |
|---|---|---|---|---|
| 1 | No .env.example |
Repo root | New dev can't set up without tribal knowledge | Create .env.example with all required keys from config/*.php |
| 2 | No CI pipeline config in repo | Missing | No automated test gating on PRs | Add Gitea CI / GitHub Actions config |
| 3 | No PHPStan/Larastan | Missing | Type-safety gaps undetected | UNKNOWN — check if integrated; if not, add baseline |
| 4 | Exception/risk-acceptance workflow incomplete | Finding.STATUS_RISK_ACCEPTED exists, no exception entity |
Can't track formal risk acceptances with evidence | Spec needed for FindingException model with approval chain |
| 5 | SQLite for tests vs PostgreSQL in prod | phpunit.xml | JSONB, GIN indexes, PG-specific features untested | Add phpunit.pgsql.xml — file exists but may not be in CI |
| 6 | No formal release process | Roadmap only in brainstorming doc | No versioned release artifacts, no changelog | Define release cadence and changelog automation |
| 7 | Dokploy config external | Per Agents.md | Deployment not reproducible from repo alone | Document or codify Dokploy config |
| 8 | Cross-tenant compare unimplemented | Spec 043 draft only | Key MSP feature missing | Prioritize for next sprint if MSP use case is active |
| 9 | Policy lifecycle / ghost policies | Spec 900 draft only | Deleted-in-Intune policies persist indefinitely | Implement orphan detection + soft-archive flow |
| 10 | Break-glass recovery surface | Config-gated, TTL-limited | Limited testing evidence | Verify break-glass flow has smoke tests in test suite |
Next Actions (Top 5)
-
Create
.env.example— Document all required env vars fromconfig/*.phpfiles. Low effort, high impact for onboarding. -
Add CI pipeline — Gitea / runner config for
vendor/bin/sail artisan test --compact+vendor/bin/sail bin pint --teston every PR. Critical for sustained quality. -
Formal exception/risk-acceptance spec — Design
FindingExceptionentity with approval chain, evidence attachment, and UI workflow. Blocks R2 "Evidence Packs" roadmap item. -
PostgreSQL test mode — Enable phpunit.pgsql.xml in CI for a subset of tests that exercise JSONB/GIN-specific behavior.
-
Cross-tenant compare implementation (Spec 043) — Build read-only compare view first (N=2 tenants). This is the first step toward the MSP portfolio use case.
Appendix
Specs Index (109 specs)
| # | Slug | Status |
|---|---|---|
| 001 | rbac-onboarding | ✅ Implemented |
| 003 | settings-catalog-readable | ✅ Implemented |
| 004 | assignments-scope-tags | ✅ Implemented |
| 005 | bulk-operations | ✅ Implemented |
| 006 | sot-foundations-assignments | ✅ Implemented |
| 007 | device-config-compliance | ✅ Implemented |
| 008 | apps-app-management | ✅ Implemented |
| 009 | app-protection-policy | ✅ Implemented |
| 011 | restore-run-wizard | ✅ Implemented |
| 012 | windows-update-rings | ✅ Implemented |
| 013 | scripts-management | ✅ Implemented |
| 014 | enrollment-autopilot | ✅ Implemented |
| 017 | policy-types-mam-endpoint-security-baselines | ✅ Implemented |
| 018 | driver-updates-wufb | ✅ Implemented |
| 023 | endpoint-security-restore | ✅ Implemented |
| 024 | terms-and-conditions | ✅ Implemented |
| 026 | custom-compliance-scripts | ✅ Implemented |
| 027 | enrollment-config-subtypes | ✅ Implemented |
| 028 | device-categories | ✅ Implemented |
| 029 | wip-policies | ✅ Implemented |
| 030 | intune-rbac-backup | ✅ Implemented |
| 031 | tenant-portfolio-context-switch | ✅ Implemented |
| 039 | inventory-program | ✅ Meta-spec (040–044) |
| 040 | inventory-core | ✅ Implemented |
| 041 | inventory-ui | ✅ Implemented |
| 043 | cross-tenant-compare-and-promotion | 📋 Draft |
| 045 | settingscatalog-classification | ✅ Implemented |
| 049 | backup-restore-job-orchestration | ✅ Implemented |
| 051 | entra-group-directory-cache | ✅ Implemented |
| 052 | async-add-policies | ✅ Implemented |
| 053 | unify-runs-monitoring | ✅ Implemented |
| 054 | unify-runs-suitewide | ✅ Implemented |
| 055 | ops-ux-rollout | ✅ Implemented |
| 058 | tenant-ui-polish | ✅ Implemented |
| 059 | unified-badges | ✅ Implemented |
| 060 | tag-badge-catalog | ✅ Implemented |
| 061 | provider-foundation | ✅ Implemented |
| 062 | tenant-rbac-v1 | ✅ Implemented |
| 063 | entra-signin | ✅ Implemented |
| 064 | auth-structure | ✅ Implemented |
| 065 | tenant-rbac-v1 | ✅ Implemented |
| 070 | workspace-create-membership-fix | ✅ Implemented |
| 071 | tenant-selection-workspace-scope | ✅ Implemented |
| 072 | managed-tenants-workspace-enforcement | ✅ Implemented |
| 074 | verification-checklist | ✅ Implemented |
| 077 | workspace-nav-monitoring-hub | ✅ Implemented |
| 078 | operations-tenantless-canonical | ✅ Implemented |
| 079 | inventory-links-non-uuid-ids | ✅ Implemented |
| 081 | provider-connection-cutover | ✅ Implemented |
| 084 | verification-surfaces-unification | ✅ Implemented |
| 085 | tenant-operate-hub | ✅ Implemented |
| 086 | retire-legacy-runs-into-operation-runs | ✅ Implemented |
| 087 | legacy-runs-removal | ✅ Implemented |
| 088 | remove-tenant-graphoptions-legacy | ✅ Implemented |
| 089 | provider-connections-tenantless-ui | ✅ Implemented |
| 091 | backupschedule-retention-lifecycle | ✅ Implemented |
| 092 | legacy-purge-final | ✅ Implemented |
| 093 | scope-001-workspace-id-isolation | ✅ Implemented |
| 094 | assignment-ops-observability-hardening | ✅ Implemented |
| 095 | graph-contracts-registry-completeness | ✅ Implemented |
| 096 | ops-polish-assignment-dedupe-system-tracking | ✅ Implemented |
| 097 | settings-foundation | ✅ Implemented |
| 098 | settings-slices-v1-backup-drift-ops | ✅ Implemented |
| 099 | alerts-v1-teams-email | ✅ Implemented |
| 100 | alert-target-test-actions | ✅ Implemented |
| 101 | golden-master-baseline-governance-v1 | ✅ Implemented |
| 102 | filament-5-2-1-upgrade | ✅ Implemented |
| 103 | ia-scope-filter-semantics | ✅ Implemented |
| 104 | provider-permission-posture | ✅ Implemented |
| 105 | entra-admin-roles-evidence-findings | ✅ Implemented |
| 106 | required-permissions-sidebar-context | ✅ Implemented |
| 107 | workspace-chooser | ✅ Implemented |
| 108 | provider-access-hardening | ✅ Implemented |
| 109 | review-pack-export | ✅ Implemented |
| 110 | ops-ux-enforcement | ✅ Implemented |
| 111 | findings-workflow-sla | ✅ Implemented |
| 112 | list-expand-parity | ✅ Implemented |
| 113 | platform-ops-runbooks | ✅ Implemented |
| 114 | system-console-control-tower | ✅ Implemented |
| 115 | baseline-operability-alerts | ✅ Implemented |
| 116 | baseline-drift-engine (meta v1) | ✅ Implemented |
| 117 | baseline-drift-engine (content v1.5) | ✅ Implemented |
| 118 | baseline-drift-engine (full capture v2) | ✅ Implemented |
| 119 | baseline-drift-engine (cutover) | ✅ Implemented |
| 700 | bugfix | ✅ Implemented |
| 900 | policy-lifecycle | 📋 Draft |
| 0800 | future-features (brainstorming) | 📋 Brainstorming |
Specs not listed above (e.g., 002, 010, 015, 016, 025, 032, 042, 044, 046–048, 056–057, 066–067, 073, 075–076, 080, 082–083, 090, 999) have spec directories but were not individually verified. Based on merge history and code presence, the vast majority are implemented.
Key File Map (Top 20 files to read first)
| File | Why |
|---|---|
| .specify/memory/constitution.md | Non-negotiable architectural rules |
| Agents.md | Agent workflow, branching, environment |
| config/tenantpilot.php | 28+ policy types, feature flags, all config |
| config/graph_contracts.php | Graph API contract registry (867 lines) |
| app/Support/Auth/Capabilities.php | Canonical RBAC capability registry |
| app/Support/OperationRunType.php | All operation run types |
| app/Support/OperationCatalog.php | Labels, durations, summary keys |
| app/Services/OperationRunService.php | Central run lifecycle (877 lines) |
| app/Support/Rbac/UiEnforcement.php | RBAC UI enforcement builder (678 lines) |
| app/Services/Providers/ProviderGateway.php | Graph API facade |
| app/Services/Providers/ProviderConnectionResolver.php | Connection resolution + validation |
| app/Services/Baselines/BaselineCompareService.php | Drift comparison engine |
| app/Services/Baselines/BaselineCaptureService.php | Snapshot capture |
| app/Models/Finding.php | Finding lifecycle model |
| app/Models/OperationRun.php | Central run model |
| app/Models/Tenant.php | Tenant model (320 lines) |
| routes/web.php | All routes (3 panels, auth, workspace) |
| routes/console.php | Scheduler definitions |
| tests/Pest.php | Test helpers & infrastructure |
| specs/0800-future-features/brainstorming.md | Future roadmap ideas |
Key Commands
# Local dev
vendor/bin/sail up -d # Start all services
vendor/bin/sail artisan migrate # Run migrations
vendor/bin/sail artisan queue:work --tries=3 # Process queue
vendor/bin/sail npm run build # Build frontend
# Testing
vendor/bin/sail artisan test --compact # Full suite
vendor/bin/sail artisan test --compact --filter=testName # Single test
vendor/bin/sail artisan test --compact tests/Feature/Guards/ # Guard tests only
# Code quality
vendor/bin/sail bin pint --dirty # Format changed files
# Deployment
vendor/bin/sail artisan filament:assets # Publish Filament assets
vendor/bin/sail artisan migrate --force # Production migration
Quickstart (exact steps)
- Clone repo,
cd TenantAtlas cp .env.example .env(MISSING — create from config files or request from team)composer installvendor/bin/sail up -dvendor/bin/sail artisan key:generatevendor/bin/sail artisan migratevendor/bin/sail npm install && vendor/bin/sail npm run buildvendor/bin/sail artisan filament:assets- Login: Navigate to
/admin— redirects to workspace chooser. For first-time setup, create a workspace and onboard a tenant via the wizard. - System console: Navigate to
/system— requiresplatform_usersrecord withplatformguard.