Compare commits
2 Commits
180-tenant
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| ce0615a9c1 | |||
| 6f8eb28ca2 |
@ -1,7 +1,9 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
apps/platform/node_modules/
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
vendor/
|
vendor/
|
||||||
|
apps/platform/vendor/
|
||||||
coverage/
|
coverage/
|
||||||
.git/
|
.git/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@ -18,12 +20,19 @@ Dockerfile*
|
|||||||
*.tmp
|
*.tmp
|
||||||
*.swp
|
*.swp
|
||||||
public/build/
|
public/build/
|
||||||
|
apps/platform/public/build/
|
||||||
public/hot/
|
public/hot/
|
||||||
|
apps/platform/public/hot/
|
||||||
public/storage/
|
public/storage/
|
||||||
|
apps/platform/public/storage/
|
||||||
storage/framework/
|
storage/framework/
|
||||||
|
apps/platform/storage/framework/
|
||||||
storage/logs/
|
storage/logs/
|
||||||
|
apps/platform/storage/logs/
|
||||||
storage/debugbar/
|
storage/debugbar/
|
||||||
|
apps/platform/storage/debugbar/
|
||||||
storage/*.key
|
storage/*.key
|
||||||
|
apps/platform/storage/*.key
|
||||||
/references/
|
/references/
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
14
.github/agents/copilot-instructions.md
vendored
14
.github/agents/copilot-instructions.md
vendored
@ -2,6 +2,12 @@ # TenantAtlas Development Guidelines
|
|||||||
|
|
||||||
Auto-generated from all feature plans. Last updated: 2025-12-22
|
Auto-generated from all feature plans. Last updated: 2025-12-22
|
||||||
|
|
||||||
|
## Relocation override
|
||||||
|
- The authoritative Laravel application root is `apps/platform`.
|
||||||
|
- Human-facing commands should use `cd apps/platform && ...`.
|
||||||
|
- Repo-root tooling may delegate via `./scripts/platform-sail` when it cannot set a nested working directory.
|
||||||
|
- If any generated technology note below conflicts with the current repo, trust `apps/platform/composer.json`, `apps/platform/package.json`, and the live Laravel application metadata over stale generated entries.
|
||||||
|
|
||||||
## Active Technologies
|
## Active Technologies
|
||||||
- PHP 8.4.15 + Laravel 12, Filament v4, Livewire v3 (feat/005-bulk-operations)
|
- PHP 8.4.15 + Laravel 12, Filament v4, Livewire v3 (feat/005-bulk-operations)
|
||||||
- PostgreSQL (app), SQLite in-memory (tests) (feat/005-bulk-operations)
|
- PostgreSQL (app), SQLite in-memory (tests) (feat/005-bulk-operations)
|
||||||
@ -139,6 +145,10 @@ ## Active Technologies
|
|||||||
- PostgreSQL with existing `restore_runs` and `operation_runs` records plus JSON or array-backed `metadata`, `preview`, `results`, and `context`; no schema change planned (181-restore-safety-integrity)
|
- PostgreSQL with existing `restore_runs` and `operation_runs` records plus JSON or array-backed `metadata`, `preview`, `results`, and `context`; no schema change planned (181-restore-safety-integrity)
|
||||||
- PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4 + Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `BackupSetResource`, `BackupItemsRelationManager`, `PolicyVersionResource`, `RestoreRunResource`, `CreateRestoreRun`, `AssignmentBackupService`, `VersionService`, `PolicySnapshotService`, `RestoreRiskChecker`, `BadgeRenderer`, `PolicySnapshotModeBadge`, `EnterpriseDetailBuilder`, and existing RBAC helpers (176-backup-quality-truth)
|
- PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4 + Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `BackupSetResource`, `BackupItemsRelationManager`, `PolicyVersionResource`, `RestoreRunResource`, `CreateRestoreRun`, `AssignmentBackupService`, `VersionService`, `PolicySnapshotService`, `RestoreRiskChecker`, `BadgeRenderer`, `PolicySnapshotModeBadge`, `EnterpriseDetailBuilder`, and existing RBAC helpers (176-backup-quality-truth)
|
||||||
- PostgreSQL with existing tenant-owned `backup_sets`, `backup_items`, `policy_versions`, and restore wizard input state; JSON-backed `metadata`, `snapshot`, `assignments`, and `scope_tags`; no schema change planned (176-backup-quality-truth)
|
- PostgreSQL with existing tenant-owned `backup_sets`, `backup_items`, `policy_versions`, and restore wizard input state; JSON-backed `metadata`, `snapshot`, `assignments`, and `scope_tags`; no schema change planned (176-backup-quality-truth)
|
||||||
|
- PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4 + Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `DashboardKpis`, `NeedsAttention`, `BackupSetResource`, `BackupScheduleResource`, `BackupQualityResolver`, `BackupQualitySummary`, `ScheduleTimeService`, shared badge infrastructure, and existing RBAC helpers (180-tenant-backup-health)
|
||||||
|
- PostgreSQL with existing tenant-owned `backup_sets`, `backup_items`, and `backup_schedules` records plus existing JSON-backed backup metadata; no schema change planned (180-tenant-backup-health)
|
||||||
|
- PHP 8.4.15, Laravel 12, Blade, Livewire v4, Filament v5.2.x, Tailwind CSS v4, Vite 7 + `laravel/framework`, `filament/filament`, `livewire/livewire`, `laravel/sail`, `laravel-vite-plugin`, `tailwindcss`, `vite`, `pestphp/pest`, `drizzle-kit`, PostgreSQL, Redis, Docker Compose (182-platform-relocation)
|
||||||
|
- PostgreSQL, Redis, filesystem storage under the Laravel app `storage/` tree, plus existing Vite build artifacts in `public/build`; no new database persistence planned (182-platform-relocation)
|
||||||
|
|
||||||
- PHP 8.4.15 (feat/005-bulk-operations)
|
- PHP 8.4.15 (feat/005-bulk-operations)
|
||||||
|
|
||||||
@ -158,8 +168,8 @@ ## Code Style
|
|||||||
PHP 8.4.15: Follow standard conventions
|
PHP 8.4.15: Follow standard conventions
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
- 182-platform-relocation: Added PHP 8.4.15, Laravel 12, Blade, Livewire v4, Filament v5.2.x, Tailwind CSS v4, Vite 7 + `laravel/framework`, `filament/filament`, `livewire/livewire`, `laravel/sail`, `laravel-vite-plugin`, `tailwindcss`, `vite`, `pestphp/pest`, `drizzle-kit`, PostgreSQL, Redis, Docker Compose
|
||||||
|
- 180-tenant-backup-health: Added PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4 + Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `DashboardKpis`, `NeedsAttention`, `BackupSetResource`, `BackupScheduleResource`, `BackupQualityResolver`, `BackupQualitySummary`, `ScheduleTimeService`, shared badge infrastructure, and existing RBAC helpers
|
||||||
- 176-backup-quality-truth: Added PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4 + Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `BackupSetResource`, `BackupItemsRelationManager`, `PolicyVersionResource`, `RestoreRunResource`, `CreateRestoreRun`, `AssignmentBackupService`, `VersionService`, `PolicySnapshotService`, `RestoreRiskChecker`, `BadgeRenderer`, `PolicySnapshotModeBadge`, `EnterpriseDetailBuilder`, and existing RBAC helpers
|
- 176-backup-quality-truth: Added PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4 + Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `BackupSetResource`, `BackupItemsRelationManager`, `PolicyVersionResource`, `RestoreRunResource`, `CreateRestoreRun`, `AssignmentBackupService`, `VersionService`, `PolicySnapshotService`, `RestoreRiskChecker`, `BadgeRenderer`, `PolicySnapshotModeBadge`, `EnterpriseDetailBuilder`, and existing RBAC helpers
|
||||||
- 181-restore-safety-integrity: Added PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4 + Filament v5, Livewire v4, Pest v4, Laravel Sail, existing `RestoreRunResource`, `RestoreService`, `RestoreRiskChecker`, `RestoreDiffGenerator`, `OperationRunResource`, `TenantlessOperationRunViewer`, shared badge infrastructure, and existing RBAC or write-gate helpers
|
|
||||||
- 178-ops-truth-alignment: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, existing `OperationRun`, `OperationLifecyclePolicy`, `OperationRunFreshnessState`, `OperationUxPresenter`, `OperationRunLinks`, `ActiveRuns`, `StuckRunClassifier`, `WorkspaceOverviewBuilder`, dashboard widgets, workspace widgets, and system ops pages
|
|
||||||
<!-- MANUAL ADDITIONS START -->
|
<!-- MANUAL ADDITIONS START -->
|
||||||
<!-- MANUAL ADDITIONS END -->
|
<!-- MANUAL ADDITIONS END -->
|
||||||
|
|||||||
49
.github/copilot-instructions.md
vendored
49
.github/copilot-instructions.md
vendored
@ -40,7 +40,7 @@ ## 3) Panel setup defaults
|
|||||||
- Assets policy:
|
- Assets policy:
|
||||||
- Panel-only assets: register via panel config.
|
- Panel-only assets: register via panel config.
|
||||||
- Shared/plugin assets: register via `FilamentAsset::register()`.
|
- Shared/plugin assets: register via `FilamentAsset::register()`.
|
||||||
- Deployment must include `php artisan filament:assets`.
|
- Deployment must include `cd apps/platform && php artisan filament:assets`.
|
||||||
|
|
||||||
Sources:
|
Sources:
|
||||||
- https://filamentphp.com/docs/5.x/panel-configuration
|
- https://filamentphp.com/docs/5.x/panel-configuration
|
||||||
@ -254,7 +254,7 @@ ## Testing
|
|||||||
- Source: https://filamentphp.com/docs/5.x/testing/testing-actions — “Testing actions”
|
- Source: https://filamentphp.com/docs/5.x/testing/testing-actions — “Testing actions”
|
||||||
|
|
||||||
## Deployment / Ops
|
## Deployment / Ops
|
||||||
- [ ] `php artisan filament:assets` is included in the deployment process when using registered assets.
|
- [ ] `cd apps/platform && php artisan filament:assets` is included in the deployment process when using registered assets.
|
||||||
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
|
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
|
||||||
|
|
||||||
=== foundation rules ===
|
=== foundation rules ===
|
||||||
@ -292,7 +292,7 @@ ## Application Structure & Architecture
|
|||||||
- Do not change the application's dependencies without approval.
|
- Do not change the application's dependencies without approval.
|
||||||
|
|
||||||
## Frontend Bundling
|
## Frontend Bundling
|
||||||
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them.
|
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `cd apps/platform && ./vendor/bin/sail npm run build`, `cd apps/platform && ./vendor/bin/sail npm run dev`, or `cd apps/platform && ./vendor/bin/sail composer run dev`. Ask them.
|
||||||
|
|
||||||
## Replies
|
## Replies
|
||||||
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
|
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
|
||||||
@ -372,28 +372,29 @@ ## Enums
|
|||||||
## Laravel Sail
|
## Laravel Sail
|
||||||
|
|
||||||
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
|
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
|
||||||
- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`.
|
- The canonical application working directory is `apps/platform`. Repo-root launchers such as MCP or VS Code tasks may use `./scripts/platform-sail`, but that helper is compatibility-only.
|
||||||
- Open the application in the browser by running `vendor/bin/sail open`.
|
- Start services using `cd apps/platform && ./vendor/bin/sail up -d` and stop them with `cd apps/platform && ./vendor/bin/sail stop`.
|
||||||
- Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples:
|
- Open the application in the browser by running `cd apps/platform && ./vendor/bin/sail open`.
|
||||||
- Run Artisan Commands: `vendor/bin/sail artisan migrate`
|
- Always prefix PHP, Artisan, Composer, and Node commands with `cd apps/platform && ./vendor/bin/sail`. Examples:
|
||||||
- Install Composer packages: `vendor/bin/sail composer install`
|
- Run Artisan Commands: `cd apps/platform && ./vendor/bin/sail artisan migrate`
|
||||||
- Execute Node commands: `vendor/bin/sail npm run dev`
|
- Install Composer packages: `cd apps/platform && ./vendor/bin/sail composer install`
|
||||||
- Execute PHP scripts: `vendor/bin/sail php [script]`
|
- Execute Node commands: `cd apps/platform && ./vendor/bin/sail npm run dev`
|
||||||
- View all available Sail commands by running `vendor/bin/sail` without arguments.
|
- Execute PHP scripts: `cd apps/platform && ./vendor/bin/sail php [script]`
|
||||||
|
- View all available Sail commands by running `cd apps/platform && ./vendor/bin/sail` without arguments.
|
||||||
|
|
||||||
=== tests rules ===
|
=== tests rules ===
|
||||||
|
|
||||||
## Test Enforcement
|
## Test Enforcement
|
||||||
|
|
||||||
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
||||||
- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter.
|
- Run the minimum number of tests needed to ensure code quality and speed. Use `cd apps/platform && ./vendor/bin/sail artisan test --compact` with a specific filename or filter.
|
||||||
|
|
||||||
=== laravel/core rules ===
|
=== laravel/core rules ===
|
||||||
|
|
||||||
## Do Things the Laravel Way
|
## Do Things the Laravel Way
|
||||||
|
|
||||||
- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
- Use `cd apps/platform && ./vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
||||||
- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`.
|
- If you're creating a generic PHP class, use `cd apps/platform && ./vendor/bin/sail artisan make:class`.
|
||||||
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
||||||
|
|
||||||
### Database
|
### Database
|
||||||
@ -404,7 +405,7 @@ ### Database
|
|||||||
- Use Laravel's query builder for very complex database operations.
|
- Use Laravel's query builder for very complex database operations.
|
||||||
|
|
||||||
### Model Creation
|
### Model Creation
|
||||||
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`.
|
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `cd apps/platform && ./vendor/bin/sail artisan make:model`.
|
||||||
|
|
||||||
### APIs & Eloquent Resources
|
### APIs & Eloquent Resources
|
||||||
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
|
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
|
||||||
@ -428,10 +429,10 @@ ### Configuration
|
|||||||
### Testing
|
### Testing
|
||||||
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
||||||
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
||||||
- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
- When creating tests, make use of `cd apps/platform && ./vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
||||||
|
|
||||||
### Vite Error
|
### Vite Error
|
||||||
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`.
|
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `cd apps/platform && ./vendor/bin/sail npm run build` or ask the user to run `cd apps/platform && ./vendor/bin/sail npm run dev` or `cd apps/platform && ./vendor/bin/sail composer run dev`.
|
||||||
|
|
||||||
=== laravel/v12 rules ===
|
=== laravel/v12 rules ===
|
||||||
|
|
||||||
@ -460,7 +461,7 @@ ### Models
|
|||||||
## Livewire
|
## Livewire
|
||||||
|
|
||||||
- Use the `search-docs` tool to find exact version-specific documentation for how to write Livewire and Livewire tests.
|
- Use the `search-docs` tool to find exact version-specific documentation for how to write Livewire and Livewire tests.
|
||||||
- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` Artisan command to create new components.
|
- Use the `cd apps/platform && ./vendor/bin/sail artisan make:livewire [Posts\CreatePost]` Artisan command to create new components.
|
||||||
- State should live on the server, with the UI reflecting it.
|
- State should live on the server, with the UI reflecting it.
|
||||||
- All Livewire requests hit the Laravel backend; they're like regular HTTP requests. Always validate form data and run authorization checks in Livewire actions.
|
- All Livewire requests hit the Laravel backend; they're like regular HTTP requests. Always validate form data and run authorization checks in Livewire actions.
|
||||||
|
|
||||||
@ -504,8 +505,8 @@ ## Testing Livewire
|
|||||||
|
|
||||||
## Laravel Pint Code Formatter
|
## Laravel Pint Code Formatter
|
||||||
|
|
||||||
- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
|
- You must run `cd apps/platform && ./vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
|
||||||
- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues.
|
- Do not run `cd apps/platform && ./vendor/bin/sail bin pint --test`, simply run `cd apps/platform && ./vendor/bin/sail bin pint` to fix any formatting issues.
|
||||||
|
|
||||||
=== pest/core rules ===
|
=== pest/core rules ===
|
||||||
|
|
||||||
@ -514,7 +515,7 @@ ### Testing
|
|||||||
- If you need to verify a feature is working, write or update a Unit / Feature test.
|
- If you need to verify a feature is working, write or update a Unit / Feature test.
|
||||||
|
|
||||||
### Pest Tests
|
### Pest Tests
|
||||||
- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`.
|
- All tests must be written using Pest. Use `cd apps/platform && ./vendor/bin/sail artisan make:test --pest {name}`.
|
||||||
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
|
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
|
||||||
- Tests should test all of the happy paths, failure paths, and weird paths.
|
- Tests should test all of the happy paths, failure paths, and weird paths.
|
||||||
- Tests live in the `tests/Feature` and `tests/Unit` directories.
|
- Tests live in the `tests/Feature` and `tests/Unit` directories.
|
||||||
@ -527,9 +528,9 @@ ### Pest Tests
|
|||||||
|
|
||||||
### Running Tests
|
### Running Tests
|
||||||
- Run the minimal number of tests using an appropriate filter before finalizing code edits.
|
- Run the minimal number of tests using an appropriate filter before finalizing code edits.
|
||||||
- To run all tests: `vendor/bin/sail artisan test --compact`.
|
- To run all tests: `cd apps/platform && ./vendor/bin/sail artisan test --compact`.
|
||||||
- To run all tests in a file: `vendor/bin/sail artisan test --compact tests/Feature/ExampleTest.php`.
|
- To run all tests in a file: `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ExampleTest.php`.
|
||||||
- To filter on a particular test name: `vendor/bin/sail artisan test --compact --filter=testName` (recommended after making a change to a related file).
|
- To filter on a particular test name: `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=testName` (recommended after making a change to a related file).
|
||||||
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
|
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
|
||||||
|
|
||||||
### Pest Assertions
|
### Pest Assertions
|
||||||
|
|||||||
13
.gitignore
vendored
13
.gitignore
vendored
@ -15,19 +15,30 @@
|
|||||||
/.zed
|
/.zed
|
||||||
/auth.json
|
/auth.json
|
||||||
/node_modules
|
/node_modules
|
||||||
|
/apps/platform/node_modules
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
coverage/
|
coverage/
|
||||||
/public/build
|
/public/build
|
||||||
|
/apps/platform/public/build
|
||||||
/public/hot
|
/public/hot
|
||||||
|
/apps/platform/public/hot
|
||||||
/public/storage
|
/public/storage
|
||||||
|
/apps/platform/public/storage
|
||||||
/storage/*.key
|
/storage/*.key
|
||||||
|
/apps/platform/storage/*.key
|
||||||
/storage/pail
|
/storage/pail
|
||||||
|
/apps/platform/storage/pail
|
||||||
/storage/framework
|
/storage/framework
|
||||||
|
/apps/platform/storage/framework
|
||||||
/storage/logs
|
/storage/logs
|
||||||
|
/apps/platform/storage/logs
|
||||||
/storage/debugbar
|
/storage/debugbar
|
||||||
|
/apps/platform/storage/debugbar
|
||||||
/vendor
|
/vendor
|
||||||
|
/apps/platform/vendor
|
||||||
/bootstrap/cache
|
/bootstrap/cache
|
||||||
|
/apps/platform/bootstrap/cache
|
||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
@ -35,3 +46,5 @@ Thumbs.db
|
|||||||
/tests/Browser/Screenshots
|
/tests/Browser/Screenshots
|
||||||
*.tmp
|
*.tmp
|
||||||
*.swp
|
*.swp
|
||||||
|
/apps/platform/.env
|
||||||
|
/apps/platform/.env.*
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
public/build/
|
public/build/
|
||||||
|
apps/platform/public/build/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
apps/platform/node_modules/
|
||||||
vendor/
|
vendor/
|
||||||
|
apps/platform/vendor/
|
||||||
*.log
|
*.log
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|||||||
@ -2,12 +2,19 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
public/build/
|
public/build/
|
||||||
|
apps/platform/public/build/
|
||||||
public/hot/
|
public/hot/
|
||||||
|
apps/platform/public/hot/
|
||||||
public/storage/
|
public/storage/
|
||||||
|
apps/platform/public/storage/
|
||||||
coverage/
|
coverage/
|
||||||
vendor/
|
vendor/
|
||||||
|
apps/platform/vendor/
|
||||||
|
apps/platform/node_modules/
|
||||||
storage/
|
storage/
|
||||||
|
apps/platform/storage/
|
||||||
bootstrap/cache/
|
bootstrap/cache/
|
||||||
|
apps/platform/bootstrap/cache/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
|||||||
73
Agents.md
73
Agents.md
@ -318,12 +318,13 @@ ## Security
|
|||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Sail (preferred locally)
|
### Sail (preferred locally)
|
||||||
- `./vendor/bin/sail up -d`
|
- `cd apps/platform && ./vendor/bin/sail up -d`
|
||||||
- `./vendor/bin/sail down`
|
- `cd apps/platform && ./vendor/bin/sail down`
|
||||||
- `./vendor/bin/sail composer install`
|
- `cd apps/platform && ./vendor/bin/sail composer install`
|
||||||
- `./vendor/bin/sail artisan migrate`
|
- `cd apps/platform && ./vendor/bin/sail artisan migrate`
|
||||||
- `./vendor/bin/sail artisan test`
|
- `cd apps/platform && ./vendor/bin/sail artisan test`
|
||||||
- `./vendor/bin/sail artisan` (general)
|
- `cd apps/platform && ./vendor/bin/sail artisan` (general)
|
||||||
|
- Root helper for tooling only: `./scripts/platform-sail ...`
|
||||||
|
|
||||||
### Drizzle (local DB tooling, if configured)
|
### Drizzle (local DB tooling, if configured)
|
||||||
- Use only for local/dev workflows.
|
- Use only for local/dev workflows.
|
||||||
@ -335,10 +336,10 @@ ### Drizzle (local DB tooling, if configured)
|
|||||||
(Agents should confirm the exact script names in `package.json` before suggesting them.)
|
(Agents should confirm the exact script names in `package.json` before suggesting them.)
|
||||||
|
|
||||||
### Non-Docker fallback (only if needed)
|
### Non-Docker fallback (only if needed)
|
||||||
- `composer install`
|
- `cd apps/platform && composer install`
|
||||||
- `php artisan serve`
|
- `cd apps/platform && php artisan serve`
|
||||||
- `php artisan migrate`
|
- `cd apps/platform && php artisan migrate`
|
||||||
- `php artisan test`
|
- `cd apps/platform && php artisan test`
|
||||||
|
|
||||||
### Frontend/assets/tooling (if present)
|
### Frontend/assets/tooling (if present)
|
||||||
- `pnpm install`
|
- `pnpm install`
|
||||||
@ -352,11 +353,11 @@ ## Where to look first
|
|||||||
- `.specify/`
|
- `.specify/`
|
||||||
- `AGENTS.md`
|
- `AGENTS.md`
|
||||||
- `README.md`
|
- `README.md`
|
||||||
- `app/`
|
- `apps/platform/app/`
|
||||||
- `database/`
|
- `apps/platform/database/`
|
||||||
- `routes/`
|
- `apps/platform/routes/`
|
||||||
- `resources/`
|
- `apps/platform/resources/`
|
||||||
- `config/`
|
- `apps/platform/config/`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -433,7 +434,7 @@ ## 3) Panel setup defaults
|
|||||||
- Assets policy:
|
- Assets policy:
|
||||||
- Panel-only assets: register via panel config.
|
- Panel-only assets: register via panel config.
|
||||||
- Shared/plugin assets: register via `FilamentAsset::register()`.
|
- Shared/plugin assets: register via `FilamentAsset::register()`.
|
||||||
- Deployment must include `php artisan filament:assets`.
|
- Deployment must include `cd apps/platform && php artisan filament:assets`.
|
||||||
|
|
||||||
Sources:
|
Sources:
|
||||||
- https://filamentphp.com/docs/5.x/panel-configuration
|
- https://filamentphp.com/docs/5.x/panel-configuration
|
||||||
@ -670,7 +671,7 @@ ## Testing
|
|||||||
|
|
||||||
## Deployment / Ops
|
## Deployment / Ops
|
||||||
|
|
||||||
- [ ] `php artisan filament:assets` is included in the deployment process when using registered assets.
|
- [ ] `cd apps/platform && php artisan filament:assets` is included in the deployment process when using registered assets.
|
||||||
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
|
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
|
||||||
|
|
||||||
=== foundation rules ===
|
=== foundation rules ===
|
||||||
@ -720,7 +721,7 @@ ## Application Structure & Architecture
|
|||||||
|
|
||||||
## Frontend Bundling
|
## Frontend Bundling
|
||||||
|
|
||||||
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them.
|
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `cd apps/platform && ./vendor/bin/sail npm run build`, `cd apps/platform && ./vendor/bin/sail npm run dev`, or `cd apps/platform && ./vendor/bin/sail composer run dev`. Ask them.
|
||||||
|
|
||||||
## Documentation Files
|
## Documentation Files
|
||||||
|
|
||||||
@ -812,28 +813,28 @@ ## PHPDoc Blocks
|
|||||||
# Laravel Sail
|
# Laravel Sail
|
||||||
|
|
||||||
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
|
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
|
||||||
- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`.
|
- Start services using `cd apps/platform && ./vendor/bin/sail up -d` and stop them with `cd apps/platform && ./vendor/bin/sail stop`.
|
||||||
- Open the application in the browser by running `vendor/bin/sail open`.
|
- Open the application in the browser by running `cd apps/platform && ./vendor/bin/sail open`.
|
||||||
- Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples:
|
- Always prefix PHP, Artisan, Composer, and Node commands with `cd apps/platform && ./vendor/bin/sail`. Examples:
|
||||||
- Run Artisan Commands: `vendor/bin/sail artisan migrate`
|
- Run Artisan Commands: `cd apps/platform && ./vendor/bin/sail artisan migrate`
|
||||||
- Install Composer packages: `vendor/bin/sail composer install`
|
- Install Composer packages: `cd apps/platform && ./vendor/bin/sail composer install`
|
||||||
- Execute Node commands: `vendor/bin/sail npm run dev`
|
- Execute Node commands: `cd apps/platform && ./vendor/bin/sail npm run dev`
|
||||||
- Execute PHP scripts: `vendor/bin/sail php [script]`
|
- Execute PHP scripts: `cd apps/platform && ./vendor/bin/sail php [script]`
|
||||||
- View all available Sail commands by running `vendor/bin/sail` without arguments.
|
- View all available Sail commands by running `cd apps/platform && ./vendor/bin/sail` without arguments.
|
||||||
|
|
||||||
=== tests rules ===
|
=== tests rules ===
|
||||||
|
|
||||||
# Test Enforcement
|
# Test Enforcement
|
||||||
|
|
||||||
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
||||||
- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter.
|
- Run the minimum number of tests needed to ensure code quality and speed. Use `cd apps/platform && ./vendor/bin/sail artisan test --compact` with a specific filename or filter.
|
||||||
|
|
||||||
=== laravel/core rules ===
|
=== laravel/core rules ===
|
||||||
|
|
||||||
# Do Things the Laravel Way
|
# Do Things the Laravel Way
|
||||||
|
|
||||||
- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
- Use `cd apps/platform && ./vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
||||||
- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`.
|
- If you're creating a generic PHP class, use `cd apps/platform && ./vendor/bin/sail artisan make:class`.
|
||||||
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
||||||
|
|
||||||
## Database
|
## Database
|
||||||
@ -846,7 +847,7 @@ ## Database
|
|||||||
|
|
||||||
### Model Creation
|
### Model Creation
|
||||||
|
|
||||||
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`.
|
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `cd apps/platform && ./vendor/bin/sail artisan make:model`.
|
||||||
|
|
||||||
### APIs & Eloquent Resources
|
### APIs & Eloquent Resources
|
||||||
|
|
||||||
@ -877,11 +878,11 @@ ## Testing
|
|||||||
|
|
||||||
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
||||||
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
||||||
- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
- When creating tests, make use of `cd apps/platform && ./vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
||||||
|
|
||||||
## Vite Error
|
## Vite Error
|
||||||
|
|
||||||
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`.
|
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `cd apps/platform && ./vendor/bin/sail npm run build` or ask the user to run `cd apps/platform && ./vendor/bin/sail npm run dev` or `cd apps/platform && ./vendor/bin/sail composer run dev`.
|
||||||
|
|
||||||
=== laravel/v12 rules ===
|
=== laravel/v12 rules ===
|
||||||
|
|
||||||
@ -912,15 +913,15 @@ ### Models
|
|||||||
|
|
||||||
# Laravel Pint Code Formatter
|
# Laravel Pint Code Formatter
|
||||||
|
|
||||||
- You must run `vendor/bin/sail bin pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
|
- You must run `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
|
||||||
- Do not run `vendor/bin/sail bin pint --test --format agent`, simply run `vendor/bin/sail bin pint --format agent` to fix any formatting issues.
|
- Do not run `cd apps/platform && ./vendor/bin/sail bin pint --test --format agent`, simply run `cd apps/platform && ./vendor/bin/sail bin pint --format agent` to fix any formatting issues.
|
||||||
|
|
||||||
=== pest/core rules ===
|
=== pest/core rules ===
|
||||||
|
|
||||||
## Pest
|
## Pest
|
||||||
|
|
||||||
- This project uses Pest for testing. Create tests: `vendor/bin/sail artisan make:test --pest {name}`.
|
- This project uses Pest for testing. Create tests: `cd apps/platform && ./vendor/bin/sail artisan make:test --pest {name}`.
|
||||||
- Run tests: `vendor/bin/sail artisan test --compact` or filter: `vendor/bin/sail artisan test --compact --filter=testName`.
|
- Run tests: `cd apps/platform && ./vendor/bin/sail artisan test --compact` or filter: `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=testName`.
|
||||||
- Do NOT delete tests without approval.
|
- Do NOT delete tests without approval.
|
||||||
- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples.
|
- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples.
|
||||||
- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task.
|
- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task.
|
||||||
|
|||||||
73
GEMINI.md
73
GEMINI.md
@ -156,12 +156,13 @@ ## Security
|
|||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Sail (preferred locally)
|
### Sail (preferred locally)
|
||||||
- `./vendor/bin/sail up -d`
|
- `cd apps/platform && ./vendor/bin/sail up -d`
|
||||||
- `./vendor/bin/sail down`
|
- `cd apps/platform && ./vendor/bin/sail down`
|
||||||
- `./vendor/bin/sail composer install`
|
- `cd apps/platform && ./vendor/bin/sail composer install`
|
||||||
- `./vendor/bin/sail artisan migrate`
|
- `cd apps/platform && ./vendor/bin/sail artisan migrate`
|
||||||
- `./vendor/bin/sail artisan test`
|
- `cd apps/platform && ./vendor/bin/sail artisan test`
|
||||||
- `./vendor/bin/sail artisan` (general)
|
- `cd apps/platform && ./vendor/bin/sail artisan` (general)
|
||||||
|
- Root helper for tooling only: `./scripts/platform-sail ...`
|
||||||
|
|
||||||
### Drizzle (local DB tooling, if configured)
|
### Drizzle (local DB tooling, if configured)
|
||||||
- Use only for local/dev workflows.
|
- Use only for local/dev workflows.
|
||||||
@ -173,10 +174,10 @@ ### Drizzle (local DB tooling, if configured)
|
|||||||
(Agents should confirm the exact script names in `package.json` before suggesting them.)
|
(Agents should confirm the exact script names in `package.json` before suggesting them.)
|
||||||
|
|
||||||
### Non-Docker fallback (only if needed)
|
### Non-Docker fallback (only if needed)
|
||||||
- `composer install`
|
- `cd apps/platform && composer install`
|
||||||
- `php artisan serve`
|
- `cd apps/platform && php artisan serve`
|
||||||
- `php artisan migrate`
|
- `cd apps/platform && php artisan migrate`
|
||||||
- `php artisan test`
|
- `cd apps/platform && php artisan test`
|
||||||
|
|
||||||
### Frontend/assets/tooling (if present)
|
### Frontend/assets/tooling (if present)
|
||||||
- `pnpm install`
|
- `pnpm install`
|
||||||
@ -190,11 +191,11 @@ ## Where to look first
|
|||||||
- `.specify/`
|
- `.specify/`
|
||||||
- `AGENTS.md`
|
- `AGENTS.md`
|
||||||
- `README.md`
|
- `README.md`
|
||||||
- `app/`
|
- `apps/platform/app/`
|
||||||
- `database/`
|
- `apps/platform/database/`
|
||||||
- `routes/`
|
- `apps/platform/routes/`
|
||||||
- `resources/`
|
- `apps/platform/resources/`
|
||||||
- `config/`
|
- `apps/platform/config/`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -271,7 +272,7 @@ ## 3) Panel setup defaults
|
|||||||
- Assets policy:
|
- Assets policy:
|
||||||
- Panel-only assets: register via panel config.
|
- Panel-only assets: register via panel config.
|
||||||
- Shared/plugin assets: register via `FilamentAsset::register()`.
|
- Shared/plugin assets: register via `FilamentAsset::register()`.
|
||||||
- Deployment must include `php artisan filament:assets`.
|
- Deployment must include `cd apps/platform && php artisan filament:assets`.
|
||||||
|
|
||||||
Sources:
|
Sources:
|
||||||
- https://filamentphp.com/docs/5.x/panel-configuration
|
- https://filamentphp.com/docs/5.x/panel-configuration
|
||||||
@ -508,7 +509,7 @@ ## Testing
|
|||||||
|
|
||||||
## Deployment / Ops
|
## Deployment / Ops
|
||||||
|
|
||||||
- [ ] `php artisan filament:assets` is included in the deployment process when using registered assets.
|
- [ ] `cd apps/platform && php artisan filament:assets` is included in the deployment process when using registered assets.
|
||||||
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
|
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
|
||||||
|
|
||||||
=== foundation rules ===
|
=== foundation rules ===
|
||||||
@ -558,7 +559,7 @@ ## Application Structure & Architecture
|
|||||||
|
|
||||||
## Frontend Bundling
|
## Frontend Bundling
|
||||||
|
|
||||||
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them.
|
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `cd apps/platform && ./vendor/bin/sail npm run build`, `cd apps/platform && ./vendor/bin/sail npm run dev`, or `cd apps/platform && ./vendor/bin/sail composer run dev`. Ask them.
|
||||||
|
|
||||||
## Documentation Files
|
## Documentation Files
|
||||||
|
|
||||||
@ -650,28 +651,28 @@ ## PHPDoc Blocks
|
|||||||
# Laravel Sail
|
# Laravel Sail
|
||||||
|
|
||||||
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
|
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
|
||||||
- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`.
|
- Start services using `cd apps/platform && ./vendor/bin/sail up -d` and stop them with `cd apps/platform && ./vendor/bin/sail stop`.
|
||||||
- Open the application in the browser by running `vendor/bin/sail open`.
|
- Open the application in the browser by running `cd apps/platform && ./vendor/bin/sail open`.
|
||||||
- Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples:
|
- Always prefix PHP, Artisan, Composer, and Node commands with `cd apps/platform && ./vendor/bin/sail`. Examples:
|
||||||
- Run Artisan Commands: `vendor/bin/sail artisan migrate`
|
- Run Artisan Commands: `cd apps/platform && ./vendor/bin/sail artisan migrate`
|
||||||
- Install Composer packages: `vendor/bin/sail composer install`
|
- Install Composer packages: `cd apps/platform && ./vendor/bin/sail composer install`
|
||||||
- Execute Node commands: `vendor/bin/sail npm run dev`
|
- Execute Node commands: `cd apps/platform && ./vendor/bin/sail npm run dev`
|
||||||
- Execute PHP scripts: `vendor/bin/sail php [script]`
|
- Execute PHP scripts: `cd apps/platform && ./vendor/bin/sail php [script]`
|
||||||
- View all available Sail commands by running `vendor/bin/sail` without arguments.
|
- View all available Sail commands by running `cd apps/platform && ./vendor/bin/sail` without arguments.
|
||||||
|
|
||||||
=== tests rules ===
|
=== tests rules ===
|
||||||
|
|
||||||
# Test Enforcement
|
# Test Enforcement
|
||||||
|
|
||||||
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
||||||
- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter.
|
- Run the minimum number of tests needed to ensure code quality and speed. Use `cd apps/platform && ./vendor/bin/sail artisan test --compact` with a specific filename or filter.
|
||||||
|
|
||||||
=== laravel/core rules ===
|
=== laravel/core rules ===
|
||||||
|
|
||||||
# Do Things the Laravel Way
|
# Do Things the Laravel Way
|
||||||
|
|
||||||
- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
- Use `cd apps/platform && ./vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
||||||
- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`.
|
- If you're creating a generic PHP class, use `cd apps/platform && ./vendor/bin/sail artisan make:class`.
|
||||||
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
||||||
|
|
||||||
## Database
|
## Database
|
||||||
@ -684,7 +685,7 @@ ## Database
|
|||||||
|
|
||||||
### Model Creation
|
### Model Creation
|
||||||
|
|
||||||
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`.
|
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `cd apps/platform && ./vendor/bin/sail artisan make:model`.
|
||||||
|
|
||||||
### APIs & Eloquent Resources
|
### APIs & Eloquent Resources
|
||||||
|
|
||||||
@ -715,11 +716,11 @@ ## Testing
|
|||||||
|
|
||||||
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
||||||
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
||||||
- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
- When creating tests, make use of `cd apps/platform && ./vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
||||||
|
|
||||||
## Vite Error
|
## Vite Error
|
||||||
|
|
||||||
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`.
|
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `cd apps/platform && ./vendor/bin/sail npm run build` or ask the user to run `cd apps/platform && ./vendor/bin/sail npm run dev` or `cd apps/platform && ./vendor/bin/sail composer run dev`.
|
||||||
|
|
||||||
=== laravel/v12 rules ===
|
=== laravel/v12 rules ===
|
||||||
|
|
||||||
@ -750,15 +751,15 @@ ### Models
|
|||||||
|
|
||||||
# Laravel Pint Code Formatter
|
# Laravel Pint Code Formatter
|
||||||
|
|
||||||
- You must run `vendor/bin/sail bin pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
|
- You must run `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
|
||||||
- Do not run `vendor/bin/sail bin pint --test --format agent`, simply run `vendor/bin/sail bin pint --format agent` to fix any formatting issues.
|
- Do not run `cd apps/platform && ./vendor/bin/sail bin pint --test --format agent`, simply run `cd apps/platform && ./vendor/bin/sail bin pint --format agent` to fix any formatting issues.
|
||||||
|
|
||||||
=== pest/core rules ===
|
=== pest/core rules ===
|
||||||
|
|
||||||
## Pest
|
## Pest
|
||||||
|
|
||||||
- This project uses Pest for testing. Create tests: `vendor/bin/sail artisan make:test --pest {name}`.
|
- This project uses Pest for testing. Create tests: `cd apps/platform && ./vendor/bin/sail artisan make:test --pest {name}`.
|
||||||
- Run tests: `vendor/bin/sail artisan test --compact` or filter: `vendor/bin/sail artisan test --compact --filter=testName`.
|
- Run tests: `cd apps/platform && ./vendor/bin/sail artisan test --compact` or filter: `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=testName`.
|
||||||
- Do NOT delete tests without approval.
|
- Do NOT delete tests without approval.
|
||||||
- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples.
|
- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples.
|
||||||
- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task.
|
- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task.
|
||||||
|
|||||||
43
README.md
43
README.md
@ -9,11 +9,18 @@
|
|||||||
|
|
||||||
## TenantPilot setup
|
## TenantPilot setup
|
||||||
|
|
||||||
- Local dev (Sail-first):
|
- Platform app root: `apps/platform`
|
||||||
- Start stack: `./vendor/bin/sail up -d`
|
- Repo-root ownership: specs, docs, scripts, editor config, agent config, orchestration, and `docker-compose.yml`
|
||||||
- Init DB: `./vendor/bin/sail artisan migrate --seed`
|
- App-root ownership: Laravel runtime, tests, Vite assets, public entrypoints, `composer.json`, `package.json`, `drizzle.config.ts`, and app-local `.env*`
|
||||||
- Tests: `./vendor/bin/sail artisan test`
|
- Local dev (Sail-first, canonical workflow):
|
||||||
- Policy sync: `./vendor/bin/sail artisan intune:sync-policies`
|
- Install: `cd apps/platform && composer install`
|
||||||
|
- Env bootstrap: `cd apps/platform && cp .env.example .env`
|
||||||
|
- Start stack: `cd apps/platform && ./vendor/bin/sail up -d`
|
||||||
|
- Generate app key: `cd apps/platform && ./vendor/bin/sail artisan key:generate`
|
||||||
|
- Init DB: `cd apps/platform && ./vendor/bin/sail artisan migrate --seed`
|
||||||
|
- Tests: `cd apps/platform && ./vendor/bin/sail artisan test --compact`
|
||||||
|
- Policy sync: `cd apps/platform && ./vendor/bin/sail artisan intune:sync-policies`
|
||||||
|
- Compatibility helper for tooling that cannot set a nested working directory: `./scripts/platform-sail ...`
|
||||||
- Filament admin: `/admin` (seed user `test@example.com`, set password via factory or `artisan tinker`).
|
- Filament admin: `/admin` (seed user `test@example.com`, set password via factory or `artisan tinker`).
|
||||||
- Microsoft Graph (Intune) env vars:
|
- Microsoft Graph (Intune) env vars:
|
||||||
- `GRAPH_TENANT_ID`
|
- `GRAPH_TENANT_ID`
|
||||||
@ -25,10 +32,17 @@ ## TenantPilot setup
|
|||||||
- **Missing permissions?** Scope tags will show as "Unknown (ID: X)" - add `DeviceManagementRBAC.Read.All`
|
- **Missing permissions?** Scope tags will show as "Unknown (ID: X)" - add `DeviceManagementRBAC.Read.All`
|
||||||
- Deployment (Dokploy, staging → production):
|
- Deployment (Dokploy, staging → production):
|
||||||
- Containerized deploy; ensure Postgres + Redis are provisioned (see `docker-compose.yml` for local baseline).
|
- Containerized deploy; ensure Postgres + Redis are provisioned (see `docker-compose.yml` for local baseline).
|
||||||
|
- Run application commands from `apps/platform`, including `php artisan filament:assets`.
|
||||||
- Run migrations on staging first, validate backup/restore flows, then promote to production.
|
- Run migrations on staging first, validate backup/restore flows, then promote to production.
|
||||||
- Ensure queue workers are running for jobs (e.g., policy sync) after deploy.
|
- Ensure queue workers are running for jobs (e.g., policy sync) after deploy.
|
||||||
- Keep secrets/env in Dokploy, never in code.
|
- Keep secrets/env in Dokploy, never in code.
|
||||||
|
|
||||||
|
## Platform relocation rollout notes
|
||||||
|
|
||||||
|
- Open branches that still touch legacy root app paths should merge `dev` first, then remap file moves from `app/`, `bootstrap/`, `config/`, `database/`, `lang/`, `public/`, `resources/`, `routes/`, `storage/`, and `tests/` into `apps/platform/...`.
|
||||||
|
- Keep using merge-based catch-up on shared feature branches; do not rebase long-lived shared branches just to absorb the relocation.
|
||||||
|
- VS Code tasks and MCP launchers now delegate through `./scripts/platform-sail` from the repo root. Human-facing docs remain `apps/platform`-first.
|
||||||
|
|
||||||
## Bulk operations (Feature 005)
|
## Bulk operations (Feature 005)
|
||||||
|
|
||||||
- Bulk actions are available in Filament resource tables (Policies, Policy Versions, Backup Sets, Restore Runs).
|
- Bulk actions are available in Filament resource tables (Policies, Policy Versions, Backup Sets, Restore Runs).
|
||||||
@ -39,8 +53,23 @@ ### Troubleshooting
|
|||||||
|
|
||||||
- **Progress stuck on “Queued…”** usually means the queue worker is not running (or not processing the queue you expect).
|
- **Progress stuck on “Queued…”** usually means the queue worker is not running (or not processing the queue you expect).
|
||||||
- Prefer using the Sail/Docker worker (see `docker-compose.yml`) rather than starting an additional local `php artisan queue:work`.
|
- Prefer using the Sail/Docker worker (see `docker-compose.yml`) rather than starting an additional local `php artisan queue:work`.
|
||||||
- Check worker status/logs: `./vendor/bin/sail ps` and `./vendor/bin/sail logs -f queue`.
|
- Check worker status/logs: `cd apps/platform && ./vendor/bin/sail ps` and `cd apps/platform && ./vendor/bin/sail logs -f queue`.
|
||||||
- **Exit code 137** for `queue:work` typically means the process was killed (often OOM). Increase Docker memory/limits or run the worker inside the container.
|
- **Exit code 137** for `queue:work` typically means the process was killed (often OOM). Increase Docker memory/limits or run the worker inside the container.
|
||||||
|
- **Moved app but old commands still fail** usually means the command is still being run from repo root. Switch to `cd apps/platform && ...` or use `./scripts/platform-sail ...` only for tooling that cannot set `cwd`.
|
||||||
|
|
||||||
|
## Rollback checklist
|
||||||
|
|
||||||
|
1. Revert the relocation commit or merge on your feature branch instead of hard-resetting shared history.
|
||||||
|
2. Preserve any local app env overrides before switching commits: `cp apps/platform/.env /tmp/tenantatlas.platform.env.backup` if needed.
|
||||||
|
3. Stop local containers and clean generated artifacts: `cd apps/platform && ./vendor/bin/sail down -v`, then remove `apps/platform/vendor`, `apps/platform/node_modules`, `apps/platform/public/build`, and `apps/platform/public/hot` if they need a clean rebuild.
|
||||||
|
4. After rollback, restore the matching env file for the restored topology and rerun the documented setup flow for that commit.
|
||||||
|
5. Notify owners of open feature branches that the topology changed so they can remap outstanding work before the next merge from `dev`.
|
||||||
|
|
||||||
|
## Deployment unknowns
|
||||||
|
|
||||||
|
- Dokploy build context for a repo-root compose file plus an app-root Laravel runtime still needs staging confirmation.
|
||||||
|
- Production web, queue, and scheduler working directories must be verified explicitly after the move; do not assume repo root and app root behave interchangeably.
|
||||||
|
- Any Dokploy volume mounts or storage persistence paths that previously targeted repo-root `storage/` must be reviewed against `apps/platform/storage/`.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
@ -64,7 +93,7 @@ ## Graph Contract Registry & Drift Guard
|
|||||||
- Sanitizes `$select`/`$expand` to allowed fields; logs warnings on trim.
|
- Sanitizes `$select`/`$expand` to allowed fields; logs warnings on trim.
|
||||||
- Derived @odata.type values within the family are accepted for preview/restore routing.
|
- Derived @odata.type values within the family are accepted for preview/restore routing.
|
||||||
- Capability fallback: on 400s related to select/expand, retries without those clauses and surfaces warnings.
|
- Capability fallback: on 400s related to select/expand, retries without those clauses and surfaces warnings.
|
||||||
- Drift check: `php artisan graph:contract:check [--tenant=]` runs lightweight probes against contract endpoints to detect capability/shape issues; useful in staging/CI (prod optional).
|
- Drift check: `cd apps/platform && php artisan graph:contract:check [--tenant=]` runs lightweight probes against contract endpoints to detect capability/shape issues; useful in staging/CI (prod optional).
|
||||||
- If Graph returns capability errors, TenantPilot downgrades safely, records warnings/audit entries, and avoids breaking preview/restore flows.
|
- If Graph returns capability errors, TenantPilot downgrades safely, records warnings/audit entries, and avoids breaking preview/restore flows.
|
||||||
|
|
||||||
## Policy Settings Display
|
## Policy Settings Display
|
||||||
|
|||||||
@ -3,6 +3,8 @@ APP_ENV=local
|
|||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
SAIL_FILES=../../docker-compose.yml
|
||||||
|
TENANTATLAS_REPO_ROOT=../..
|
||||||
|
|
||||||
APP_LOCALE=en
|
APP_LOCALE=en
|
||||||
APP_FALLBACK_LOCALE=en
|
APP_FALLBACK_LOCALE=en
|
||||||
@ -21,11 +23,12 @@ LOG_DEPRECATIONS_CHANNEL=null
|
|||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
DB_CONNECTION=pgsql
|
DB_CONNECTION=pgsql
|
||||||
DB_HOST=127.0.0.1
|
DB_HOST=pgsql
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
FORWARD_DB_PORT=55432
|
||||||
DB_DATABASE=tenantatlas
|
DB_DATABASE=tenantatlas
|
||||||
DB_USERNAME=root
|
DB_USERNAME=root
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=postgres
|
||||||
|
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
@ -43,7 +46,7 @@ CACHE_STORE=database
|
|||||||
MEMCACHED_HOST=127.0.0.1
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
REDIS_CLIENT=phpredis
|
REDIS_CLIENT=phpredis
|
||||||
REDIS_HOST=127.0.0.1
|
REDIS_HOST=redis
|
||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\BackupItem;
|
||||||
|
use App\Models\BackupSet;
|
||||||
|
use App\Models\Policy;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Models\TenantMembership;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\UserTenantPreference;
|
||||||
|
use App\Models\Workspace;
|
||||||
|
use App\Models\WorkspaceMembership;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class SeedBackupHealthBrowserFixture extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'tenantpilot:backup-health:seed-browser-fixture {--force-refresh : Rebuild the fixture backup basis even if it already exists}';
|
||||||
|
|
||||||
|
protected $description = 'Seed a local/testing browser fixture for the Spec 180 blocked backup drill-through scenario.';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
if (! app()->environment(['local', 'testing'])) {
|
||||||
|
$this->error('This fixture command is limited to local and testing environments.');
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fixture = config('tenantpilot.backup_health.browser_smoke_fixture');
|
||||||
|
|
||||||
|
if (! is_array($fixture)) {
|
||||||
|
$this->error('The backup-health browser smoke fixture is not configured.');
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$workspaceConfig = is_array($fixture['workspace'] ?? null) ? $fixture['workspace'] : [];
|
||||||
|
$userConfig = is_array($fixture['user'] ?? null) ? $fixture['user'] : [];
|
||||||
|
$scenarioConfig = is_array($fixture['blocked_drillthrough'] ?? null) ? $fixture['blocked_drillthrough'] : [];
|
||||||
|
$tenantRouteKey = (string) ($scenarioConfig['tenant_id'] ?? $scenarioConfig['tenant_external_id'] ?? '18000000-0000-4000-8000-000000000180');
|
||||||
|
|
||||||
|
$workspace = Workspace::query()->updateOrCreate(
|
||||||
|
['slug' => (string) ($workspaceConfig['slug'] ?? 'spec-180-backup-health-smoke')],
|
||||||
|
['name' => (string) ($workspaceConfig['name'] ?? 'Spec 180 Backup Health Smoke')],
|
||||||
|
);
|
||||||
|
|
||||||
|
$password = (string) ($userConfig['password'] ?? 'password');
|
||||||
|
|
||||||
|
$user = User::query()->updateOrCreate(
|
||||||
|
['email' => (string) ($userConfig['email'] ?? 'smoke-requester+180@tenantpilot.local')],
|
||||||
|
[
|
||||||
|
'name' => (string) ($userConfig['name'] ?? 'Spec 180 Requester'),
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$tenant = Tenant::query()->updateOrCreate(
|
||||||
|
['external_id' => $tenantRouteKey],
|
||||||
|
[
|
||||||
|
'workspace_id' => (int) $workspace->getKey(),
|
||||||
|
'name' => (string) ($scenarioConfig['tenant_name'] ?? 'Spec 180 Blocked Backup Tenant'),
|
||||||
|
'tenant_id' => $tenantRouteKey,
|
||||||
|
'app_client_id' => (string) ($scenarioConfig['app_client_id'] ?? '18000000-0000-4000-8000-000000000182'),
|
||||||
|
'app_client_secret' => null,
|
||||||
|
'app_certificate_thumbprint' => null,
|
||||||
|
'app_status' => 'ok',
|
||||||
|
'app_notes' => null,
|
||||||
|
'status' => Tenant::STATUS_ACTIVE,
|
||||||
|
'environment' => 'dev',
|
||||||
|
'is_current' => false,
|
||||||
|
'metadata' => ['fixture' => 'spec-180-browser-smoke'],
|
||||||
|
'rbac_status' => 'ok',
|
||||||
|
'rbac_last_checked_at' => now(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
WorkspaceMembership::query()->updateOrCreate(
|
||||||
|
['workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey()],
|
||||||
|
['role' => 'owner'],
|
||||||
|
);
|
||||||
|
|
||||||
|
TenantMembership::query()->updateOrCreate(
|
||||||
|
['tenant_id' => (int) $tenant->getKey(), 'user_id' => (int) $user->getKey()],
|
||||||
|
['role' => 'owner', 'source' => 'manual', 'source_ref' => 'spec-180-browser-smoke'],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Schema::hasColumn('users', 'last_workspace_id')) {
|
||||||
|
$user->forceFill(['last_workspace_id' => (int) $workspace->getKey()])->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Schema::hasTable('user_tenant_preferences')) {
|
||||||
|
UserTenantPreference::query()->updateOrCreate(
|
||||||
|
['user_id' => (int) $user->getKey(), 'tenant_id' => (int) $tenant->getKey()],
|
||||||
|
['last_used_at' => now()],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$policy = Policy::query()->updateOrCreate(
|
||||||
|
[
|
||||||
|
'tenant_id' => (int) $tenant->getKey(),
|
||||||
|
'external_id' => (string) ($scenarioConfig['policy_external_id'] ?? 'spec-180-rbac-stale-policy'),
|
||||||
|
'policy_type' => (string) ($scenarioConfig['policy_type'] ?? 'settingsCatalogPolicy'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'display_name' => (string) ($scenarioConfig['policy_name'] ?? 'Spec 180 RBAC Smoke Policy'),
|
||||||
|
'platform' => 'windows',
|
||||||
|
'last_synced_at' => now(),
|
||||||
|
'metadata' => ['fixture' => 'spec-180-browser-smoke'],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$backupSet = BackupSet::withTrashed()->firstOrNew([
|
||||||
|
'tenant_id' => (int) $tenant->getKey(),
|
||||||
|
'name' => (string) ($scenarioConfig['backup_set_name'] ?? 'Spec 180 Blocked Stale Backup'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$backupSet->forceFill([
|
||||||
|
'created_by' => (string) $user->email,
|
||||||
|
'status' => 'completed',
|
||||||
|
'item_count' => 1,
|
||||||
|
'completed_at' => now()->subHours(max(25, (int) ($scenarioConfig['stale_age_hours'] ?? 48))),
|
||||||
|
'metadata' => ['fixture' => 'spec-180-browser-smoke'],
|
||||||
|
'deleted_at' => null,
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
if (method_exists($backupSet, 'trashed') && $backupSet->trashed()) {
|
||||||
|
$backupSet->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
$backupItem = BackupItem::withTrashed()->firstOrNew([
|
||||||
|
'backup_set_id' => (int) $backupSet->getKey(),
|
||||||
|
'policy_identifier' => (string) ($scenarioConfig['policy_external_id'] ?? 'spec-180-rbac-stale-policy'),
|
||||||
|
'policy_type' => (string) ($scenarioConfig['policy_type'] ?? 'settingsCatalogPolicy'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$backupItem->forceFill([
|
||||||
|
'tenant_id' => (int) $tenant->getKey(),
|
||||||
|
'policy_id' => (int) $policy->getKey(),
|
||||||
|
'platform' => 'windows',
|
||||||
|
'captured_at' => $backupSet->completed_at,
|
||||||
|
'payload' => [
|
||||||
|
'id' => (string) ($scenarioConfig['policy_external_id'] ?? 'spec-180-rbac-stale-policy'),
|
||||||
|
'name' => (string) ($scenarioConfig['policy_name'] ?? 'Spec 180 RBAC Smoke Policy'),
|
||||||
|
],
|
||||||
|
'metadata' => [
|
||||||
|
'policy_name' => (string) ($scenarioConfig['policy_name'] ?? 'Spec 180 RBAC Smoke Policy'),
|
||||||
|
'fixture' => 'spec-180-browser-smoke',
|
||||||
|
],
|
||||||
|
'assignments' => [],
|
||||||
|
'deleted_at' => null,
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
if (method_exists($backupItem, 'trashed') && $backupItem->trashed()) {
|
||||||
|
$backupItem->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((bool) $this->option('force-refresh')) {
|
||||||
|
$backupSet->forceFill([
|
||||||
|
'completed_at' => now()->subHours(max(25, (int) ($scenarioConfig['stale_age_hours'] ?? 48))),
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
$backupItem->forceFill([
|
||||||
|
'captured_at' => $backupSet->completed_at,
|
||||||
|
])->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->table(
|
||||||
|
['Fixture', 'Value'],
|
||||||
|
[
|
||||||
|
['Workspace', (string) $workspace->name],
|
||||||
|
['User email', (string) $user->email],
|
||||||
|
['User password', $password],
|
||||||
|
['Tenant', (string) $tenant->name],
|
||||||
|
['Tenant external id', (string) $tenant->external_id],
|
||||||
|
['Dashboard URL', "/admin/t/{$tenant->external_id}"],
|
||||||
|
['Fixture login URL', route('admin.local.backup-health-browser-fixture-login', absolute: false)],
|
||||||
|
['Blocked route', "/admin/t/{$tenant->external_id}/backup-sets"],
|
||||||
|
['Locally denied capability', 'tenant.view'],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->info('The dashboard remains visible for this fixture user, while backup drill-through routes stay forbidden via a local/testing-only capability deny seam.');
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -395,6 +395,7 @@ public static function table(Table $table): Table
|
|||||||
return $nextRun->format('M j, Y H:i:s');
|
return $nextRun->format('M j, Y H:i:s');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
->description(fn (BackupSchedule $record): ?string => static::scheduleFollowUpDescription($record))
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
@ -1149,4 +1150,31 @@ protected static function dayOfWeekOptions(): array
|
|||||||
7 => 'Sunday',
|
7 => 'Sunday',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static function scheduleFollowUpDescription(BackupSchedule $record): ?string
|
||||||
|
{
|
||||||
|
if (! $record->is_enabled || $record->trashed()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$graceCutoff = now('UTC')->subMinutes(max(1, (int) config('tenantpilot.backup_health.schedule_overdue_grace_minutes', 30)));
|
||||||
|
$lastRunStatus = strtolower(trim((string) $record->last_run_status));
|
||||||
|
$isOverdue = $record->next_run_at?->lessThan($graceCutoff) ?? false;
|
||||||
|
$neverSuccessful = $record->last_run_at === null
|
||||||
|
&& ($isOverdue || ($record->created_at?->lessThan($graceCutoff) ?? false));
|
||||||
|
|
||||||
|
if ($neverSuccessful) {
|
||||||
|
return 'No successful run has been recorded yet.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isOverdue) {
|
||||||
|
return 'This schedule looks overdue.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($lastRunStatus, ['failed', 'partial', 'skipped', 'canceled'], true)) {
|
||||||
|
return 'The last run needs follow-up.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -3,7 +3,11 @@
|
|||||||
namespace App\Filament\Resources\BackupScheduleResource\Pages;
|
namespace App\Filament\Resources\BackupScheduleResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\BackupScheduleResource;
|
use App\Filament\Resources\BackupScheduleResource;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Support\BackupHealth\TenantBackupHealthAssessment;
|
||||||
|
use App\Support\BackupHealth\TenantBackupHealthResolver;
|
||||||
use App\Support\Filament\CanonicalAdminTenantFilterState;
|
use App\Support\Filament\CanonicalAdminTenantFilterState;
|
||||||
|
use Filament\Facades\Filament;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
|
||||||
@ -64,4 +68,23 @@ private function syncCanonicalAdminTenantFilterState(): void
|
|||||||
tenantFilterName: null,
|
tenantFilterName: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSubheading(): ?string
|
||||||
|
{
|
||||||
|
if (request()->string('backup_health_reason')->toString() !== TenantBackupHealthAssessment::REASON_SCHEDULE_FOLLOW_UP) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tenant = Filament::getTenant();
|
||||||
|
|
||||||
|
if (! $tenant instanceof Tenant) {
|
||||||
|
return 'One or more enabled schedules need follow-up.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var TenantBackupHealthResolver $resolver */
|
||||||
|
$resolver = app(TenantBackupHealthResolver::class);
|
||||||
|
$summary = $resolver->assess($tenant)->scheduleFollowUp->summaryMessage;
|
||||||
|
|
||||||
|
return $summary ?? 'One or more enabled schedules need follow-up.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -18,6 +18,8 @@
|
|||||||
use App\Services\OperationRunService;
|
use App\Services\OperationRunService;
|
||||||
use App\Services\Operations\BulkSelectionIdentity;
|
use App\Services\Operations\BulkSelectionIdentity;
|
||||||
use App\Support\Auth\Capabilities;
|
use App\Support\Auth\Capabilities;
|
||||||
|
use App\Support\BackupHealth\TenantBackupHealthAssessment;
|
||||||
|
use App\Support\BackupHealth\TenantBackupHealthResolver;
|
||||||
use App\Support\BackupQuality\BackupQualityResolver;
|
use App\Support\BackupQuality\BackupQualityResolver;
|
||||||
use App\Support\Badges\BadgeDomain;
|
use App\Support\Badges\BadgeDomain;
|
||||||
use App\Support\Badges\BadgeRenderer;
|
use App\Support\Badges\BadgeRenderer;
|
||||||
@ -675,11 +677,22 @@ private static function enterpriseDetailPage(BackupSet $record): EnterpriseDetai
|
|||||||
$relatedContext = static::relatedContextEntries($record);
|
$relatedContext = static::relatedContextEntries($record);
|
||||||
$isArchived = $record->trashed();
|
$isArchived = $record->trashed();
|
||||||
$qualitySummary = static::backupQualitySummary($record);
|
$qualitySummary = static::backupQualitySummary($record);
|
||||||
|
$backupHealthAssessment = static::backupHealthContinuityAssessment($record);
|
||||||
$qualityBadge = match (true) {
|
$qualityBadge = match (true) {
|
||||||
$qualitySummary->totalItems === 0 => $factory->statusBadge('No items', 'gray'),
|
$qualitySummary->totalItems === 0 => $factory->statusBadge('No items', 'gray'),
|
||||||
$qualitySummary->hasDegradations() => $factory->statusBadge('Degraded input', 'warning', 'heroicon-m-exclamation-triangle'),
|
$qualitySummary->hasDegradations() => $factory->statusBadge('Degraded input', 'warning', 'heroicon-m-exclamation-triangle'),
|
||||||
default => $factory->statusBadge('No degradations', 'success', 'heroicon-m-check-circle'),
|
default => $factory->statusBadge('No degradations', 'success', 'heroicon-m-check-circle'),
|
||||||
};
|
};
|
||||||
|
$backupHealthBadge = $backupHealthAssessment instanceof TenantBackupHealthAssessment
|
||||||
|
? $factory->statusBadge(
|
||||||
|
static::backupHealthContinuityLabel($backupHealthAssessment),
|
||||||
|
$backupHealthAssessment->tone(),
|
||||||
|
'heroicon-m-exclamation-triangle',
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
$descriptionHint = $backupHealthAssessment instanceof TenantBackupHealthAssessment
|
||||||
|
? trim($backupHealthAssessment->headline.' '.($backupHealthAssessment->supportingMessage ?? ''))
|
||||||
|
: 'Backup quality, lifecycle status, and related operations stay ahead of raw backup metadata.';
|
||||||
|
|
||||||
return EnterpriseDetailBuilder::make('backup_set', 'tenant')
|
return EnterpriseDetailBuilder::make('backup_set', 'tenant')
|
||||||
->header(new SummaryHeaderData(
|
->header(new SummaryHeaderData(
|
||||||
@ -688,19 +701,28 @@ private static function enterpriseDetailPage(BackupSet $record): EnterpriseDetai
|
|||||||
statusBadges: [
|
statusBadges: [
|
||||||
$factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor),
|
$factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor),
|
||||||
$factory->statusBadge($isArchived ? 'Archived' : 'Active', $isArchived ? 'warning' : 'success'),
|
$factory->statusBadge($isArchived ? 'Archived' : 'Active', $isArchived ? 'warning' : 'success'),
|
||||||
|
...array_filter([$backupHealthBadge]),
|
||||||
$qualityBadge,
|
$qualityBadge,
|
||||||
],
|
],
|
||||||
keyFacts: [
|
keyFacts: [
|
||||||
$factory->keyFact('Items', $record->item_count),
|
$factory->keyFact('Items', $record->item_count),
|
||||||
|
...array_filter([
|
||||||
|
$backupHealthAssessment instanceof TenantBackupHealthAssessment
|
||||||
|
? $factory->keyFact('Backup posture', static::backupHealthContinuityLabel($backupHealthAssessment), badge: $backupHealthBadge)
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
$factory->keyFact('Backup quality', $qualitySummary->compactSummary),
|
$factory->keyFact('Backup quality', $qualitySummary->compactSummary),
|
||||||
$factory->keyFact('Created by', $record->created_by),
|
$factory->keyFact('Created by', $record->created_by),
|
||||||
$factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)),
|
$factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)),
|
||||||
$factory->keyFact('Captured', static::formatDetailTimestamp($record->created_at)),
|
$factory->keyFact('Captured', static::formatDetailTimestamp($record->created_at)),
|
||||||
],
|
],
|
||||||
descriptionHint: 'Backup quality, lifecycle status, and related operations stay ahead of raw backup metadata.',
|
descriptionHint: $descriptionHint,
|
||||||
))
|
))
|
||||||
->decisionZone($factory->decisionZone(
|
->decisionZone($factory->decisionZone(
|
||||||
facts: array_values(array_filter([
|
facts: array_values(array_filter([
|
||||||
|
$backupHealthAssessment instanceof TenantBackupHealthAssessment
|
||||||
|
? $factory->keyFact('Backup posture', static::backupHealthContinuityLabel($backupHealthAssessment), badge: $backupHealthBadge)
|
||||||
|
: null,
|
||||||
$factory->keyFact('Backup quality', $qualitySummary->compactSummary, badge: $qualityBadge),
|
$factory->keyFact('Backup quality', $qualitySummary->compactSummary, badge: $qualityBadge),
|
||||||
$factory->keyFact('Degraded items', $qualitySummary->degradedItemCount),
|
$factory->keyFact('Degraded items', $qualitySummary->degradedItemCount),
|
||||||
$factory->keyFact('Metadata only', $qualitySummary->metadataOnlyCount),
|
$factory->keyFact('Metadata only', $qualitySummary->metadataOnlyCount),
|
||||||
@ -717,7 +739,7 @@ private static function enterpriseDetailPage(BackupSet $record): EnterpriseDetai
|
|||||||
),
|
),
|
||||||
description: 'Start here to judge whether this backup set looks strong or weak as restore input before reading diagnostics or raw metadata.',
|
description: 'Start here to judge whether this backup set looks strong or weak as restore input before reading diagnostics or raw metadata.',
|
||||||
compactCounts: $factory->countPresentation(summaryLine: $qualitySummary->summaryMessage),
|
compactCounts: $factory->countPresentation(summaryLine: $qualitySummary->summaryMessage),
|
||||||
attentionNote: $qualitySummary->positiveClaimBoundary,
|
attentionNote: $backupHealthAssessment?->positiveClaimBoundary ?? $qualitySummary->positiveClaimBoundary,
|
||||||
title: 'Backup quality',
|
title: 'Backup quality',
|
||||||
))
|
))
|
||||||
->addSection(
|
->addSection(
|
||||||
@ -810,4 +832,39 @@ private static function backupQualitySummary(BackupSet $record): \App\Support\Ba
|
|||||||
|
|
||||||
return app(BackupQualityResolver::class)->summarizeBackupSet($record);
|
return app(BackupQualityResolver::class)->summarizeBackupSet($record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function backupHealthContinuityAssessment(BackupSet $record): ?TenantBackupHealthAssessment
|
||||||
|
{
|
||||||
|
$requestedReason = request()->string('backup_health_reason')->toString();
|
||||||
|
|
||||||
|
if (! in_array($requestedReason, [
|
||||||
|
TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE,
|
||||||
|
TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED,
|
||||||
|
], true)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var TenantBackupHealthResolver $resolver */
|
||||||
|
$resolver = app(TenantBackupHealthResolver::class);
|
||||||
|
$assessment = $resolver->assess((int) $record->tenant_id);
|
||||||
|
|
||||||
|
if ($assessment->latestRelevantBackupSetId !== (int) $record->getKey()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($assessment->primaryReason !== $requestedReason) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $assessment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function backupHealthContinuityLabel(TenantBackupHealthAssessment $assessment): string
|
||||||
|
{
|
||||||
|
return match ($assessment->primaryReason) {
|
||||||
|
TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE => 'Latest backup is stale',
|
||||||
|
TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED => 'Latest backup is degraded',
|
||||||
|
default => ucfirst($assessment->posture),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Filament\Resources\BackupSetResource\Pages;
|
namespace App\Filament\Resources\BackupSetResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\BackupSetResource;
|
use App\Filament\Resources\BackupSetResource;
|
||||||
|
use App\Support\BackupHealth\TenantBackupHealthAssessment;
|
||||||
use App\Support\Filament\CanonicalAdminTenantFilterState;
|
use App\Support\Filament\CanonicalAdminTenantFilterState;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
@ -40,4 +41,14 @@ protected function getTableEmptyStateActions(): array
|
|||||||
BackupSetResource::makeCreateAction(),
|
BackupSetResource::makeCreateAction(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSubheading(): ?string
|
||||||
|
{
|
||||||
|
return match (request()->string('backup_health_reason')->toString()) {
|
||||||
|
TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS => 'No usable completed backup basis is currently available for this tenant.',
|
||||||
|
TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE => 'The latest backup detail is no longer available, so this view stays on the backup-set list.',
|
||||||
|
TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED => 'The latest backup detail is no longer available, so this view stays on the backup-set list.',
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user