Skip to content

fix(service): the SLA Violations tile counts open breaches at read time - #2090

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-2070-sla-tile-read-time
Oct 13, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-2070-sla-tile-read-time

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #2070
Clause-②: no

Acceptance 3, ruled C (ruling comment 6142062705). Acceptance 1 and 2 landed in PR #2080.

What changes

The SLA Violations tile on the service dashboard (sla_violations) now counts at read time. A case counts when it is flagged is_sla_violated, or when it is still open (status not resolved / closed) and its sla_due_date is strictly before the current instant. That second branch is exactly the selection case_sla_monitor sweeps, so the tile reads true on a deployment that runs no scheduled work (the platform default), where nothing flags an open breach. It counts; it does not act. Escalation and the owner's alert still need the sweep.

filter: {
  $or: [
    { is_sla_violated: true },
    { status: { $nin: ['resolved', 'closed'] }, sla_due_date: { $lt: '{now}' } },
  ],
},

{now} is the query-filter spelling of the instant the flow template spells {NOW()}. The flow spelling is refused in a query filter: measured 400 FILTER_TOKEN_UNKNOWN on POST /api/v1/analytics/dataset/query. The sweep is unchanged.

Token cost, paid first by an equal shrink in src/service (ceiling unchanged)

  • Shrink (commit 1, behaviour unchanged): all 14 widgets on service_dashboard bind case_metrics. The dataset is now declared once (const dataset = 'case_metrics') and passed by shorthand, the same shape case.view.ts uses for its data provider (refactor(service): shrink src/service back under its authored-total token ceiling, behaviour unchanged (#2082) #2085). The registered ServiceDashboard value is JSON-identical before and after (cmp exit 0). Saves 192 chars.
  • Growth (commit 2): the tile's filter adds 135 chars.
  • node scripts/check-source-token-ratchet.mjs, src/service authored total: ~21,911 → ~21,897 tokens (87,645 → 87,588 chars). The ceiling stays at ~22,000, with headroom ~89 → ~103. The interaction layer goes from ~5,616 to ~5,602.

One rule, pinned through the existing parity test

test/case-sla-breach-on-close.test.ts already pinned the sweep's strict $lt. Its section 4 now pins the three readers of one boundary:

  • the sweep selects { $lt: '{NOW()}' } (kept as before);
  • the tile's filter equals $or of the flag and the sweep's own query_breached selection, with is_sla_violated: false folded away. The status set and the comparison operator are read off the sweep, and only the instant token is mapped. A $lte on either side, a different status set, or a third clause on the sweep turns it red.

The new section 5 drives the real engine with no sweep run. It writes seven cases: open and late, parked on the customer and late, flagged and late, flagged with its deadline moved out, open and not yet due, resolved on time with the deadline passing since, and one with no deadline. Through the booted analytics service the tile counts 4, where the flag alone gives 2. It then runs case_sla_monitor and checks that the sweep flags exactly the cases the tile already counted and that the tile's number does not move. This is the behavioural proof that the two spellings select one set of rows.

test/live-work-predicate-parity.test.ts: the tile joins the by-name roster of "no longer live work" consumers. That roster's own rule is that every consumer is pinned there by name.

File surface: two test files beyond the claim, both forced

  • test/dashboard-agent-global-filter.test.ts runs every count widget against a hand-written ground-truth model that throws on any shape it does not understand, and $or was one. The model now understands the tile's $or ($nin on status, $lt '{now}' on the due date). Five fixture rows carry a due date, so the Agent-partition check exercises the open-case branch.
  • test/dashboard-date-range-window.test.ts runs every service widget over a hand-shaped crm_case that had no sla_due_date column, and the driver refuses a filter on a missing column. The fixture gains the column and four rows with a deadline, and its ground-truth model learns the same $or.

Docs

Only sentences that became false were changed. content/docs/service/sla-and-escalation and content/docs/service/cases (three locales each) described the tile as counting is_sla_violated: true. Cases also said every breach lands in Escalated Cases, which now holds only for the breaches the sweep flags. The changeset is .changeset/2070-sla-tile-read-time.md.

Measured at the public door: fresh DB, objectstack dev, sweep not bound

objectstack dev -p 4172 (the pnpm dev command on another port, to stay clear of sibling servers in this container) logged that case_sla_monitor is NOT bound, because scheduled work is off by default. On the fresh demo DB there were 38 cases: 13 flagged, 20 open and past due (16 of them unflagged), and a ground truth of 29 by union.

reading count
REST dataset query, old filter { is_sla_violated: true } 13
REST dataset query, new filter 29
REST dataset query, new filter + the default 90-day window 29
Browser DOM probe (Chromium, /_console/apps/app.objectstack.hotcrm/dashboard/service_dashboard) 29

The console's own request for the tile was runtimeFilter: { $and: [ { $or: [ flag, { status $nin, sla_due_date: { $lt: ISO instant } } ] }, { created_at: 90-day window } ] }. The console resolves {now} itself before sending, and the response was case_count: 29. A REST caller sending {now} is resolved by the server, and the two agree. The hosted runtime is NOT MEASURED, because none is reachable from this container.

Ablations (on committed state, through ablation-replace.mjs; restore proven by blob == HEAD and an empty git diff HEAD)

  • A. Tile filter back to { is_sla_violated: true }: 4 red. The section 4 pin goes red, the section 5 count reads 2 where 4 is expected, and both roster tests go red. The post-sweep test stays green, because after a sweep the flag alone does count them; the pre-sweep count is the one that discriminates.
  • D. Tile status set narrowed to ['closed']: 4 red. The section 4 pin goes red, the count goes 5 against 4 (the resolved-on-time row) before and after the sweep, and the roster goes red.
  • B. $lt changed to $lte: 2 red. The section 4 pin and the global-filter truth model both refuse the shape. No row can sit exactly at the instant, so only the structural pin can see this one.

Gates on 7647070e, each through the shared verify lock

  • pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens && pnpm build: VERDICT command-exit 0. The run printed Validation passed, 18 suggestion(s) (pre-existing), i18n lint gate: 0, source hygiene clean, source token ratchet clean and Build complete. tsc --listFiles lists all four touched test files.
  • pnpm test ran as two file halves whose union is all 179 test files (diffed equal), split to fit the foreground cap:
    • half 1: Test Files 90 passed (90), Tests 1640 passed | 1 skipped, VERDICT command-exit 0;
    • half 2: Test Files 89 passed (89), Tests 2054 passed, VERDICT command-exit 0.

Acceptance notes (not filed)


Generated by Claude Code

…once

All fourteen widgets on service_dashboard bind the same dataset,
`case_metrics`, and each spelled it out. Declare it once and pass it by
shorthand (`dataset,`), the shape case.view.ts already uses for its one
data provider. The registered ServiceDashboard value is unchanged
(JSON-identical to the base's), and src/service's authored total drops
from ~21,911 to ~21,863 tokens (-192 chars).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
The tile counted only `is_sla_violated`, which nothing sets on an open
case unless the scheduled case_sla_monitor sweep runs, and scheduled
work is off by default. It now counts a flagged case OR a case still
open (status not resolved/closed) whose sla_due_date is strictly before
the current instant: the selection the sweep itself makes. It counts;
it does not flag, escalate or alert, which still need the sweep.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
case-sla-breach-on-close.test.ts: the tile's filter must be the flag OR
case_sla_monitor's query_breached selection (operator and status set
read off the sweep; only the instant's spelling differs, `{now}` in a
query filter for the flow template's `{NOW()}`). A new section counts
seven cases through the dataset query path with no sweep run (4 counted,
the flag alone would give 2), then runs the sweep and checks it flags
exactly the cases the tile already counted, with the count unchanged.

live-work-predicate-parity.test.ts: the tile joins the by-name roster of
"no longer live work" consumers. dashboard-agent-global-filter.test.ts:
the ground-truth model learns the tile's `$or`, and five fixture rows
carry a due date so the partition check exercises the open-case branch.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
The SLA & Escalation and Cases pages (three locales each) described the
tile as counting `is_sla_violated: true`, and Cases said every breach
lands in Escalated Cases. The tile now also counts open cases past their
due date at read time, and only the breaches the sweep flags are
escalated. Changeset for the tile change.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
… column

dashboard-date-range-window.test.ts runs every service widget over a
hand-shaped crm_case with no sla_due_date column, and the driver refuses
a filter on a column the table lacks. The fixture gains the column and
four rows with a deadline (two open and past due, one open and not yet
due, one closed and past due), and the ground-truth model learns the
tile's `$or`.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BCDqr1DqnQjEh5jA9gGkUG
@vercel

vercel Bot commented Oct 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 13, 2026 10:45am UTC

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants