TenantAtlas/specs/110-ops-ux-enforcement/research.md
ahmido f13a4ce409 feat(110): Ops-UX enterprise start/dedup standard (repo-wide) (#134)
Implements Spec 110 Ops‑UX Enforcement and applies the repo‑wide “enterprise” standard for operation start + dedup surfaces.

Key points
- Start surfaces: only ephemeral queued toast (no DB notifications for started/queued/running).
- Dedup paths: canonical “already queued” toast.
- Progress refresh: dispatch run-enqueued browser event so the global widget updates immediately.
- Completion: exactly-once terminal DB notification on completion (per Ops‑UX contract).

Tests & formatting
- Full suite: 1738 passed, 8 skipped (8477 assertions).
- Pint: `vendor/bin/sail bin pint --dirty --format agent` (pass).

Notable change
- Removed legacy `RunStatusChangedNotification` (replaced by the terminal-only completion notification policy).

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #134
2026-02-24 09:30:15 +00:00

4.8 KiB
Raw Blame History

Phase 0 Research: Ops-UX Enforcement & Cleanup

This research document resolves implementation uncertainties for the enforcement spec and records key decisions.

Decision 1 — Implement guards as Pest “architecture tests” (filesystem scan)

Decision: Implement Guard A/B/C as Pest tests that scan PHP source files in app/ (and for Guard C also tests/) and fail CI with actionable file + snippet output.

Rationale:

  • Runs in the same CI pipeline as the rest of the test suite (no new tooling step).
  • Enforcement is repo-wide and does not rely on developer discipline or code review memory.
  • Can produce highly actionable failure output (file path + snippet) without external dependencies.

Alternatives considered:

  • PHPStan custom rules: higher precision, but adds configuration + toolchain scope.
  • nikic/php-parser AST scanning: high precision but adds a dependency (out of bounds per repo constraints).

Decision 2 — Guard A detection uses tokenizer-assisted scanning (not pure regex)

Decision: Implement Guard A using PHPs built-in tokenizer (token_get_all()) to detect ->update([...]) call sites and then inspect the array literal for status / outcome keys.

Rationale:

  • Pure multi-line regex is brittle with nested arrays, comments, and strings.
  • Tokenization gives a dependency-free way to avoid common false positives and to compute an approximate line number.
  • The specs intended heuristic (“->update([...]) block contains status/outcome keys”) maps naturally to tokens.

Alternatives considered:

  • Regex-only DOTALL matching with string/comment skipping ((*SKIP)(*F)): simpler, but can still drift across the wrong closing bracket and become noisy.
  • Manual char-by-char scanner after a simple “find start” regex: workable, but token-based is typically easier to maintain in PHP.

Repo evidence:

  • Canonical status/outcome transitions live in OperationRunService::updateRun().
  • Violations exist in jobs/services that call $operationRun->update([... 'status' => ..., 'outcome' => ...]) directly (enumerated in the spec).

Decision 3 — Guard B flags “OperationRun in scope” + “DB notification emission”

Decision: Implement Guard B as a two-signal scan:

  • OperationRun signal: file contains any of: use App\\Models\\OperationRun;, OperationRun, $this->operationRun, $operationRun.
  • DB emission signal: file contains either sendToDatabase( or ->notify(.
  • Allowlist: only app/Services/OperationRunService.php and app/Notifications/OperationRunCompleted.php.

Rationale:

  • Enforces the constitution: completion notifications are centralized, queued/running DB notifications are banned.
  • Intentionally errs on the side of flagging new ad-hoc notification producers.

Alternatives considered:

  • “DB emission only” = sendToDatabase( and ignore ->notify(: lower noise, but would miss notify-based DB notifications.
  • Restrict scan to app/Jobs/** and app/Services/**: lower noise, but spec requires app/**/*.php for defense-in-depth.

Repo evidence (high-level):

  • OperationRunService currently emits both OperationRunCompleted and OperationRunQueued via ->notify(...).
  • There are multiple job files with ->sendToDatabase(...) in code paths that also handle an OperationRun.
  • There are Filament resource actions that both dispatch jobs with $operationRun and call ->sendToDatabase(...) for queued feedback. These are queued DB notifications and conflict with FR-004.

Decision 4 — Ban queued DB notifications by changing service defaults

Decision: Change OperationRunService::dispatchOrFail(..., bool $emitQueuedNotification = true) (and any upstream helper that takes emitQueuedNotification) so the default is false, and ensure no call site opts back into queued DB notifications.

Rationale:

  • Matches FR-004 / FR-012b: queued/running DB notifications are forbidden repo-wide.
  • Prevents new call sites from accidentally enabling queued DB notifications by omission.

Alternatives considered:

  • Delete OperationRunQueued entirely: higher churn; leaving the class unused is acceptable as long as its never emitted.
  • Keep default true but require opt-out everywhere: violates FR-012b and is easy to regress.

Decision 5 — “Exactly once terminal notification” relies on previousStatus guard

Decision: Rely on OperationRunService::updateRun() behavior that emits OperationRunCompleted only when transitioning into Completed from a non-completed status.

Rationale:

  • Centralizes “exactly once” semantics in a single place.
  • Keeps jobs/services free of notification-specific logic.

Alternatives considered:

  • Add additional DB-level dedupe: unnecessary if the service is the only producer and transitions are canonical.