Back to Blog

Assignee Dimension, Fleet Guards, and a Lineage Fix for an Unclaimable Task

By · 7 min read
Enterprise Platform Access Control Observability TypeScript

A quiet week on the enterprise workflow platform still produced one genuinely tricky lineage bug, two access-control gaps worth understanding precisely, and the kickoff of a real analytics rebuild. None of these individually needed a deep dive — together they're a good snapshot of what week-to-week maintenance on a mature review pipeline actually looks like once the big features are built.

A permission check that's technically correct but inconsistent with its sibling checks is still a bug — the inconsistency itself is what a user experiences.

A task that couldn't be claimed because its own history was valid

The sharpest bug this week was a task that became permanently unclaimable after passing through External QC. The root cause was almost paradoxical: the lineage walk that reconstructs a task version's ancestry explicitly skips over External QC markers when building the chain — by design, since those markers aren't part of the content lineage — but then required the node where the walk stopped to be markerless. When the walk legitimately stopped at a committed External QC marker, the code treated a valid, expected state as an error and threw. That turned GET /api/v1/tasks/<id>/versions into a 500, which failed the claim pre-check, which meant the claim button simply never submitted.

The fix adds a dedicated helper that recognizes this specific case — stopping at a committed External QC marker that's an ancestor, not the target — and returns the marker's own source version as the chain base instead of throwing. The bug is a good reminder that "skip X when building the chain" and "X should never appear in the chain" are two different rules, and conflating them turns a deliberately-ignored case into an unhandled one.

A fleet guard that was stricter than the page it was feeding

Separately, ADMIN-tier users had been granted access to the platform's /analytics page, but the Talent Performance tab and the Overview's "Active now" list both depend on an underlying fleet-status endpoint that was still locked to PLATFORM_ADMIN only. Because the Next.js route simply forwards the caller's bearer token straight through to that backend, an ADMIN user's otherwise-valid request got a 403 from the layer underneath the page they were already allowed to view. On screen, that surfaced as a generic "Failed to load analytics data" message on one tab and a silently empty "Active now" list on another — neither of which points a reader back to the actual cause.

The fix widens the fleet guard to accept both ADMIN and PLATFORM_ADMIN, mirroring a pattern the task-archive endpoints already used elsewhere in the platform. The lesson is narrow but recurs constantly in systems with more than one permission tier: granting access to a page is not the same as granting access to every endpoint that page calls, and the gap between the two shows up as a confusing error far from its actual cause.

A status chip that disagreed with the badge right next to it

A cancelled production run displayed "Cancelled" correctly in both the task list and the detail page's header badge — but the External QC step's own chip, sitting in the same view, still read "QC Pending." The cause: External QC is modeled as a document-driven step, so the general status-overlay logic deliberately skips it, deferring to a separate resolver that reads the gate document directly. That resolver never checked whether the overall task had been cancelled. The per-step chip rendering path had simply never been taught that "cancelled" was a state it needed to represent. The fix teaches it. It's a small bug, but a visible one — nothing erodes trust in a status dashboard faster than two labels on the same screen disagreeing with each other.

A retry budget charging the wrong party

External QC's retry-budget logic had the actors backwards relative to every other gate on the platform. Where Trainer and Final Review charge retries against the reviewer's actions, External QC had been charging the budget against the QC team's own feedback upload instead. The fix inverts it: a feedback upload with non-pass results and an exhausted retry budget is now accepted regardless, with the reviewer locked out from sending it back further; the retry is consumed specifically on a reviewer's own send-back action, not on the QC team doing their job. Getting this backwards meant the party being held accountable for a limited resource was the one least able to control whether it got consumed.

Rebuilding analytics on one shared catalog

The largest piece of work this week was the first PR in a three-PR stack building out a real analytics layer: an assignee dimension, a per-task talent drill-down, time-and-iterations tracking, and CSV export, covering four separate ticketed requirements plus a handful of audit-driven fixes found along the way. The architecturally important decision sits underneath those features rather than in them: Overview, Delivery, and Distribution are rewritten to read from the exact same shared task catalog that the All Tasks page already uses, with the server-side overview listing removed entirely. Counts match the Tasks page by construction now, computed from one status-counting function rather than from two independently-maintained queries that happened to usually agree.

That's the real fix here, even more than the new dimensions themselves. Two views of the same underlying data drifting slightly apart is one of the most common and hardest-to-notice classes of dashboard bug — nothing throws an error, numbers just quietly don't match, and users lose confidence in whichever number they saw first. Collapsing both consumers onto one source eliminates the category of bug rather than patching each instance of it.

The pattern: consistency bugs hide in the seams between correct pieces

Every fix this week was individually "correct code doing the wrong thing in context" rather than a straightforward logic error: a lineage rule that was right for most cases and wrong for one explicitly-designed one; a permission check that was internally consistent but inconsistent with a sibling page; a status resolver that was right about its own domain and silent about a domain it didn't own; a retry budget that charged a real cost to the wrong actor. None of these would show up in a unit test of the function where the bug lived — they only show up when you check that function against its neighbors.

Related Articles