# Research: Portfolio Triage Review State and Operator Progress

## Decision: Persist lightweight triage progress in `tenant_triage_reviews` instead of reusing formal review artifacts

### Rationale

The codebase already has formal governance artifacts such as `TenantReview`, `TenantReviewSection`, `ReviewPack`, and `EvidenceSnapshot`. Those models carry longer lifecycle, export, publication, and evidence semantics. Spec 189 needs only workspace-shared operator progress for current portfolio triage, so a dedicated lightweight table is the narrowest persisted truth that survives navigation and time without dragging formal review machinery into the operator flow.

### Alternatives considered

- Reuse `TenantReview`: rejected because it is governance-oriented, lifecycle-heavy, and semantically stronger than "someone checked the current triage concern".
- Reuse `ReviewPack`: rejected because it is an export or evidence artifact, not an operational progress record.
- Keep progress request-scoped only: rejected because current posture and arrival context do not survive registry revisits, handoff, or later sessions.

## Decision: Persist only manual states and derive `not_reviewed` plus `changed_since_review`

### Rationale

Only two states require durable operator intent: `reviewed` and `follow_up_needed`. `not_reviewed` can be derived from the absence of a matching active record, and `changed_since_review` can be derived from a current-fingerprint mismatch. Persisting all four would add state surface without adding new behavior.

### Alternatives considered

- Persist all visible UI states: rejected because `not_reviewed` and `changed_since_review` are derivative display outcomes, not independent product truth.
- Persist a generic workflow status family such as `open`, `in_progress`, `blocked`, `done`: rejected because the spec explicitly keeps workflow orchestration out of scope.

## Decision: Use one active row per workspace, tenant, and concern family, and resolve prior rows on superseding writes

### Rationale

The feature needs one coherent current review record for each workspace, tenant, and concern family. Resolving prior active rows and inserting a new active row on each superseding mutation preserves lightweight historical continuity without requiring a formal review timeline or collaboration engine.

### Alternatives considered

- Overwrite one row in place forever: rejected because it erases minimal continuity and makes later stale-detection harder to reason about.
- Create unbounded episode history every time the current concern disappears: rejected because the spec explicitly avoids complex review-epoch history and background cleanup machinery.

## Decision: Build fingerprints only from stable existing concern truth

### Rationale

`TenantBackupHealthResolver`, `TenantBackupHealthAssessment`, `RestoreSafetyResolver`, and the current portfolio-triage concern-family outputs already provide the stable state and reason inputs needed to detect meaningful change. Fingerprints should be built from those bounded values and supporting stable keys, not from translated copy, timestamps, or free-text.

### Alternatives considered

- Fingerprint raw rendered copy: rejected because wording and translation changes would create false `changed_since_review` results.
- Fingerprint every available diagnostic field: rejected because volatile or cosmetic values would cause churn and make review state noisy.

## Decision: Batch-load review state alongside existing posture batch resolvers

### Rationale

`WorkspaceOverviewBuilder` already batches backup-health and recovery-evidence truth for visible tenants, and `TenantResource` already reuses that posture truth at list level. The new review-state resolver should follow the same shape: load all active review rows for the current workspace and tenant set in one query, key them by tenant and concern family, and combine them with already-batched posture truth.

### Alternatives considered

- Resolve review state per row inside table columns or widget views: rejected because it would create N+1 behavior on `/admin` and `/admin/tenants`.
- Add a new materialized portfolio summary table: rejected because it would persist convenience projections rather than independent truth.

## Decision: Reuse BadgeCatalog, `UiEnforcement`, and existing portfolio-arrival surfaces

### Rationale

The repo already centralizes badge semantics through `BadgeDomain` plus domain badge mappers and centralizes Filament RBAC behavior through `UiEnforcement`. Recent portfolio-triage work already added `PortfolioArrivalContext` and `TenantTriageArrivalContinuity`. Reusing those paths keeps review state visually and behaviorally consistent with the rest of the operator product.

### Alternatives considered

- Add page-local badge colors or hand-built status pills: rejected because BADGE-001 forbids ad hoc status mappings.
- Add raw capability checks or role-string checks inline on actions: rejected because RBAC-UX requires canonical capability registry use and central helpers.
- Add a separate queue page for review-state actions: rejected because the operator flow already lives in overview, registry, and tenant arrival continuity.

## Decision: Keep review-state mutations lightweight, preview-confirmed, DB-only, and auditable without `OperationRun`

### Rationale

The new actions mutate only TenantPilot-internal portfolio progress and complete well below the long-running threshold. They should not start an `OperationRun` or call Microsoft Graph. To satisfy the constitution's write-safety expectations without creating a separate workflow engine, the UI can require a bounded pre-execution preview plus explicit confirmation before the service writes the new review state. The service can then emit bounded `AuditLog` entries via existing audit infrastructure while still keeping audit timeline UI out of scope.

### Alternatives considered

- Add an `OperationRun` for each review-state mutation: rejected because the actions are fast, local, and not operationally meaningful enough for Monitoring.
- Skip auditing entirely: rejected because the codebase already has lightweight audit infrastructure for internal writes and the constitution expects traceability for write paths.
- Add a dedicated review-state timeline page: rejected because the product decision is to stay lightweight and non-governance-oriented.