spec: 078 Operations tenantless canonical migration
- Single canonical run detail at /admin/operations/{run}
- Decommission auto-generated tenant-scoped pages (list + view)
- Secure 302 redirects for legacy URLs (deny-as-not-found)
- Infolist reuse strategy (InteractsWithInfolists + fallback)
- KPI header hidden in tenantless mode (Phase 1)
- Dead code cleanup (OperationsDetail.php)
- 10 test specifications covering redirects, 404 semantics, rendering
- Quality checklist: all items pass
This commit is contained in:
parent
fb1046c97a
commit
58758a5bcf
@ -0,0 +1,36 @@
|
||||
# Specification Quality Checklist: Operations Tenantless Canonical Migration
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-02-06
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs) — Implementation Notes section is clearly marked non-normative; FR-078-002 mentions trait names as implementation guidance only
|
||||
- [x] Focused on user value and business needs — all user stories describe user outcomes, not system internals
|
||||
- [x] Written for non-technical stakeholders — principles and requirements use domain language
|
||||
- [x] All mandatory sections completed — User Scenarios, Requirements, Success Criteria all present
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain — all decisions resolved (302 vs 301, KPI deferral, infolist approach)
|
||||
- [x] Requirements are testable and unambiguous — each FR has specific verifiable behavior
|
||||
- [x] Success criteria are measurable — SC-001 through SC-006 all have concrete pass/fail conditions
|
||||
- [x] Success criteria are technology-agnostic (no implementation details) — criteria reference URLs and user outcomes, not code
|
||||
- [x] All acceptance scenarios are defined — 4 user stories with given/when/then scenarios
|
||||
- [x] Edge cases are identified — 5 edge cases documented including null workspace, non-numeric record, null tenant
|
||||
- [x] Scope is clearly bounded — Non-Goals section explicitly excludes KPI workspace-scoping, alerts engine, capability-gating
|
||||
- [x] Dependencies and assumptions identified — baseline routes, existing link helpers, constitution alignment documented
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria — FR-078-001 through FR-078-012 each specify observable behavior
|
||||
- [x] User scenarios cover primary flows — canonical view, legacy redirects, contextual nav, list regression
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria — SC-001 (one canonical URL), SC-003 (secure redirects), SC-005 (verification report tenantless)
|
||||
- [x] No implementation details leak into specification — Implementation Notes section is non-normative; core spec is behavior-focused
|
||||
|
||||
## Notes
|
||||
|
||||
- FR-078-002 includes implementation guidance (trait names) as a non-normative hint for planners; the normative requirement is "reuse infolist schema" regardless of approach.
|
||||
- Open decision on 301 vs 302 documented; 302 chosen as Phase 1 default with clear promotion path.
|
||||
- KPI workspace-scoping explicitly deferred (Non-Goals + FR-078-008) — keeps migration scope focused.
|
||||
353
specs/078-operations-tenantless-canonical/spec.md
Normal file
353
specs/078-operations-tenantless-canonical/spec.md
Normal file
@ -0,0 +1,353 @@
|
||||
# Feature Specification: Operations Tenantless Canonical Migration
|
||||
|
||||
**Feature Branch**: `078-operations-tenantless-canonical`
|
||||
**Created**: 2026-02-06
|
||||
**Status**: Draft
|
||||
**Stack**: Filament v5 + Livewire v4 (native only)
|
||||
**Input**: Eliminate dual "run detail" surfaces (tenant-scoped Filament view vs tenantless canonical viewer) and make Operations truly canonical, supportable, and secure.
|
||||
|
||||
---
|
||||
|
||||
## 0. Executive Summary
|
||||
|
||||
Operations is already **canonical at the index and tenantless detail** (`/admin/operations`, `/admin/operations/{run}`), but **auto-generated tenant-scoped Filament resource routes** still exist as side-effects of panel discovery:
|
||||
|
||||
- Tenant-scoped detail: `/admin/t/{tenant}/operations/r/{record}`
|
||||
- Tenant-scoped list: `/admin/t/{tenant}/operations`
|
||||
|
||||
This spec makes the tenantless detail page **the only run detail view**, removes the auto-generated tenant-scoped pages, and provides **secure, deny-as-not-found redirects** for legacy URLs—without introducing non-native UI frameworks.
|
||||
|
||||
---
|
||||
|
||||
## 1. Goals
|
||||
|
||||
1. **Single canonical run detail view** — `/admin/operations/{run}`
|
||||
2. **Remove auto-generated tenant-scoped pages** — decommission both `/admin/t/{tenant}/operations/r/{record}` and `/admin/t/{tenant}/operations`
|
||||
3. **Secure legacy compatibility** — redirect legacy URLs with membership + tenant-match checks (no leakage)
|
||||
4. **No duplication of UI logic** — reuse `OperationRunResource::infolist()` in the tenantless viewer
|
||||
5. **Enterprise security semantics** — non-member → 404, no existence leakage via redirects
|
||||
|
||||
---
|
||||
|
||||
## 2. Non-Goals
|
||||
|
||||
- Building a new operations dashboard
|
||||
- Introducing an alerts engine
|
||||
- Changing operation execution semantics
|
||||
- Implementing fine-grained `operations.view` capabilities (access remains workspace-membership)
|
||||
- Workspace-scoped KPI header (tracked separately; header hidden in tenantless mode for Phase 1)
|
||||
|
||||
---
|
||||
|
||||
## 3. Current State (Baseline)
|
||||
|
||||
**Registered routes (today):**
|
||||
|
||||
| Route | Pattern | Name | Handler | Scope |
|
||||
|-------|---------|------|---------|-------|
|
||||
| Canonical list | `GET /admin/operations` | `admin.operations.index` | `Operations.php` (custom page) | Workspace (session) |
|
||||
| Canonical detail | `GET /admin/operations/{run}` | `admin.operations.view` | `TenantlessOperationRunViewer.php` | Workspace membership |
|
||||
| Auto-generated list | `GET /admin/t/{tenant}/operations` | `filament.admin.resources.operations.index` | `ListOperationRuns.php` | Tenant-scoped |
|
||||
| Auto-generated view | `GET /admin/t/{tenant}/operations/r/{record}` | `filament.admin.resources.operations.view` | `ViewOperationRun.php` | Tenant-scoped |
|
||||
|
||||
**Key constraints:**
|
||||
- Tenant-scoped routes exist because `OperationRunResource` is auto-discovered in a tenant panel (`AdminPanelProvider::discoverResources`).
|
||||
- All production "View run" links already point to canonical tenantless detail via `OperationRunLinks::view()` → `OperationRunLinks::tenantlessView()`.
|
||||
- The resource already declares `$isScopedToTenant = false` and `$shouldRegisterNavigation = false`.
|
||||
|
||||
**Link helper state:**
|
||||
- `OperationRunLinks::view($run, $tenant)` delegates to `tenantlessView($run)` — the `$tenant` parameter is a no-op.
|
||||
- `OperationRunLinks::related($run, $tenant)` returns up to 11 contextual links (Policies, Inventory, Drift, Backup Sets, Provider Connections, etc.).
|
||||
- `OperationRunUrl::view()` and `OperationRunUrl::index()` are thin wrappers around `OperationRunLinks`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Enterprise Principles (Normative)
|
||||
|
||||
### P-078-001 — Canonical deep links
|
||||
There MUST be exactly one canonical URL to open a run: `/admin/operations/{run}`.
|
||||
|
||||
### P-078-002 — No tenant context dependency
|
||||
Canonical run detail MUST render without tenant context and MUST NOT require `/admin/t/{tenant}`.
|
||||
|
||||
### P-078-003 — Deny-as-not-found
|
||||
Any user not entitled to view a run MUST receive **404**, not 403. (Constitution: RBAC-UX-002)
|
||||
|
||||
### P-078-004 — No leakage via redirects
|
||||
Legacy routes MUST NOT leak existence of runs or tenant association; redirects MUST be authorization-aware.
|
||||
|
||||
### P-078-005 — Filament-native
|
||||
Use Filament pages/resources/infolists/actions and Livewire v4 only. No custom SPA routing.
|
||||
|
||||
---
|
||||
|
||||
## User Scenarios & Testing
|
||||
|
||||
### User Story 1 — View operation run via canonical URL (Priority: P1)
|
||||
|
||||
A workspace member clicks a "View run" link (from notification, widget, or operations list) and sees the full run detail at `/admin/operations/{run}` — regardless of whether the run has a tenant or not.
|
||||
|
||||
**Why this priority**: This is the core feature — the single canonical detail surface that replaces the dual-surface.
|
||||
|
||||
**Independent Test**: Create runs with and without `tenant_id`, navigate to `/admin/operations/{run}`, assert all sections render (summary, target scope, verification report, context JSON).
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a run with `tenant_id` set and user is workspace member, **When** user visits `/admin/operations/{run}`, **Then** full detail renders including target scope, verification report (if present), summary counts, and context JSON.
|
||||
2. **Given** a run with `tenant_id = null` (e.g., onboarding run), **When** user visits `/admin/operations/{run}`, **Then** detail renders without crash; target scope shows "No target scope details recorded."
|
||||
3. **Given** a run with a verification report in context, **When** user visits canonical detail, **Then** verification report section renders correctly with badge rendering and acknowledgements — even though `Filament::getTenant()` returns null.
|
||||
4. **Given** user is NOT a workspace member, **When** user visits `/admin/operations/{run}`, **Then** 404 (deny-as-not-found).
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 — Legacy tenant-scoped URL redirects safely (Priority: P2)
|
||||
|
||||
A user has a bookmarked or old notification link pointing to `/admin/t/{tenant}/operations/r/{record}`. The system redirects to the canonical URL if authorized, or returns 404 if not.
|
||||
|
||||
**Why this priority**: Prevents broken links from historical notifications/bookmarks without leaking information.
|
||||
|
||||
**Independent Test**: Hit legacy URL with authorized/unauthorized users and matching/mismatching tenant IDs; assert correct redirect or 404.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** authorized member + matching `tenant_id`, **When** user visits `/admin/t/{tenant}/operations/r/{record}`, **Then** 302 redirect to `/admin/operations/{record}`.
|
||||
2. **Given** non-member, **When** user visits legacy URL, **Then** 404 (no redirect, no existence leakage).
|
||||
3. **Given** member but URL has wrong `{tenant}` (doesn't match `run.tenant_id`), **When** user visits legacy URL, **Then** 404.
|
||||
4. **Given** run doesn't exist, **When** user visits legacy URL, **Then** 404.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 — Contextual navigation from run detail (Priority: P2)
|
||||
|
||||
On the canonical detail page, a workspace member sees contextual "Open" actions (e.g., "Open tenant", "Policies", "Backup Set", "Provider Connection") based on the run's context — using the existing `OperationRunLinks::related()` mechanism.
|
||||
|
||||
**Why this priority**: Replaces the "Admin details" back-link with richer, already-implemented navigation.
|
||||
|
||||
**Independent Test**: Create runs of different types, verify that the related links appear in the header actions group.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a run of type `policy.sync` with `policy_id` in context, **When** viewing canonical detail, **Then** header shows "Open" group with links to Operations index, Policy, and Policies list.
|
||||
2. **Given** a run with `tenant_id` and user is tenant member, **When** viewing canonical detail, **Then** related links include tenant-scoped resources (via `OperationRunLinks::related()`).
|
||||
3. **Given** a run with `tenant_id = null`, **When** viewing canonical detail, **Then** no tenant-specific related links appear; only generic links (Operations index).
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 — Operations list remains workspace-scoped (Priority: P3)
|
||||
|
||||
The `/admin/operations` page continues to show all runs for the current workspace, with an optional tenant default filter when tenant context is active.
|
||||
|
||||
**Why this priority**: Existing behavior; this story ensures no regression during migration.
|
||||
|
||||
**Independent Test**: Visit `/admin/operations` with and without tenant context; verify workspace scoping and default filter behavior.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** workspace context set but no tenant context, **When** visiting `/admin/operations`, **Then** all workspace runs shown.
|
||||
2. **Given** tenant context active, **When** visiting `/admin/operations`, **Then** tenant filter defaults to active tenant but can be cleared to show all.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- Run with `workspace_id = 0` or null → 404 (existing behavior in `OperationRunPolicy::view`)
|
||||
- Run exists but requesting user has no workspace membership → 404 (deny-as-not-found)
|
||||
- Verification report section with `Filament::getTenant()` returning null → falls back to `OperationRunLinks::tenantlessView()` for previous-run URLs (already handled in infolist)
|
||||
- Legacy redirect with non-numeric `{record}` → Laravel model binding returns 404 naturally
|
||||
- Legacy redirect for `/admin/operations/r/{record}` (the `/r/` slug variant) → redirect to `/admin/operations/{record}`
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
**Constitution alignment (RBAC-UX):**
|
||||
- Authorization plane: Workspace-level (not tenant-level). Operations detail requires workspace membership only.
|
||||
- 404 vs 403: Non-member → 404 (RBAC-UX-002). No capability-gating for view-only operations access in this spec.
|
||||
- Server-side enforcement: `OperationRunPolicy::view()` + `WorkspaceMembership` check in `TenantlessOperationRunViewer::mount()`.
|
||||
- Legacy redirects enforce authorization before redirecting (P-078-004).
|
||||
- Global search: `OperationRunResource` has `$shouldRegisterNavigation = false` and no `$recordTitleAttribute` — not globally searchable.
|
||||
|
||||
**Constitution alignment (OPS-EX-AUTH-001):** Not applicable — no auth handshakes involved.
|
||||
|
||||
**Constitution alignment (BADGE-001):** No badge changes. Existing badge mappings (`OperationRunStatus`, `OperationRunOutcome`) are reused via shared `BadgeRenderer` in the reused infolist.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
#### 5.1 Canonical Run Detail — Feature-Complete Tenantless Page
|
||||
|
||||
- **FR-078-001**: `GET /admin/operations/{run}` MUST display: run summary (status/outcome/timestamps/initiator), target scope, verification report (DB-only), summary counts, failure summary, and context JSON (redacted).
|
||||
- **FR-078-002**: Tenantless detail page MUST reuse `OperationRunResource::infolist()` schema by implementing `HasInfolists` + `InteractsWithInfolists` on `TenantlessOperationRunViewer` and delegating to a shared static infolist builder method. Implementation approach: add `use InteractsWithInfolists; implements HasInfolists` to the page, define `public function operationRunInfolist(Schema $schema): Schema` that calls `OperationRunResource::infolist($schema)`, and render via `{{ $this->operationRunInfolist }}` in the Blade view. If the `InteractsWithInfolists` trait proves incompatible with a standalone Filament Page in v5, the fallback approach is extracting the infolist schema components into a shared static builder and rendering via a `ViewEntry` component in the Blade template.
|
||||
- **FR-078-003**: Tenantless detail MUST reuse `OperationRunLinks::related($run, $tenant)` for contextual header actions (replacing the removed "Admin details" button). If `$run->tenant` is null, only generic links (Operations index) appear.
|
||||
|
||||
#### 5.2 Decommission Auto-Generated Tenant-Scoped Pages
|
||||
|
||||
- **FR-078-004**: Remove the `'view'` page entry from `OperationRunResource::getPages()` and delete `ViewOperationRun.php`. This eliminates the auto-generated `/admin/t/{tenant}/operations/r/{record}` route.
|
||||
- **FR-078-011**: Remove the `'index'` page entry from `OperationRunResource::getPages()` and delete `ListOperationRuns.php`. This eliminates the auto-generated `/admin/t/{tenant}/operations` route. The resource retains only its `table()` and `infolist()` schema builders for reuse by the custom pages.
|
||||
|
||||
#### 5.3 Legacy Redirect Compatibility (Secure)
|
||||
|
||||
- **FR-078-005**: Add a legacy redirect handler for `GET /admin/t/{tenant}/operations/r/{record}`:
|
||||
1. Resolve the run by `{record}`.
|
||||
2. If user is not a member of `run.workspace_id` → **404**.
|
||||
3. If `run.tenant_id` is not null and does not match `{tenant}` → **404**.
|
||||
4. Redirect **302** to `/admin/operations/{record}`.
|
||||
|
||||
- **FR-078-006**: Add a legacy redirect handler for `GET /admin/operations/r/{record}`:
|
||||
1. If user cannot view the run (policy check) → **404**.
|
||||
2. Redirect **302** to `/admin/operations/{record}`.
|
||||
|
||||
- **FR-078-012**: Add a legacy redirect handler for `GET /admin/t/{tenant}/operations`:
|
||||
1. Redirect **302** to `/admin/operations`.
|
||||
|
||||
Note: No auth check needed since `/admin/operations` already enforces workspace membership.
|
||||
|
||||
- All redirects use **302** (not 301) during rollout phase. 301 may be adopted once stable.
|
||||
|
||||
#### 5.4 KPI Header Handling (Phase 1 Simplification)
|
||||
|
||||
- **FR-078-008**: `OperationsKpiHeader` widget MUST continue to render when tenant context is available. When no tenant context exists (tenantless pages), the KPI header SHOULD be hidden (not rendered) rather than showing zeros. Full workspace-scoped KPI support is deferred to a follow-up spec.
|
||||
|
||||
- **FR-078-009**: Polling/active-run queries on the canonical list page (`Operations.php`) MUST handle `tenant_id = null` runs and tenantless rendering safely. The existing workspace-scoped query in `Operations.php` already handles this; no breaking changes expected.
|
||||
|
||||
#### 5.5 Dead Code Cleanup
|
||||
|
||||
- **FR-078-010**: Delete `app/Livewire/Monitoring/OperationsDetail.php` and its Blade view `resources/views/livewire/monitoring/operations-detail.blade.php`. This component is unreferenced dead code that enforces an obsolete tenant-only abort check.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **OperationRun**: The central model. Has `workspace_id` (required for authorization), `tenant_id` (nullable — onboarding/provider-check runs may lack it), `context` (JSONB with target_scope, verification_report, etc.), `summary_counts`, `failure_summary`.
|
||||
- **WorkspaceMembership**: The authorization boundary. View access requires membership in the run's workspace.
|
||||
- **OperationRunLinks**: Centralized link helper. `view()` → `tenantlessView()` (already canonical). `related()` provides contextual navigation links.
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Exactly one canonical run detail URL exists: `/admin/operations/{run}`. The route `filament.admin.resources.operations.view` is no longer registered.
|
||||
- **SC-002**: All "View run" links across notifications, widgets, tables, and jobs resolve to `/admin/operations/{run}` (no tenant-scoped detail links remain).
|
||||
- **SC-003**: Legacy tenant-scoped URLs redirect securely (authorized → 302 to canonical; unauthorized → 404) with zero information leakage.
|
||||
- **SC-004**: Runs with `tenant_id = null` render on canonical detail without errors.
|
||||
- **SC-005**: Verification report section renders correctly on canonical detail when `Filament::getTenant()` returns null.
|
||||
- **SC-006**: All existing Pest tests pass after migration (no regressions).
|
||||
|
||||
---
|
||||
|
||||
## Security & RBAC
|
||||
|
||||
### SR-078-001 — View permissions
|
||||
- Viewing operations list and run detail requires workspace membership (baseline).
|
||||
- Non-member → 404 (deny-as-not-found, per RBAC-UX-002).
|
||||
- No fine-grained capabilities for operations view in this spec.
|
||||
|
||||
### SR-078-002 — No sensitive leakage in context JSON
|
||||
- Context JSON shown in UI uses allowlist-based redaction (existing behavior).
|
||||
- The verification report section already sanitizes sensitive data.
|
||||
|
||||
### SR-078-003 — Legacy redirect authorization
|
||||
- Legacy redirects MUST verify workspace membership before issuing any redirect.
|
||||
- A redirect to `/admin/operations/{run}` MUST NOT be issued if the user would receive 404 on the target page.
|
||||
|
||||
---
|
||||
|
||||
## UX Requirements
|
||||
|
||||
### UX-078-001 — Consistent CTA label
|
||||
All run CTAs MUST use the canonical label per ux-contracts.md:
|
||||
- **"View run"** (exact casing)
|
||||
|
||||
### UX-078-002 — Context navigation via related links
|
||||
Canonical detail header MUST show an "Open" action group populated by `OperationRunLinks::related()`. This provides all relevant contextual links (Operations index, Provider Connection, Policies, Backup Sets, Restore Runs, Drift, Inventory, etc.) based on run type and context.
|
||||
|
||||
### UX-078-003 — Empty/missing target scope
|
||||
If target scope is missing: show the existing non-blocking text "No target scope details were recorded for this run." Do not crash; degrade gracefully.
|
||||
|
||||
---
|
||||
|
||||
## Tests (Mandatory)
|
||||
|
||||
### T-078-001 — Canonical run detail renders without tenant context
|
||||
- Create run with `tenant_id = null` and with `tenant_id` set
|
||||
- Assert `/admin/operations/{run}` renders summary sections without crash
|
||||
|
||||
### T-078-002 — Tenant-scoped legacy detail redirects securely
|
||||
- Authorized member + matching tenant → 302 to `/admin/operations/{run}`
|
||||
- Non-member → 404 (no redirect)
|
||||
- Mismatched `tenant_id` in URL → 404
|
||||
- Non-existent run → 404
|
||||
|
||||
### T-078-003 — Legacy tenantless /r/ redirect
|
||||
- `/admin/operations/r/{run}` redirects to `/admin/operations/{run}` for authorized user
|
||||
- Non-member → 404
|
||||
|
||||
### T-078-004 — No auto-generated tenant-scoped routes exist
|
||||
- Assert route names `filament.admin.resources.operations.view` and `filament.admin.resources.operations.index` are not registered (or return 404)
|
||||
|
||||
### T-078-005 — No "Admin details" link on canonical detail
|
||||
- Assert canonical detail page does not render `/admin/t/.../operations/r/...` links
|
||||
|
||||
### T-078-006 — KPI header hidden in tenantless mode
|
||||
- On `/admin/operations` without tenant context, KPI header does not render (or renders gracefully)
|
||||
|
||||
### T-078-007 — DB-only rendering
|
||||
- Rendering canonical detail does not dispatch jobs or perform HTTP calls (existing guard tests remain valid)
|
||||
|
||||
### T-078-008 — Verification report renders on tenantless detail
|
||||
- Create run with verification report in context and `tenant_id` set
|
||||
- Visit `/admin/operations/{run}` without `Filament::getTenant()` set
|
||||
- Assert verification report section renders (badge, acknowledgements, change indicator with tenantless previous-run URL)
|
||||
|
||||
### T-078-009 — Tenant-scoped list redirect
|
||||
- `/admin/t/{tenant}/operations` redirects (302) to `/admin/operations`
|
||||
|
||||
### T-078-010 — Related links appear on canonical detail
|
||||
- Create run of type `restore.execute` with `restore_run_id` in context
|
||||
- Assert header actions include "Restore Run" link
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- ✅ Exactly one canonical run detail URL: `/admin/operations/{run}`
|
||||
- ✅ Auto-generated tenant-scoped routes (list + view) are removed; legacy URLs redirect securely or 404 without leakage
|
||||
- ✅ No "Admin details" / tenant-scoped back-links from canonical pages
|
||||
- ✅ Operations list works in workspace mode; tenant context only adds default filter
|
||||
- ✅ Runs with `tenant_id = null` display safely
|
||||
- ✅ Verification report renders correctly without tenant context
|
||||
- ✅ Related contextual links (from `OperationRunLinks::related()`) replace the removed "Admin details" button
|
||||
- ✅ Dead code (`OperationsDetail.php` + Blade view) removed
|
||||
- ✅ Tests cover redirects, 404 semantics, infolist rendering, and canonical link rules
|
||||
- ✅ Implementation is Filament v5 + Livewire v4 native only
|
||||
- ✅ All redirects use 302 during rollout
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes (Non-Normative)
|
||||
|
||||
### Infolist Reuse Strategy
|
||||
|
||||
**Primary approach**: Add `implements HasInfolists` and `use InteractsWithInfolists` to `TenantlessOperationRunViewer`. Define a named infolist method that delegates to `OperationRunResource::infolist()`. Render via `{{ $this->operationRunInfolist }}` in the Blade template, replacing the current hand-crafted HTML.
|
||||
|
||||
**Fallback approach** (if trait is incompatible with standalone Filament Page in v5): Extract the infolist schema into a shared static builder method on `OperationRunResource` and render it through a `ViewEntry` component in the existing Blade template structure.
|
||||
|
||||
Both approaches require prototyping in a session branch before committing to a plan.
|
||||
|
||||
### Resource After Decommission
|
||||
|
||||
After removing both page entries from `getPages()`, `OperationRunResource` becomes a "headless" resource — it provides `table()` and `infolist()` schema builders reused by:
|
||||
- `Operations.php` (custom list page, via `OperationRunResource::table($table)`)
|
||||
- `TenantlessOperationRunViewer` (canonical detail, via infolist delegation)
|
||||
|
||||
The resource class itself is retained; only its auto-generated routes are eliminated.
|
||||
|
||||
### KPI Header Deferral
|
||||
|
||||
`OperationsKpiHeader` currently queries 6 times by `tenant_id` and uses `ActiveRuns::existForTenant()`. Full workspace-scoping requires adding `existForWorkspace()` to `ActiveRuns` and refactoring all 6 queries. This is deferred to a separate spec to keep this migration focused. Phase 1 simply hides the KPI header when tenant context is absent.
|
||||
|
||||
---
|
||||
|
||||
## Open Decisions
|
||||
|
||||
- **301 vs 302**: Using 302 during rollout. Can be promoted to 301 once no new legacy links are being created.
|
||||
- **"Copy JSON" capability-gating**: Recommended for enterprise environments but not in scope for this spec.
|
||||
Loading…
Reference in New Issue
Block a user