TenantAtlas/specs/225-assignment-hygiene/research.md
Ahmed Darrazi bc07e05659
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m27s
feat: add findings hygiene report and control catalog layering
2026-04-22 14:19:23 +02:00

5.6 KiB

Research: Assignment Hygiene & Stale Work Detection

Decision 1: Add a dedicated hygiene report instead of stretching My Findings or intake

Decision: Create a new canonical admin-plane page at /admin/findings/hygiene rather than adding one more fixed view to My Findings or the intake queue.

Rationale: My Findings already answers the personal execution question and intake already answers the shared unassigned-backlog question. Hygiene is a third operator question: which assigned or in-progress findings are no longer in a healthy workflow path across visible tenants. Mixing that into either existing surface would blur their current decision role.

Alternatives considered:

  • Add a Needs repair filter to My Findings. Rejected because broken assignments often no longer belong to the current operator, so the personal queue would still miss the cross-tenant repair problem.
  • Add another fixed mode to intake. Rejected because intake is explicitly about pre-assignment backlog, not unhealthy assigned work.

Decision 2: Reuse the existing workspace overview signal pattern

Decision: Extend WorkspaceOverviewBuilder with one findings hygiene signal rather than creating a new dashboard widget family.

Rationale: The repository already exposes my_findings_signal from WorkspaceOverviewBuilder, and /admin is already the accepted discoverability surface for workspace-wide findings follow-up. A second signal in the same builder keeps counts, phrasing, and drill-in behavior in one place.

Alternatives considered:

  • Build a dedicated Livewire widget. Rejected because the current overview already has a signal builder pattern and this feature does not need a new widget framework.
  • Make the report discoverable only through navigation. Rejected because the spec explicitly wants a small overview signal that exposes hidden workflow decay before operators start browsing deeper pages.

Decision 3: Use one narrow FindingAssignmentHygieneService

Decision: Add one focused service that owns hygiene classification, filtering, and unique-count aggregation for both the report and the overview signal.

Rationale: This feature has two real concrete consumers of the same derived truth: the canonical report and the overview signal. Duplicating issue classification in both places would drift quickly. A narrow service is the smallest justified shared seam and does not require a generic workflow-health framework.

Alternatives considered:

  • Duplicate the queries in the report page and WorkspaceOverviewBuilder. Rejected because tenant-visible scope, multi-reason counting, and stale-work anchoring would diverge under maintenance.
  • Introduce a broader workflow-health engine. Rejected because findings are the only real consumer today.

Decision 4: Keep broken-assignment truth bounded to current entitlement and existing user lifecycle

Decision: In v1, classify broken assignment from two existing truths only: the assignee no longer has current tenant entitlement, or the assignee user record is soft-deleted. Do not introduce absence, capacity, or shift scheduling models.

Rationale: The repository already enforces tenant membership as the true work boundary, and User already uses SoftDeletes. Those are real current truths. Anything broader would require new product modeling and would violate the spec's narrow scope.

Alternatives considered:

  • Add an explicit operator-availability state or calendar. Rejected because it introduces a new workflow domain and new persistence unrelated to the current slice.
  • Treat every owner-only finding as broken. Rejected because owner-only accountability is already a valid state under Spec 219 and belongs outside this hygiene slice unless assignment is actually broken.

Decision 5: Derive stale work from workflow activity, not observation freshness

Decision: Derive stale in progress from workflow activity anchors such as in_progress_at, reopened_at, and the latest existing finding workflow audit event (finding.assigned, finding.in_progress, finding.reopened) rather than from last_seen_at or generic updated_at.

Rationale: Observation pipelines update last_seen_at and may touch the record during recurring detection, which would falsely calm truly stale operator work. The stale question here is whether workflow moved, not whether the problem was re-observed by the system.

Alternatives considered:

  • Use last_seen_at. Rejected because recurring detection would reset the stale window even when no operator work happened.
  • Use updated_at. Rejected because model updates are broader than workflow activity and would make stale classification depend on incidental writes.
  • Add a new last_workflow_activity_at column. Rejected because the spec explicitly prefers derivation over new persistence.

Decision 6: Keep repair on the existing finding detail surface

Decision: The hygiene report remains read-first and uses row click to drill into the existing tenant finding detail route for repair.

Rationale: The current findings workflow already has audited assignment and lifecycle actions on the detail surface. Recreating those actions on the hygiene report would create a second mutation surface and complicate action hierarchy, RBAC, and audit semantics.

Alternatives considered:

  • Add inline reassignment actions to the report. Rejected because the report would stop being a pure repair-identification surface and would duplicate the existing finding workflow UI.
  • Add a bulk repair action. Rejected because the spec explicitly keeps automation and redistribution out of scope for v1.