fix(views): a resolved case is not at risk — sla_at_risk selects on live work - #1331
Merged
Merged
Conversation
…ive work
`sla_at_risk` selected on `is_closed == false`. That flag is derived as
`effStatus === 'closed'` and never flips on `resolved`, while
`case_sla_monitor` — the flow that OWNS SLA breach detection — has always
filtered `status: { $nin: ['resolved', 'closed'] }`.
The two surfaces therefore disagreed about the same case: the sweep would not
flag a resolved case as breached while the tab still listed it for an agent to
work. The view now carries the same predicate the producer already answered
with, spelled in the view layer as `status not_in ['resolved', 'closed']`
(`$nin` is the lowered AST form and is not authorable in a view filter).
`sla_at_risk` moves from the boundary roster of
`test/live-work-predicate-parity.test.ts` into its consumer roster in the same
change, which is what that guard's by-name design requires.
`test/sla-at-risk-live-work.test.ts` adds the behavioural half on both drivers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ED4cusQ7pxZ7d7TCLKrZb
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
os-trump
marked this pull request as ready for review
August 26, 2026 07:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1325
Description
The ⏰ SLA at Risk tab selected on
is_closed == false. That flag is derived bycase_sla_defaultsaseffStatus === 'closed'(src/objects/case.hook.ts) and neverflips on
resolved— whilecase_sla_monitor, the flow that owns SLA breachdetection, has always filtered
status: { $nin: ['resolved', 'closed'] }(
src/flows/case-sla-monitor.flow.ts).So the two surfaces disagreed about the same case: the sweep would not flag a resolved
case as breached, and the tab still listed that case for a service agent to pick up. An
agent working the queue could be handed work the automation that owns SLA had already
decided was finished. That is the surface a human reads contradicting the surface that
acts — a defect, not an untidiness, and this is the distinction that separates it from
the sibling views.
The view now carries the predicate the producer already answered with, spelled in the
view layer as
operator: 'not_in'.$ninis the lowered AST form and is notauthorable in a view
filter[];parseFilterAST(['status','not_in',[...]])lowers toexactly
{ status: { $nin: [...] } }.Type of Change
Related Issues
closed#1145 / fix(service): the triage tab and its sharing rule mean live work, not not-yet-closed #1323 — the ruling, the shape, and the parity guard this change extends.is_closed == falseconsumers should follow #1145 — one is a view label question, two hand a manager and a director standing access to resolved cases #1328 — the decision card formy_open_casesand the two critical-escalation sharingrules. Deliberately untouched here and still open; this PR is not part of that
decision.
Changes Made
src/views/case.view.ts—sla_at_riskselects on{ field: 'status', operator: 'not_in', value: ['resolved', 'closed'] }. The priorityhalf (
high/critical) is unchanged.test/live-work-predicate-parity.test.ts— the two-sided edit the guard requires.sla_at_riskmoves out of the boundary roster (NOT_LIVE_WORK) and into the consumerroster (
LIVE_WORK_CONSUMERS), and is added to theis_closedoffender sweep. Theboundary roster's header records why this one entry left it and why the remaining three
must not follow it on the strength of this PR.
test/sla-at-risk-live-work.test.ts— new behavioural half..changeset/sla-at-risk-is-live-work.md— a real changeset: this narrows what anauthor-visible tab shows.
Testing
Gate chain
pnpm verifygreen oncd5620a(the commit this PR is opened at):validate,typecheck,lint,lint:i18n-gate,hygiene,hygiene:tokens,build,test— 142 test files passed, 2993 passed / 1 skipped.pnpm hygiene:tokensis green and the interaction layer is unmoved in the way thatmatters to #1321:
interaction layer ~37,431 tokens (ceiling ~40,000; headroom ~2,569).The behavioural pin runs the SHIPPED view filter, lowered through the platform's own
parseFilterAST, against a real engine on both drivers (driver-memory, which storesonly written columns, and
sqlite-wasm, which materialises every declared column — theabsent-vs-NULL axis a boolean gets wrong). Its fixture does not assert
is_closed— itruns the shipped
case_sla_defaultshandler throughtest/helpers/hook-harness.tsandstores what the app itself derives, so "would otherwise qualify" is a measurement rather
than a fiction of the test. A dedicated case then proves that claim by running the OLD
is_closed == falsespelling over the same rows and showing the resolved case comingback.
Reverse-verification. The filter was reverted on a committed tree, the mutation was
proven to have reached disk before any verdict was read (
git hash-objectmoved52e5c65→b1e6140; the anchor matched exactly once; the on-disk line counts moved aspredicted), and the guard went red naming this consumer in three separate assertions
rather than merely red:
every named consumer resolves—sla_at_risk (view filter[] — src/views/case.view.ts)sla_at_risk excludes exactly CLOSED_CASE_STATUSES—expected [] to deeply equal [ 'closed', 'resolved' ]no live-work consumer keys the concept on is_closed—sla_at_risk (view filter)plus both driver blocks of the behavioural half:
expected [ 'live-critical', 'resolved-high' ] to not include 'resolved-high'.The tree was then restored with
git checkout HEAD -- PATH(the bare form restores fromthe index, which is the mutation) and the restore proven byte-identical:
git diff HEADempty and the blob hash back to
52e5c65.One earlier run of that same procedure stopped rather than reporting, because the
predicted on-disk line count did not match. The mutation had in fact landed; the
expectation was miscalibrated (one of the three
is_closedfilters is written on a singleline and never matched the indented pattern). Recorded because the stop is the reason the
reading above can be trusted.
Checklist
Additional Notes
Deliberately out of scope, each for a different reason:
case_workflow— measured on fix(service): the triage tab and its sharing rule mean live work, not not-yet-closed #1323 and ruled a deliberate non-consumer. Its columns comefrom the
statusfield's own options, soresolvedis a real column on that kanban andis where a card lands when an agent drags one across. Excluding it would turn the resolve
gesture's destination into a hole. Not re-opened here.
my_open_cases,src/sharing/case.sharing.ts:15and:32— the open decision card[Decision] Which of the three remaining
is_closed == falseconsumers should follow #1145 — one is a view label question, two hand a manager and a director standing access to resolved cases #1328. Not touched, and the parity guard still pins their current shape by name, so alater change to them stays visible instead of arriving as a silent widening.
The scope of this PR is one view. It is the one entry on that boundary roster that turned
out not to be a judgement call, because a producer had already answered the same question.
Generated by Claude Code
Generated by Claude Code