You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SLA reporting shows 0% violations / 100% met although cases are closed after their SLA due date: is_sla_violated is set only by the open-case sweep, so a late close never records the breach #2070
Filing gate ①: a product defect, reproduced and measured. Source: 2026-10-12 hosted-shape browser pass (rig at cloud v1.36.0's pins, HotCRM 4.0.1 from the marketplace).
Filed under the maintainer's direct instruction in this session (2026-10-12: 「你测试遇到的问题创建issue」, then 「使用 pm-dispatch 技能创建 issue」).
Reader: the repo:hotcrm lane's execution seat (case SLA hook / flow and seed). Acceptance 3 may need a plan-copy decision. If so, route that part to the decision box rather than guessing.
Dedupe: repo:objectstack-ai/hotcrm is:issue is_sla_violated → 22 hits, all states. … SLA violation closed case → 8. Nearest: #1213 (closed: the gauge plotted the violation rate under the 达成率 title), a different defect; #1825 (closed: the scheduled monitor's runAs).
What happens
SLA reporting says nobody ever breached an SLA after the fact.
Report sla_performance (数据洞察 → SLA 达成): SLA 违约率 0.0% for every priority.
Service dashboard tile: SLA 达标率 100%.
The data says otherwise. 9 closed or resolved cases were closed after their sla_due_date, and every one has is_sla_violated: false:
case
sla_due_date
closed_date
CASE-00012
10-08 04:00Z
10-09
CASE-00033
09-17 08:00Z
09-18
CASE-00011
10-09 06:00Z
10-12
CASE-00037
09-13 04:00Z
09-15
CASE-00004
10-05 06:00Z
10-06
CASE-00024
09-26 08:00Z
09-27
CASE-00015
10-05 04:00Z
10-06
CASE-00028
09-22 04:00Z
09-24
CASE-00020
10-01 12:00Z
10-03
Read from GET /api/v1/data/crm_case?top=500. Some of these are seed rows. Others were closed by hand through Close Case during the pass, so this is the live path, not only seed data.
Cause (from the app source)
is_sla_violated is written only by the scheduled case_sla_monitor sweep.
That sweep looks only at open cases (status not in resolved/closed).
Closing a case late therefore never records the breach, and the closed-late seed rows were authored with false.
Plan-dependent part (same symptom, separate cause)
On a hosted free plan, package-authored scheduled work is OFF. The log line is [ArtifactKernelFactory] package-authored scheduled work is OFF for environment '…' on plan 'free' (cloud#2337). So case_sla_monitor never runs at all:
20 open cases are past due, but only the 4 seeded breaches are flagged.
The "SLA 违约" tile under-counts.
Nothing in the UI says SLA tracking is inactive on this plan.
Whether HotCRM should say so (an empty-state note) or the platform should is for the lane to decide.
Acceptance
Closing a case after its sla_due_date records the breach, for example through a close-time hook rather than only the sweep. The report and dashboard then count it.
Seed data is consistent: a closed-late seeded case is is_sla_violated: true.
On a plan where the sweep is not armed, the SLA surfaces do not present a confident 100% (a note, or the plan-gate copy).
First touch by the repo:hotcrm seat, session_01WnMDsLSaUQUD41WaWnADcF, 2026-10-12T08:50Z. ⛔ Not a claim.
Grade kept:bug, priority:p2 (SLA reporting states 0% breaches against 9 measured late closes). → pm:queue.
Split at dispatch: acceptance 1 and 2 (record the breach at close; seed rows consistent) are app work. Acceptance 3 (what the SLA surfaces say when the plan does not arm scheduled work) is a product-copy question; the dispatch asks the dev for evidence on it, and an unsettled answer goes to the decision box as its own item, ⛔ not guessed.
Charter note: HotCRM must run on the community edition and on a plan without scheduled work; a surface that claims 100% where nothing measures it is the declared ≠ enforced case the lane charter puts first.
Claim: PM loop round R81
Session: session_01WnMDsLSaUQUD41WaWnADcF
Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-2070-sla-breach-on-close
Worktree: hotcrm-issue-2070
Domain: repo:hotcrm (single-lane repo, no domain:* taxonomy)
Seat: repo:hotcrm#1
File surface: acceptance 1 (record the breach when a case closes late): src/service/objects/case.hook.ts and/or src/service/objects/_case-sla.ts, src/service/flows/case-sla-monitor.flow.ts only if the sweep's rule moves into a shared helper; acceptance 2: the closed-late rows of src/service/data/service.seed.ts; content/docs/service/** SLA sentences only if one becomes false; their tests; one .changeset/2070-*.md. Acceptance 3 (what SLA surfaces say on a plan with no scheduled work): measured and returned as options, ⛔ not built in this PR (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default (dispatch-gates --tier --repo objectstack-ai/hotcrm hits no declared tier glob for these paths; the tier stays the PM's per-card call)
Clause-②: no
Responsibility: the app: is_sla_violated is written only by the open-case sweep case_sla_monitor, so a case closed after sla_due_date never records the breach; the seed authors closed-late rows as false | none: the platform computes no SLA | every reader of the SLA report and the service dashboard (0% violations / 100% met against 9 measured late closes)
Thread-read: 6120912405
Serial constraints cleared: no open PR or claim on src/service/objects/case.* or the seed. #2071 and #2072 (case_detail_page) and #2060 items 3/4 (case profile, sharing, case.view.ts) are queued, not in flight; this card touches none of their files. In flight this round, disjoint files: #2060 item 1 (src/revenue), #2067 (src/sales account approval). PR #2074 (#2068, lead conversion) is accepted and landing. The PR says Refs #2070, ⛔ not Fixes, while acceptance 3 is open.
os-dev-report
{
"issue": 2070,
"status": "done",
"branch": "claude/issue-2070-sla-breach-on-close",
"pr": "#2080",
"session": "session_01WnMDsLSaUQUD41WaWnADcF (subagent run; the parent seat's id, as the container's CLAUDE_CODE_REMOTE_SESSION_ID cse_01WnMDsLSaUQUD41WaWnADcF answers it)",
"premise_still_valid": true,
"summary": "Acceptance 1: case_sla_defaults (case.hook.ts, the hook that already derives is_closed / closed_date / resolution_time_hours) now sets is_sla_violated: true on the write that moves a case INTO resolved/closed when sla_due_date is strictly before the stop instant (the closed_date this write carries, else now). Only entry into the set is judged, a case with no due date is untouched, and the flag is never cleared, escalated or alerted. The rule is declared once as caseSlaBreached in _case-sla.ts and mirrored inline in the hook body (QuickJS sandbox, no module scope); a new suite drives the LOWERED body against the helper, and the inline status list joined the CLOSED_CASE_STATUSES by-name roster. Acceptance 2: every settled seed row derives its flag from its own daysAgo() dates via seededSettledBreach. 9 rows flip to true (6 closed, 3 resolved), matching the card's 9, and a fresh demo DB reads 2 of 8 closed within SLA (25%), not 100%. Measured at the public door (objectstack dev, default boot, case_sla_monitor NOT bound): a REST close or resolve of a past-due case stores true, an on-time close stores false, and the dataset query path reads gauge 0.25 and report critical/high/medium 1.0, low 0.0, with all 9 settled-late cases flagged. PM assumption 1 confirmed on the base commit (agent resolve, agent close and close_case all stored false). Assumption 2 partly falsified: a literal shared code path is impossible (sandbox body plus query filter), so the rule has one declaration and three pinned readers. Assumption 3 held: no profile was widened, and the agent's editable:false mask does not refuse the agent's own close (measured). Acceptance 3 is measured and returned in open_questions, not built.",
"tests": "All on head cf272f6, through os-verify-lock (slot 2070). (1) Pre-test chain 'pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens && pnpm build': 'os-verify-lock: VERDICT command-exit 0 · held the lock 35s'; it printed 'Validation passed', 'i18n lint gate: 0 i18n/missing-* issues', 'source hygiene clean', 'source token ratchet clean' and 'Build complete'. (2) pnpm test split into two acquisitions of vitest run over file halves whose union is all 178 test files (diffed equal; split to fit the ~600s foreground cap after an 8-minute queue): half 1 'VERDICT command-exit 0 · held the lock 221s', 'Test Files 89 passed (89)', 'Tests 1606 passed | 1 skipped'; half 2 'VERDICT command-exit 0 · held the lock 266s', 'Test Files 89 passed (89)', 'Tests 2049 passed'. An earlier unsplit pnpm test on 393fb6a: 1 failed (flow-scheduled fixture settled late, so the new hook flagged it before the sweep), fixed in cf272f6. typecheck covers test/ (tsc --listFiles lists 189 test files, including the touched ones). (3) Ablations via objectstack scripts/ablation-replace.mjs (anchor must hit, blob changed, restore proven by blob == HEAD and empty git diff HEAD), on committed state: A) the breach assignment disabled ('if (false && ...)'), giving 10 of 18 new tests red, exactly the late-finish rows (lowered body, engine through both doors, and the report/gauge/tile readings); on-time, boundary-met, untouched and never-cleared rows stay green, as do flow-scheduled and the parity roster. B) the inline list narrowed to ['closed'], giving 7 red, including 'case_sla_defaults excludes exactly CLOSED_CASE_STATUSES'. C) one seed row put back to a hand-picked false, giving seed-consistency red, naming the row, plus the gauge's 6-of-8 and 25% pins red. (4) Public door: objectstack dev -p 4170 on a fresh DB, signed in as dev admin; REST PATCH close/resolve late gives true, on time gives false; POST /api/v1/analytics/dataset/query scoped to those cases gives compliance 0.5 and tile 2. The dev server tree was killed by PID, and the worktree was removed.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, which is POST /repos/objectstack-ai/hotcrm/pulls (draft) plus POST /repos//issues/2080/assignees [os-warren], read back as identical 9964 bytes with os-warren assigned; (2) this os-dev-report, POST /repos//issues/2070/comments. 0 label writes: labeler.yml applied path labels itself, and no gate reads a label this PR needs. git push is not counted.",
"gates": {
"head": "cf272f6",
"pre_test_chain": "os-verify-lock: VERDICT command-exit 0 · held the lock 35s · waited 11s",
"test_half_1": "os-verify-lock: VERDICT command-exit 0 · held the lock 221s (3m41s) · waited 487s (8m07s); Test Files 89 passed (89); Tests 1606 passed | 1 skipped (1607)",
"test_half_2": "os-verify-lock: VERDICT command-exit 0 · held the lock 266s (4m26s) · waited 12s; Test Files 89 passed (89); Tests 2049 passed (2049)",
"ci": "in_progress at report time: Build and Test (22.x), Typecheck and Build, Playwright, Analyze Code. Check Changeset, Quality Checks, link-check and Label Pull Request were success."
},
"line_budget": {
"additions": 520,
"deletions": 68,
"changed_files": 21,
"src_additions": "src/ +130 -27 (seed +53 -24 is outside the ratchet by ruling)",
"hygiene_tokens": "src/service authored total ~21,990 tokens (ceiling ~22,000; headroom ~10); base ~21,836, so +154. business semantics ~13,008 (ceiling ~14,000). Any further src/service metadata growth (e.g. acceptance-3 option C) must be paid for by deletions or a ceiling decision."
},
"files_changed": [
".changeset/2070-sla-breach-on-close.md",
"src/service/objects/_case-sla.ts",
"src/service/objects/case.hook.ts",
"src/service/objects/case.object.ts (comment only)",
"src/service/flows/case-sla-monitor.flow.ts (comment only)",
"src/service/data/service.seed.ts",
"content/docs/service/sla-and-escalation{,.zh-Hans,.zh-Hant}.mdx",
"content/docs/service/cases{,.zh-Hans,.zh-Hant}.mdx",
"content/docs/reference/faq{,.zh-Hans,.zh-Hant}.mdx",
"src/docs/crm_service.md",
"test/case-sla-breach-on-close.test.ts (new)",
"test/seed-consistency.test.ts",
"test/sla-compliance-gauge.test.ts",
"test/live-work-predicate-parity.test.ts",
"test/flow-scheduled.test.ts"
],
"deviations": [
"File surface widened, each widening named in the PR body. case.object.ts and case-sla-monitor.flow.ts got comment-only edits naming the second writer; no metadata changed in either. content/docs/reference/faq (3 locales) and src/docs/crm_service.md are outside content/docs/service/** but each carried a sentence this change makes false ('a case resolved late before the next sweep is never flagged' / 'there no breach is flagged'). test/flow-scheduled.test.ts: its resolved/closed fixtures settled late in one system write and now carry the breach before the sweep, so they settle on time first and have the clock wound back afterwards. test/live-work-predicate-parity.test.ts got one roster entry and one extractor, not a new gate.",
"PM mechanism assumption 2 (one place the sweep and the close-time write both use) partly falsified; see summary. The replacement is one declaration (caseSlaBreached) plus pinned readers.",
"pnpm test was run as two file-half acquisitions (union = all 178 files) rather than one, to fit the foreground cap after queueing; no file was skipped.",
"Commit trailers are model-free ('Co-authored-by: Claude' plus Claude-Session), and the PR footer is the session-URL form, per os-dev.md over the harness attribution reminder, which asked for a model-named Co-Authored-By and a different footer.",
"Visible demo change: on a fresh demo DB the SLA Compliance gauge reads 25% and the SLA Violations tile 13 (was 100% and 4). This is what acceptance 2 asks for; if the product wants a healthier-looking demo, that means retuning the seed's settle delays, a separate call."
],
"open_questions": [
{
"question": "Acceptance 3. Measured on a default objectstack dev boot (scheduled work OFF by platform default, case_sla_monitor NOT bound). After this PR the closed-case surfaces no longer depend on the sweep: gauge 25%, report rates by priority, all 9 settled-late cases flagged. What still depends on the sweep is open work: 20 seeded open cases are past due and only the 4 seeded ones are flagged, so the SLA Violations tile reads 13 against a true 29, and no open breach is escalated or alerted. No SLA surface says the sweep is off; the in-product crm_service.md and the admin Automation page do, but the content/docs/service SLA pages do not. Which, if any, should be built?",
"options": [
"A. App-side note: a sentence on the SLA Violations tile description and on the service SLA doc pages (3 locales). Cost 1-2h, but the tile description is src/service metadata and that package has ~10 tokens of authored-total headroom left, so it needs deletions or a ceiling call; a docs-only variant costs no tokens. Axes: business need is real (16 of 20 open breaches invisible) but a static note cannot tell whether THIS deployment runs the sweep and shows where it does; long-term it hand-copies a platform default into prose, which drifts if the default or plan matrix moves (not contract-first); for AI it is unenforced prose and teaches hedging with copy; for startup focus it is cheapest.",
"B. Platform plan-gate copy: the console shows the platform's own reason (scheduledWorkDisabledReason is already on the wire in GET /automation/_status; the console card is objectui#9217, open). Reaching a dashboard tile would need a new seam linking a tile to the flow that maintains its data (no spec key today); HotCRM waits. Axes: the business need spans all 8 HotCRM scheduled flows and every package, but the tile-to-flow link has no measured consumer beyond this card; long-term it is the right owner for deployment-policy copy (AGENTS.md sections 1 and 4); for AI it is good if derived from the binding audit, and risky if it becomes a new authorable key the platform does not enforce; for startup focus it is new platform surface, so default tight.",
"C. Read-time open-breach count in the app: the SLA Violations tile counts is_sla_violated OR (status not in CLOSED_CASE_STATUSES and sla_due_date $lt {now}). Measured on the 17.7.0 dataset path in-process: {now} and $or both resolve, and the count is 29, equal to the truth (vs 13). Cost about half a day (binding/measure, docs in 3 locales, a test, a parity-roster entry), plus the same ~10-token src/service headroom problem. It does not flag the open case itself or escalate; those need the sweep by nature. Axes: business need is real and measured; long-term it is contract-first (the surface measures what it claims on every deployment, no prose), but it is a second spelling of the breach rule that must be pinned to the same set and strict $lt; for AI it is a declared measure the executor enforces; for startup focus it is modest, with no new platform surface and pull measured on the default install.",
"D. Nothing more. The confident 100% the card names (gauge/report over closed cases) is gone on every deployment (25% measured on the default boot); the open-case gap is already stated in the in-product doc and the admin Automation page. Cost zero. Axes: the business need is met for the card's literal text, but the tile still under-counts open breaches silently; long-term there is no workaround; nothing new for AI; startup focus is best."
],
"recommendation": "D now, with C as the follow-up if the maintainer wants the tile right on scheduled-off deployments. C is the only option where the surface measures instead of hedging; A is rejected (prose about a deployment state the surface cannot detect); B is the right home for plan copy in general but is new platform surface without measured pull beyond this tile. The axes conflict on C vs D (business need favours C, startup focus favours D), so this is the maintainer's call."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in the PR's Acceptance notes, not filed — a predicate (bulk) update stands case_sla_defaults down entirely (existing D3 per-row stand-down), so a bulk close stamps no breach, closed_date, is_closed or resolution time; no public door reaches it (REST bulk ingress iterates by id, close_case writes by id)",
"carrier: 承接者:无 · noted, not filed — sla_due_date carries no field mask for service_agent, so an agent can move the deadline in the same save that closes the case and no breach is recorded (also true against the sweep); a profile/policy question, the docs speak of a manager renegotiating",
"carrier: 承接者:无 · noted, not filed — the seed comment on the resolved 'How to configure SSO with Okta?' row says case.hook stamps closed_date on resolve as a resolved-date proxy; the hook stamps it on closed only (comment-only drift, untouched here)"
]
}
Verdict: os-dev report on PR #2080 — ACCEPT acceptance 1 and 2; acceptance 3 goes to the decision box on this card when the PR lands. Not governed (21 files: src/service/, src/docs/, content/docs/, test/, .changeset/; +520 / −68). The seat lands it through the merge queue once every check on head cf272f6b is green. repo:hotcrm seat, session_01WnMDsLSaUQUD41WaWnADcF, 2026-10-12T10:38Z.
Checked against GitHub and the branch (fetched at cf272f6; 3 commits), not taken from the report:
PR shape: draft, base main, assignee os-warren. The first line is Refs #2070 (acceptance 1 and 2), and the body carries no closing keyword, so merging leaves this card open for acceptance 3. Clause-②: no starts a line.
The fix:
case_sla_defaults (case.hook.ts) sets is_sla_violated: true on the write that takes a case INTO resolved / closed when sla_due_date is strictly before the stop instant (this write's closed_date, else now).
Only the entry into the set is judged, a case without a due date is untouched, and the flag is never cleared.
The rule is declared once as caseSlaBreached in _case-sla.ts. The sandboxed hook spells it inline, pinned by a test that drives the lowered body against the helper and by the status-set parity roster.
Seed: every settled demo row derives its flag from its own dates (9 rows flip to true, the card's 9).
Docs, read sentence by sentence: the SLA, cases and FAQ pages (3 locales each) and src/docs/crm_service.md now say a late finish is flagged at close, while open breaches still need the sweep.
Changeset: says which surfaces change on every deployment and that the demo now shows 2 of 8 closed cases within SLA.
Ablations (report): disabling the assignment turned exactly the late-finish rows red; narrowing the status list turned the parity roster red; a hand-picked seed row turned seed-consistency red.
Public door (report, objectstack dev, sweep NOT bound): a late REST close or resolve stores true and an on-time one false; the dataset query reads the gauge at 0.25.
CI on cf272f6 at this reading: 9 of 10 checks success; Build and Test (22.x) in_progress.
Deviations, accepted:
The file surface widened to case.object.ts and case-sla-monitor.flow.ts (comments only), the FAQ pages and src/docs/crm_service.md (each carried a sentence this change makes false), and test/flow-scheduled.test.ts (its fixtures settled late and now carry the breach). All are named in the PR body.
PM assumption 2 is partly falsified: no literal shared code path is possible across the sandbox and the query filter. One declaration with pinned readers replaces it.
pnpm test ran as two file halves whose union is all 178 files.
Visible change, recorded: a fresh demo database now reads SLA Compliance 25% and the SLA Violations tile 13 (was 100% and 4). That is the truth of the seeded dates, which acceptance 2 asks for. A healthier-looking demo would be a separate seed-retuning call.
⚠️src/service token ceiling: after this PR the authored total reads ~21,990 / 22,000 (~10 tokens of headroom). The next src/service card (#2072, #2073 items 3 and 4) pays for its growth with an equal shrink.
Out-of-scope findings: none in the three classes. A bulk predicate close standing the hook down, an agent moving sla_due_date in the closing save, and a stale seed comment are Acceptance notes / carrier none.
Acceptance 3 (the dev's options A–D, with measurements: after this PR the closed-case surfaces are right everywhere, and the open-breach tile under-counts where the sweep is off, 13 against a true 29) is put to the maintainer on this card when the PR lands, with the four-axis analysis.
Release: session session_01WnMDsLSaUQUD41WaWnADcF · reason: acceptance 1 and 2 landed; acceptance 3 needs the maintainer · destination: needs-user-decision (this card is the anchor)
Landed: PR #2080 MERGED as 283a31f on main through the merge queue. repo:hotcrm seat, 2026-10-12T10:46Z.
Two readings: the PR object reads merged; git diff 29bf17e 283a31f has the same stable patch-id (1aedb9781aa1) as the reviewed diff at head cf272f6: 21 files, +520 / −68.
Delivered:
acceptance 1: a late resolve or close records the breach at that moment, on every deployment;
acceptance 2: the seeded settled cases carry the flag their own dates call for.
State:pm:dispatched → needs-user-decision and the assignee is cleared in this act. Only acceptance 3 remains, and it is put to the maintainer below.
os-decision-facets
① 项目长远合理性:C 让度量在任何部署上自证、不靠文案;代价是违约规则多一处读时写法,用既有 parity 测试钉住。
② 实际业务拉动:默认部署实测少报(13 对 29),服务经理今天就会看错积压。
③ 防 AI 犯错:C 由执行器算出,声明即兑现;D 静默少报,A 是无人执行的文案。
④ 创业阶段不扩散:C 不加平台键;B 需要新平台面,不在本卡做。
Prior rulings read: sla / scheduled work / case_sla_monitor over this card and #1825 / #1213 → no ruling on plan-gated SLA copy; ADR none found; thread: dev report on this card, PR #2080.
Ruling (acceptance 3): letter C · the maintainer's word, verbatim: 「hotcrm 三个决裁都同意」 (triage seat session session_01AavokzJ5DndAwitDXvKy4U, on the batch the seat presented in chat with each card's own recommendation)
Recorded by the triage seat (objectstack seat post objectstack-ai/objectstack#6015) · 2026-10-13T09:00Z. ⛔ Not a claim, ⛔ not a dispatch. needs-user-decision → pm:queue in this act, for the repo:hotcrm seat to execute.
What the ruling binds (the card's own 裁后执行 for C):
The 「SLA 违约」 tile counts at read time: flagged cases, or open cases whose sla_due_date has passed. It reads true on every deployment, with or without scheduled work.
The breach rule is pinned to the same $lt judgment case_sla_monitor uses, through the existing parity test. ⛔ No second wording of the rule.
Escalation and alerts still need the sweep. This counts; it does not act.
src/service pays its token cost with an equal shrink first. ⛔ The ceiling does not move.
Claim: PM loop round R82
Session: session_01BCDqr1DqnQjEh5jA9gGkUG
Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-2070-sla-tile-read-time
Worktree: hotcrm-issue-2070
Domain: repo:hotcrm (single-lane repo, no domain:* taxonomy)
Seat: repo:hotcrm#1
File surface: src/service/dashboards/service.dashboard.ts (the 「SLA 违约」 tile's filter, ~line 117), src/service/datasets/case.dataset.ts only if the tile's measure needs it, the existing SLA parity test under test/ (pinning the tile's breach judgment to case_sla_monitor's $lt), any src/service/** file the equal token shrink comes from (behaviour unchanged, named in the PR), content/docs/service/** sentences only if one becomes false, one .changeset/2070-*.md (stop on breach; explain in the report)
Container & model: S (the tile's measure plus a parity pin, with a token payment to find), mode:subagent, model: default (dispatch-gates --tier --repo objectstack-ai/hotcrm hits no declared tier glob for these paths; the tier stays the PM's per-card call)
Clause-②: no
Responsibility: the app: the 「SLA 违约」 tile counts only is_sla_violated, which only the scheduled sweep writes for open cases | the platform's scheduled work, which is OFF by default and per plan | every service manager reading the tile on a deployment without scheduled work (13 shown vs 29 real, measured)
Ruling-ref: 6142062705 (acceptance 3, letter C; the maintainer's 「hotcrm 三个决裁都同意」, recorded by the triage seat; fetched in this act)
Thread-read: 6142062705
Serial constraints cleared: src/service/** hot-file queue (seat post §3): #2073 items 3 and 4, and #2070 option C; nothing in flight on it. Token headroom src/service authored ~87 (read by #2031's dev on be03a57, whose only src/service change is the protocol string; unverified on main); the ruling binds an equal shrink first, and ⛔ the ceiling does not move. #2031 (decision box) touches src/service/index.ts only. In flight in this lane: #2066 (src/sales/apps/crm.app.ts, assets/), #2069 (src/revenue/**, locale tax_rate rows), both disjoint. The R81 branch claude/issue-2070-sla-breach-on-close (cf272f6, PR #2080 merged by squash) is NOT reused, since a reuse would need a force-push.
os-dev-report
{
"issue": 2070,
"status": "done",
"branch": "claude/issue-2070-sla-tile-read-time",
"pr": "#2090",
"session": "session_01BCDqr1DqnQjEh5jA9gGkUG (subagent run; the parent seat's id, as the dispatch and the relay envelope name it)",
"premise_still_valid": true,
"summary": "Acceptance 3, ruling C. The SLA Violations tile (service_dashboard / sla_violations) now counts at read time: is_sla_violated, OR status $nin ['resolved','closed'] AND sla_due_date $lt '{now}'. That second branch is case_sla_monitor's own query_breached selection, with the instant spelled in the query-filter dialect, because the flow-template {NOW()} is refused there (400 FILTER_TOKEN_UNKNOWN, measured). The sweep is untouched. The tile counts and does not act; escalation and alerts still need the sweep. The token cost was paid first, in its own behaviour-unchanged commit: the 14 widgets' dataset binding is declared once (const dataset = 'case_metrics', shorthand, the #2085 shape), and ServiceDashboard is JSON-identical before and after. src/service authored total went ~21,911 to ~21,897 and the ceiling did not move. On a fresh DB (objectstack dev, sweep NOT bound) the tile reads 29, against 13 with the old filter; the ground truth is 29, and the REST dataset query and the browser DOM agree. The parity pin lives in test/case-sla-breach-on-close.test.ts: structurally (operator and status set read off the sweep) and behaviourally (it runs the sweep and checks that it flags exactly the rows the tile already counted, with the count unchanged). The tile also joins the live-work roster.",
"tests": "All on head 7647070 unless noted, each through os-verify-lock (slot hotcrm-2070). (1) The pre-test chain 'pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens && pnpm build' gave 'VERDICT command-exit 0 · held the lock 45s'. It 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 4 touched test files (190 test files in all). (2) pnpm test ran as 2 file halves whose union is all 179 test files (diffed equal): half 1 gave 'VERDICT command-exit 0 · held the lock 290s', 'Test Files 90 passed (90)', 'Tests 1640 passed | 1 skipped'; half 2 gave 'VERDICT command-exit 0 · held the lock 380s', 'Test Files 89 passed (89)', 'Tests 2054 passed'. An earlier half 1 on 2256475 had 1 file red (dashboard-date-range-window: its hand-shaped crm_case had no sla_due_date column, so the driver refused the tile's filter), fixed in 7647070. (3) Ablations on committed state through objectstack scripts/ablation-replace.mjs (the anchor hit x1 to x0, the blob changed, and restore was proven by blob == HEAD 2c002688f997 and an empty git diff HEAD), over the 3 parity/truth suites: A) tile filter back to { is_sla_violated: true } turned 4 red (section-4 pin, section-5 count 2 vs 4, both live-work roster rows); the post-sweep test stays green by construction. D) $nin narrowed to ['closed'] turned 4 red (pin, count 5 vs 4 before and after the sweep, roster). B) $lt to $lte turned 2 red (pin, global-filter truth model). The edits are in src, which these suites import directly, so no dist step was involved. (4) Public door: objectstack dev -p 4172 on a fresh DB, sweep NOT bound (logged). 38 cases; 13 flagged; 20 open and past due (16 unflagged); truth union 29. POST /api/v1/analytics/dataset/query gave 13 with the old filter, 29 with the new one, and 29 with the new one plus the 90-day window. The Chromium DOM probe on /_console/apps/app.objectstack.hotcrm/dashboard/service_dashboard read 'SLA Violations / Cases that breached their SLA / 29 / Cases'. The console's own tile request carried $and[ $or[flag, {status $nin, sla_due_date $lt ISO instant resolved client-side}], created_at window ] and got case_count 29. The hosted runtime is NOT MEASURED (none reachable). The dev server process group was killed by its PGID; the worktree and node_modules were removed.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 relay strokes (each POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]). (1) pr_create, which is POST /repos/objectstack-ai/hotcrm/pulls (draft) plus POST /repos//issues/2090/assignees [os-justin]; read back as 7976 bytes sent and 7976 stored, identical, with assignee os-justin. (2) This os-dev-report: POST /repos//issues/2070/comments via post-stamped.mjs. 0 label writes. git push is not counted. Reads only otherwise (single-card REST reads of #2070, its comments and PR #2090).",
"gates": {
"head": "7647070e",
"pre_test_chain": "pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens && pnpm build — os-verify-lock: VERDICT command-exit 0 · held the lock 45s · waited 5s — exit 0",
"test_half_1": "pnpm exec vitest run [90 files] — os-verify-lock: VERDICT command-exit 0 · held the lock 290s (4m50s) · waited 6s; Test Files 90 passed (90); Tests 1640 passed | 1 skipped (1641) — exit 0",
"test_half_2": "pnpm exec vitest run [89 files] — os-verify-lock: VERDICT command-exit 0 · held the lock 380s (6m20s) · waited 104s; Test Files 89 passed (89); Tests 2054 passed (2054) — exit 0",
"hygiene_tokens": "node scripts/check-source-token-ratchet.mjs — '✓ source token ratchet clean', exit 0; src/service authored total ~21,911 (base 0f84c9c) to ~21,897 (head), ceiling ~22,000 unchanged",
"ci": "not awaited (report at end of local verification); PM reads convergence"
},
"tokens": {
"src_service_authored_total_before": "~21,911 tokens / 87,645 chars (headroom ~89) on 0f84c9c",
"src_service_authored_total_after": "~21,897 tokens / 87,588 chars (headroom ~103) on 7647070",
"shrink": "-192 chars: service_dashboard's dataset binding written once (commit 8d5197a; registered value JSON-identical, cmp exit 0)",
"growth": "+135 chars: the tile's $or filter (commit ae2b2b0)",
"ceiling": "~22,000, not moved"
},
"assumption_5": "Not refused: on objectstack origin/main 01a3421f (spec 18.0.0-next.0) a dashboard widget filter is analyticsCarrierFilter() = FilterConditionSchema + the nested-relation walk, whose own doc keeps a {placeholder} string resolved at request time passing, and the 18 template-dialect refusal (flow-value-slot-template-dialect-refused) covers flow node value slots only; read, not executed. Its corollary: the sweep's own {NOW()} in query_breached IS in that refusal's scope (a flow token at a get_record filter operand, not a whole-string query placeholder), so #2031 must re-spell it, and the section-4 pin will go red on that change by design.",
"files_changed": [
"src/service/dashboards/service.dashboard.ts (shrink commit + the tile filter)",
"test/case-sla-breach-on-close.test.ts (section 4 extended, section 5 new)",
"test/live-work-predicate-parity.test.ts (one roster entry + one extractor)",
"test/dashboard-agent-global-filter.test.ts (truth model learns the tile's $or; 5 fixture rows get a due date)",
"test/dashboard-date-range-window.test.ts (fixture gains sla_due_date + 4 rows with a deadline; truth model learns the $or)",
"content/docs/service/sla-and-escalation{,.zh-Hans,.zh-Hant}.mdx (one sentence each)",
"content/docs/service/cases{,.zh-Hans,.zh-Hant}.mdx (one sentence each)",
".changeset/2070-sla-tile-read-time.md"
],
"deviations": [
"The file surface widened beyond the claim to two test files, both forced and named in the PR body. test/dashboard-agent-global-filter.test.ts runs every count widget against a ground-truth model that throws on unknown shapes ($or was one). test/dashboard-date-range-window.test.ts runs every service widget over a hand-shaped crm_case with no sla_due_date column, which the driver refuses. test/live-work-predicate-parity.test.ts was counted as the existing parity test (its roster rule: every live-work consumer is pinned there by name).",
"PM assumption 3: the existing parity test that pins case_sla_monitor's $lt is test/case-sla-breach-on-close.test.ts (section 4, 'case_sla_monitor selects STRICTLY past-due cases'). case-sla-matrix and sla-at-risk-live-work do not pin it. 'Share one wording' is met up to the instant token: the tile's open-case branch must deep-equal the sweep's selection with the operator and status set read off the sweep. Byte identity would need the sweep moved from {NOW()} to {now}. That was not done here: it is outside the ruling and the claim, and #2031's upgrade has to touch that token anyway.",
"PM assumption 4: measured on main as headroom ~89, not ~87.",
"PM assumption 2: confirmed on a fresh DB at the public door (13 vs 29, not only in-process). Hosted runtime NOT MEASURED.",
"The browser check ran on port 4172 (objectstack dev -p 4172, the pnpm dev command on another port), not 4001, so it could not hit a sibling's server on the shared port.",
"pnpm test ran as two file halves (union = all 179 files) to fit the foreground cap. Half 2 first hit a queue-timeout (exit 99, holder: #2069's pnpm test) and was re-acquired on the same slot.",
"Commit trailers are model-free (Co-authored-by: Claude + Claude-Session), and the PR footer is the session-URL form, per os-dev over the harness attribution reminder (which asked for a model-named Co-Authored-By and the robot-emoji footer)."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #2031's dev · noted in the PR's Acceptance notes, not filed — case_sla_monitor's query_breached filter spells the instant {NOW()}, a flow template token at a get_record filter operand that 18.0.0-next.0 refuses (whole-string query placeholders excepted); the upgrade re-spells it, and the section-4 pin in test/case-sla-breach-on-close.test.ts goes red until the tile and sweep are moved together",
"carrier: 承接者:无 · noted, not filed — the src/service interaction layer prints the advisory to re-anchor its ceiling to ~6,000 (headroom ~1,398); not acted on, the ruling says no ceiling moves in this PR"
]
}
Verdict on the os-dev report on this card for PR #2090 — ACCEPT (acceptance 3, ruling C, Ruling-ref: 6142062705). repo:hotcrm seat, session_01BCDqr1DqnQjEh5jA9gGkUG, 2026-10-13T10:48Z. The seat lands it through the merge queue once every check on head 7647070e is green.
Checked on GitHub, head 7647070e:
Shape: draft → main, assignee os-justin. The first line is Fixes #2070, and the body carries no other closing keyword (full-body grep). 12 files, +235 / −45; not governed (check-governed-merges --pr); model-free trailers on all 5 commits.
The ruling's items:
Read-time count. The tile's filter is $or: [ { is_sla_violated: true }, { status: { $nin: ['resolved','closed'] }, sla_due_date: { $lt: '{now}' } } ]. The second branch is the selection case_sla_monitor sweeps (case-sla-monitor.flow.ts:112, $lt '{NOW()}'; {now} is the query-filter spelling of the same instant).
One wording. It is pinned in the existing test/case-sla-breach-on-close.test.ts §4, structurally, plus a behavioural §5. The ablations went red on { is_sla_violated: true } alone, on $nin ['closed'] and on $lte.
Counts, does not act. The sweep is untouched, and the changeset and docs say escalation still needs it.
Equal shrink first.8d5197a3 declares the dashboard's one dataset once, and the registered value is JSON-identical. src/service authored went ~21,911 → ~21,897; the ceiling did not move.
Changeset and docs, read sentence by sentence:
13 → 29 on a fresh demo DB with the sweep unbound: measured at the REST door and by DOM probe ✓;
"the hourly SLA sweep": the flow's start is 0 * * * * ✓;
"open = neither Resolved nor Closed" matches the $nin set ✓;
cases.mdx / sla-and-escalation.mdx × 3 now describe the tile's two branches ✓.
File surface: two test files beyond the claim (dashboard-agent-global-filter, dashboard-date-range-window). Both are forced: one is a ground-truth model that throws on $or, the other a fixture with no sla_due_date column. Both are named in the PR body. Accepted.
CI on 7647070e at verdict: Check Changeset, Quality Checks, Typecheck and Build, Playwright, CodeQL, link-check, Label all success; Build and Test (22.x) is in_progress.
Cross-card note, handed to #2031's in-flight dev: under 18.0.0-next.0, the sweep's '{NOW()}' at a get_record filter operand is refused (a flow token, not a whole-string query placeholder). The upgrade re-spells it, and §4's pin must stay green. The candidate spelling is the same query placeholder '{now}', which keeps one wording with the tile; the #2031 dev measures it.
Landed: PR #2090 MERGED as df5cbb99 on main through the merge queue (merged 10:58:43Z). This card was closed completed by its Fixes line, so acceptance 1–3 are all on main (1–2 via PR #2080). repo:hotcrm seat, 2026-10-13T10:59Z.
Two readings: the PR object reads merged; git diff df5cbb99^ df5cbb99 has the same stable patch-id (77d5e5fd8f8a) as the reviewed diff at head 7647070e.
Activity
objectstack-fleet commentedon Oct 12, 2026
First touch by the
repo:hotcrmseat,session_01WnMDsLSaUQUD41WaWnADcF, 2026-10-12T08:50Z. ⛔ Not a claim.bug,priority:p2(SLA reporting states 0% breaches against 9 measured late closes). →pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round R81
Session:
session_01WnMDsLSaUQUD41WaWnADcFAccount:
os-warren(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-2070-sla-breach-on-closeWorktree:
hotcrm-issue-2070Domain:
repo:hotcrm(single-lane repo, nodomain:*taxonomy)Seat:
repo:hotcrm#1File surface: acceptance 1 (record the breach when a case closes late):
src/service/objects/case.hook.tsand/orsrc/service/objects/_case-sla.ts,src/service/flows/case-sla-monitor.flow.tsonly if the sweep's rule moves into a shared helper; acceptance 2: the closed-late rows ofsrc/service/data/service.seed.ts;content/docs/service/**SLA sentences only if one becomes false; their tests; one.changeset/2070-*.md. Acceptance 3 (what SLA surfaces say on a plan with no scheduled work): measured and returned as options, ⛔ not built in this PR (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default(dispatch-gates --tier --repo objectstack-ai/hotcrmhits no declared tier glob for these paths; the tier stays the PM's per-card call)Clause-②: no
Responsibility:
the app: is_sla_violated is written only by the open-case sweep case_sla_monitor, so a case closed after sla_due_date never records the breach; the seed authors closed-late rows as false | none: the platform computes no SLA | every reader of the SLA report and the service dashboard (0% violations / 100% met against 9 measured late closes)Thread-read: 6120912405
Serial constraints cleared: no open PR or claim on
src/service/objects/case.*or the seed. #2071 and #2072 (case_detail_page) and #2060 items 3/4 (case profile, sharing,case.view.ts) are queued, not in flight; this card touches none of their files. In flight this round, disjoint files: #2060 item 1 (src/revenue), #2067 (src/salesaccount approval). PR #2074 (#2068, lead conversion) is accepted and landing. The PR saysRefs #2070, ⛔ notFixes, while acceptance 3 is open.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{
"issue": 2070,
"status": "done",
"branch": "claude/issue-2070-sla-breach-on-close",
"pr": "#2080",
"session": "session_01WnMDsLSaUQUD41WaWnADcF (subagent run; the parent seat's id, as the container's CLAUDE_CODE_REMOTE_SESSION_ID cse_01WnMDsLSaUQUD41WaWnADcF answers it)",
"premise_still_valid": true,
"summary": "Acceptance 1: case_sla_defaults (case.hook.ts, the hook that already derives is_closed / closed_date / resolution_time_hours) now sets is_sla_violated: true on the write that moves a case INTO resolved/closed when sla_due_date is strictly before the stop instant (the closed_date this write carries, else now). Only entry into the set is judged, a case with no due date is untouched, and the flag is never cleared, escalated or alerted. The rule is declared once as caseSlaBreached in _case-sla.ts and mirrored inline in the hook body (QuickJS sandbox, no module scope); a new suite drives the LOWERED body against the helper, and the inline status list joined the CLOSED_CASE_STATUSES by-name roster. Acceptance 2: every settled seed row derives its flag from its own daysAgo() dates via seededSettledBreach. 9 rows flip to true (6 closed, 3 resolved), matching the card's 9, and a fresh demo DB reads 2 of 8 closed within SLA (25%), not 100%. Measured at the public door (objectstack dev, default boot, case_sla_monitor NOT bound): a REST close or resolve of a past-due case stores true, an on-time close stores false, and the dataset query path reads gauge 0.25 and report critical/high/medium 1.0, low 0.0, with all 9 settled-late cases flagged. PM assumption 1 confirmed on the base commit (agent resolve, agent close and close_case all stored false). Assumption 2 partly falsified: a literal shared code path is impossible (sandbox body plus query filter), so the rule has one declaration and three pinned readers. Assumption 3 held: no profile was widened, and the agent's editable:false mask does not refuse the agent's own close (measured). Acceptance 3 is measured and returned in open_questions, not built.",
"tests": "All on head cf272f6, through os-verify-lock (slot 2070). (1) Pre-test chain 'pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens && pnpm build': 'os-verify-lock: VERDICT command-exit 0 · held the lock 35s'; it printed 'Validation passed', 'i18n lint gate: 0 i18n/missing-* issues', 'source hygiene clean', 'source token ratchet clean' and 'Build complete'. (2) pnpm test split into two acquisitions of vitest run over file halves whose union is all 178 test files (diffed equal; split to fit the ~600s foreground cap after an 8-minute queue): half 1 'VERDICT command-exit 0 · held the lock 221s', 'Test Files 89 passed (89)', 'Tests 1606 passed | 1 skipped'; half 2 'VERDICT command-exit 0 · held the lock 266s', 'Test Files 89 passed (89)', 'Tests 2049 passed'. An earlier unsplit pnpm test on 393fb6a: 1 failed (flow-scheduled fixture settled late, so the new hook flagged it before the sweep), fixed in cf272f6. typecheck covers test/ (tsc --listFiles lists 189 test files, including the touched ones). (3) Ablations via objectstack scripts/ablation-replace.mjs (anchor must hit, blob changed, restore proven by blob == HEAD and empty git diff HEAD), on committed state: A) the breach assignment disabled ('if (false && ...)'), giving 10 of 18 new tests red, exactly the late-finish rows (lowered body, engine through both doors, and the report/gauge/tile readings); on-time, boundary-met, untouched and never-cleared rows stay green, as do flow-scheduled and the parity roster. B) the inline list narrowed to ['closed'], giving 7 red, including 'case_sla_defaults excludes exactly CLOSED_CASE_STATUSES'. C) one seed row put back to a hand-picked false, giving seed-consistency red, naming the row, plus the gauge's 6-of-8 and 25% pins red. (4) Public door: objectstack dev -p 4170 on a fresh DB, signed in as dev admin; REST PATCH close/resolve late gives true, on time gives false; POST /api/v1/analytics/dataset/query scoped to those cases gives compliance 0.5 and tile 2. The dev server tree was killed by PID, and the worktree was removed.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, which is POST /repos/objectstack-ai/hotcrm/pulls (draft) plus POST /repos//issues/2080/assignees [os-warren], read back as identical 9964 bytes with os-warren assigned; (2) this os-dev-report, POST /repos//issues/2070/comments. 0 label writes: labeler.yml applied path labels itself, and no gate reads a label this PR needs. git push is not counted.",
"gates": {
"head": "cf272f6",
"pre_test_chain": "os-verify-lock: VERDICT command-exit 0 · held the lock 35s · waited 11s",
"test_half_1": "os-verify-lock: VERDICT command-exit 0 · held the lock 221s (3m41s) · waited 487s (8m07s); Test Files 89 passed (89); Tests 1606 passed | 1 skipped (1607)",
"test_half_2": "os-verify-lock: VERDICT command-exit 0 · held the lock 266s (4m26s) · waited 12s; Test Files 89 passed (89); Tests 2049 passed (2049)",
"ci": "in_progress at report time: Build and Test (22.x), Typecheck and Build, Playwright, Analyze Code. Check Changeset, Quality Checks, link-check and Label Pull Request were success."
},
"line_budget": {
"additions": 520,
"deletions": 68,
"changed_files": 21,
"src_additions": "src/ +130 -27 (seed +53 -24 is outside the ratchet by ruling)",
"hygiene_tokens": "src/service authored total ~21,990 tokens (ceiling ~22,000; headroom ~10); base ~21,836, so +154. business semantics ~13,008 (ceiling ~14,000). Any further src/service metadata growth (e.g. acceptance-3 option C) must be paid for by deletions or a ceiling decision."
},
"files_changed": [
".changeset/2070-sla-breach-on-close.md",
"src/service/objects/_case-sla.ts",
"src/service/objects/case.hook.ts",
"src/service/objects/case.object.ts (comment only)",
"src/service/flows/case-sla-monitor.flow.ts (comment only)",
"src/service/data/service.seed.ts",
"content/docs/service/sla-and-escalation{,.zh-Hans,.zh-Hant}.mdx",
"content/docs/service/cases{,.zh-Hans,.zh-Hant}.mdx",
"content/docs/reference/faq{,.zh-Hans,.zh-Hant}.mdx",
"src/docs/crm_service.md",
"test/case-sla-breach-on-close.test.ts (new)",
"test/seed-consistency.test.ts",
"test/sla-compliance-gauge.test.ts",
"test/live-work-predicate-parity.test.ts",
"test/flow-scheduled.test.ts"
],
"deviations": [
"File surface widened, each widening named in the PR body. case.object.ts and case-sla-monitor.flow.ts got comment-only edits naming the second writer; no metadata changed in either. content/docs/reference/faq (3 locales) and src/docs/crm_service.md are outside content/docs/service/** but each carried a sentence this change makes false ('a case resolved late before the next sweep is never flagged' / 'there no breach is flagged'). test/flow-scheduled.test.ts: its resolved/closed fixtures settled late in one system write and now carry the breach before the sweep, so they settle on time first and have the clock wound back afterwards. test/live-work-predicate-parity.test.ts got one roster entry and one extractor, not a new gate.",
"PM mechanism assumption 2 (one place the sweep and the close-time write both use) partly falsified; see summary. The replacement is one declaration (caseSlaBreached) plus pinned readers.",
"pnpm test was run as two file-half acquisitions (union = all 178 files) rather than one, to fit the foreground cap after queueing; no file was skipped.",
"Commit trailers are model-free ('Co-authored-by: Claude' plus Claude-Session), and the PR footer is the session-URL form, per os-dev.md over the harness attribution reminder, which asked for a model-named Co-Authored-By and a different footer.",
"Visible demo change: on a fresh demo DB the SLA Compliance gauge reads 25% and the SLA Violations tile 13 (was 100% and 4). This is what acceptance 2 asks for; if the product wants a healthier-looking demo, that means retuning the seed's settle delays, a separate call."
],
"open_questions": [
{
"question": "Acceptance 3. Measured on a default objectstack dev boot (scheduled work OFF by platform default, case_sla_monitor NOT bound). After this PR the closed-case surfaces no longer depend on the sweep: gauge 25%, report rates by priority, all 9 settled-late cases flagged. What still depends on the sweep is open work: 20 seeded open cases are past due and only the 4 seeded ones are flagged, so the SLA Violations tile reads 13 against a true 29, and no open breach is escalated or alerted. No SLA surface says the sweep is off; the in-product crm_service.md and the admin Automation page do, but the content/docs/service SLA pages do not. Which, if any, should be built?",
"options": [
"A. App-side note: a sentence on the SLA Violations tile description and on the service SLA doc pages (3 locales). Cost 1-2h, but the tile description is src/service metadata and that package has ~10 tokens of authored-total headroom left, so it needs deletions or a ceiling call; a docs-only variant costs no tokens. Axes: business need is real (16 of 20 open breaches invisible) but a static note cannot tell whether THIS deployment runs the sweep and shows where it does; long-term it hand-copies a platform default into prose, which drifts if the default or plan matrix moves (not contract-first); for AI it is unenforced prose and teaches hedging with copy; for startup focus it is cheapest.",
"B. Platform plan-gate copy: the console shows the platform's own reason (scheduledWorkDisabledReason is already on the wire in GET /automation/_status; the console card is objectui#9217, open). Reaching a dashboard tile would need a new seam linking a tile to the flow that maintains its data (no spec key today); HotCRM waits. Axes: the business need spans all 8 HotCRM scheduled flows and every package, but the tile-to-flow link has no measured consumer beyond this card; long-term it is the right owner for deployment-policy copy (AGENTS.md sections 1 and 4); for AI it is good if derived from the binding audit, and risky if it becomes a new authorable key the platform does not enforce; for startup focus it is new platform surface, so default tight.",
"C. Read-time open-breach count in the app: the SLA Violations tile counts is_sla_violated OR (status not in CLOSED_CASE_STATUSES and sla_due_date $lt {now}). Measured on the 17.7.0 dataset path in-process: {now} and $or both resolve, and the count is 29, equal to the truth (vs 13). Cost about half a day (binding/measure, docs in 3 locales, a test, a parity-roster entry), plus the same ~10-token src/service headroom problem. It does not flag the open case itself or escalate; those need the sweep by nature. Axes: business need is real and measured; long-term it is contract-first (the surface measures what it claims on every deployment, no prose), but it is a second spelling of the breach rule that must be pinned to the same set and strict $lt; for AI it is a declared measure the executor enforces; for startup focus it is modest, with no new platform surface and pull measured on the default install.",
"D. Nothing more. The confident 100% the card names (gauge/report over closed cases) is gone on every deployment (25% measured on the default boot); the open-case gap is already stated in the in-product doc and the admin Automation page. Cost zero. Axes: the business need is met for the card's literal text, but the tile still under-counts open breaches silently; long-term there is no workaround; nothing new for AI; startup focus is best."
],
"recommendation": "D now, with C as the follow-up if the maintainer wants the tile right on scheduled-off deployments. C is the only option where the surface measures instead of hedging; A is rejected (prose about a deployment state the surface cannot detect); B is the right home for plan copy in general but is new platform surface without measured pull beyond this tile. The axes conflict on C vs D (business need favours C, startup focus favours D), so this is the maintainer's call."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in the PR's Acceptance notes, not filed — a predicate (bulk) update stands case_sla_defaults down entirely (existing D3 per-row stand-down), so a bulk close stamps no breach, closed_date, is_closed or resolution time; no public door reaches it (REST bulk ingress iterates by id, close_case writes by id)",
"carrier: 承接者:无 · noted, not filed — sla_due_date carries no field mask for service_agent, so an agent can move the deadline in the same save that closes the case and no breach is recorded (also true against the sweep); a profile/policy question, the docs speak of a manager renegotiating",
"carrier: 承接者:无 · noted, not filed — the seed comment on the resolved 'How to configure SSO with Okta?' row says case.hook stamps closed_date on resolve as a resolved-date proxy; the hook stamps it on closed only (comment-only drift, untouched here)"
]
}
objectstack-fleet commentedon Oct 12, 2026
Verdict: os-dev report on PR #2080 — ACCEPT acceptance 1 and 2; acceptance 3 goes to the decision box on this card when the PR lands. Not governed (21 files:
src/service/,src/docs/,content/docs/,test/,.changeset/; +520 / −68). The seat lands it through the merge queue once every check on headcf272f6bis green.repo:hotcrmseat,session_01WnMDsLSaUQUD41WaWnADcF, 2026-10-12T10:38Z.Checked against GitHub and the branch (fetched at
cf272f6; 3 commits), not taken from the report:main, assigneeos-warren. The first line isRefs #2070 (acceptance 1 and 2), and the body carries no closing keyword, so merging leaves this card open for acceptance 3.Clause-②: nostarts a line.case_sla_defaults(case.hook.ts) setsis_sla_violated: trueon the write that takes a case INTOresolved/closedwhensla_due_dateis strictly before the stop instant (this write'sclosed_date, else now).caseSlaBreachedin_case-sla.ts. The sandboxed hook spells it inline, pinned by a test that drives the lowered body against the helper and by the status-set parity roster.src/docs/crm_service.mdnow say a late finish is flagged at close, while open breaches still need the sweep.objectstack dev, sweep NOT bound): a late REST close or resolve storestrueand an on-time onefalse; the dataset query reads the gauge at 0.25.cf272f6at this reading: 9 of 10 checkssuccess; Build and Test (22.x)in_progress.Deviations, accepted:
case.object.tsandcase-sla-monitor.flow.ts(comments only), the FAQ pages andsrc/docs/crm_service.md(each carried a sentence this change makes false), andtest/flow-scheduled.test.ts(its fixtures settled late and now carry the breach). All are named in the PR body.pnpm testran as two file halves whose union is all 178 files.Visible change, recorded: a fresh demo database now reads SLA Compliance 25% and the SLA Violations tile 13 (was 100% and 4). That is the truth of the seeded dates, which acceptance 2 asks for. A healthier-looking demo would be a separate seed-retuning call.
src/servicetoken ceiling: after this PR the authored total reads ~21,990 / 22,000 (~10 tokens of headroom). The nextsrc/servicecard (#2072, #2073 items 3 and 4) pays for its growth with an equal shrink.Out-of-scope findings: none in the three classes. A bulk predicate close standing the hook down, an agent moving
sla_due_datein the closing save, and a stale seed comment are Acceptance notes / carrier none.Acceptance 3 (the dev's options A–D, with measurements: after this PR the closed-case surfaces are right everywhere, and the open-breach tile under-counts where the sweep is off, 13 against a true 29) is put to the maintainer on this card when the PR lands, with the four-axis analysis.
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Release: session
session_01WnMDsLSaUQUD41WaWnADcF· reason: acceptance 1 and 2 landed; acceptance 3 needs the maintainer · destination:needs-user-decision(this card is the anchor)Landed: PR #2080 MERGED as
283a31fonmainthrough the merge queue.repo:hotcrmseat, 2026-10-12T10:46Z.merged;git diff 29bf17e 283a31fhas the same stable patch-id (1aedb9781aa1) as the reviewed diff at headcf272f6: 21 files, +520 / −68.pm:dispatched→needs-user-decisionand the assignee is cleared in this act. Only acceptance 3 remains, and it is put to the maintainer below.待裁:排程关闭的部署上,「SLA 违约」磁贴该怎么数未结案的超时工单
一句话问题:在不跑排程任务的部署上(平台默认就是关),「SLA 违约」磁贴只数到被标记过的工单。未结案、已超时的工单没人去标记,所以磁贴少报:演示库实测 13,实际 29。PR #2080 之后,已结案的报表和达成率都已准确,剩下的只是这块「未结案超时」。
Governing text:
AGENTS.md§4:平台的坏默认在平台修(关系到方案 B)。前提(带复查命令):
objectstack dev默认启动时case_sla_monitor未绑定(dev 实测)。git grep -n "is_sla_violated" origin/main -- src/service/dashboards/service.dashboard.ts,应命中filter: { is_sla_violated: true }。src/service,需要先删出 token(余量约 10)sla_due_date已过的{now}与$or可用、计数 29);同样要先在src/service删出 token业务含义直译:
四轴(业务立场):
$lt判据。A 是把平台的部署状态手抄成文案,部署一变就漂。B 是部署政策类文案的正确归属,但「磁贴→流程」是新平台面。D 留下一个静默少报。推荐:C;回退:D。B 是平台层面的长期方向,不是本卡的事;若平台日后提供,C 不冲突。
{now}与$or只在 17.7.0 进程内的数据集路径实测过,托管运行时未测;src/service需要等量删减才放得下。裁后执行:
pm:queue,派 dev 改磁贴度量,并用 parity 测试钉住与case_sla_monitor相同的判据;src/service先付 token。completed关卡。os-decision-facets
① 项目长远合理性:C 让度量在任何部署上自证、不靠文案;代价是违约规则多一处读时写法,用既有 parity 测试钉住。
② 实际业务拉动:默认部署实测少报(13 对 29),服务经理今天就会看错积压。
③ 防 AI 犯错:C 由执行器算出,声明即兑现;D 静默少报,A 是无人执行的文案。
④ 创业阶段不扩散:C 不加平台键;B 需要新平台面,不在本卡做。
Prior rulings read:
sla/scheduled work/case_sla_monitorover this card and #1825 / #1213 → no ruling on plan-gated SLA copy; ADR none found; thread: dev report on this card, PR #2080.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Ruling (acceptance 3): letter C · the maintainer's word, verbatim: 「hotcrm 三个决裁都同意」 (triage seat session
session_01AavokzJ5DndAwitDXvKy4U, on the batch the seat presented in chat with each card's own recommendation)Recorded by the triage seat (objectstack seat post objectstack-ai/objectstack#6015) · 2026-10-13T09:00Z. ⛔ Not a claim, ⛔ not a dispatch.
needs-user-decision→pm:queuein this act, for therepo:hotcrmseat to execute.What the ruling binds (the card's own 裁后执行 for C):
sla_due_datehas passed. It reads true on every deployment, with or without scheduled work.$ltjudgmentcase_sla_monitoruses, through the existing parity test. ⛔ No second wording of the rule.src/servicepays its token cost with an equal shrink first. ⛔ The ceiling does not move.Fixes #2070. Acceptance 1 and 2 landed in PR fix(case): record an SLA breach when a case is resolved or closed late #2080.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round R82
Session:
session_01BCDqr1DqnQjEh5jA9gGkUGAccount:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-2070-sla-tile-read-timeWorktree:
hotcrm-issue-2070Domain:
repo:hotcrm(single-lane repo, nodomain:*taxonomy)Seat:
repo:hotcrm#1File surface:
src/service/dashboards/service.dashboard.ts(the 「SLA 违约」 tile's filter, ~line 117),src/service/datasets/case.dataset.tsonly if the tile's measure needs it, the existing SLA parity test undertest/(pinning the tile's breach judgment tocase_sla_monitor's$lt), anysrc/service/**file the equal token shrink comes from (behaviour unchanged, named in the PR),content/docs/service/**sentences only if one becomes false, one.changeset/2070-*.md(stop on breach; explain in the report)Container & model:
S(the tile's measure plus a parity pin, with a token payment to find),mode:subagent,model: default(dispatch-gates --tier --repo objectstack-ai/hotcrmhits no declared tier glob for these paths; the tier stays the PM's per-card call)Clause-②: no
Responsibility:
the app: the 「SLA 违约」 tile counts only is_sla_violated, which only the scheduled sweep writes for open cases | the platform's scheduled work, which is OFF by default and per plan | every service manager reading the tile on a deployment without scheduled work (13 shown vs 29 real, measured)Ruling-ref: 6142062705 (acceptance 3, letter C; the maintainer's 「hotcrm 三个决裁都同意」, recorded by the triage seat; fetched in this act)
Thread-read: 6142062705
Serial constraints cleared:
src/service/**hot-file queue (seat post §3): #2073 items 3 and 4, and #2070 option C; nothing in flight on it. Token headroomsrc/serviceauthored ~87 (read by #2031's dev onbe03a57, whose onlysrc/servicechange is the protocol string; unverified onmain); the ruling binds an equal shrink first, and ⛔ the ceiling does not move. #2031 (decision box) touchessrc/service/index.tsonly. In flight in this lane: #2066 (src/sales/apps/crm.app.ts,assets/), #2069 (src/revenue/**, localetax_raterows), both disjoint. The R81 branchclaude/issue-2070-sla-breach-on-close(cf272f6, PR #2080 merged by squash) is NOT reused, since a reuse would need a force-push.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 2070,
"status": "done",
"branch": "claude/issue-2070-sla-tile-read-time",
"pr": "#2090",
"session": "session_01BCDqr1DqnQjEh5jA9gGkUG (subagent run; the parent seat's id, as the dispatch and the relay envelope name it)",
"premise_still_valid": true,
"summary": "Acceptance 3, ruling C. The SLA Violations tile (service_dashboard / sla_violations) now counts at read time: is_sla_violated, OR status $nin ['resolved','closed'] AND sla_due_date $lt '{now}'. That second branch is case_sla_monitor's own query_breached selection, with the instant spelled in the query-filter dialect, because the flow-template {NOW()} is refused there (400 FILTER_TOKEN_UNKNOWN, measured). The sweep is untouched. The tile counts and does not act; escalation and alerts still need the sweep. The token cost was paid first, in its own behaviour-unchanged commit: the 14 widgets' dataset binding is declared once (const dataset = 'case_metrics', shorthand, the #2085 shape), and ServiceDashboard is JSON-identical before and after. src/service authored total went ~21,911 to ~21,897 and the ceiling did not move. On a fresh DB (objectstack dev, sweep NOT bound) the tile reads 29, against 13 with the old filter; the ground truth is 29, and the REST dataset query and the browser DOM agree. The parity pin lives in test/case-sla-breach-on-close.test.ts: structurally (operator and status set read off the sweep) and behaviourally (it runs the sweep and checks that it flags exactly the rows the tile already counted, with the count unchanged). The tile also joins the live-work roster.",
"tests": "All on head 7647070 unless noted, each through os-verify-lock (slot hotcrm-2070). (1) The pre-test chain 'pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens && pnpm build' gave 'VERDICT command-exit 0 · held the lock 45s'. It 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 4 touched test files (190 test files in all). (2) pnpm test ran as 2 file halves whose union is all 179 test files (diffed equal): half 1 gave 'VERDICT command-exit 0 · held the lock 290s', 'Test Files 90 passed (90)', 'Tests 1640 passed | 1 skipped'; half 2 gave 'VERDICT command-exit 0 · held the lock 380s', 'Test Files 89 passed (89)', 'Tests 2054 passed'. An earlier half 1 on 2256475 had 1 file red (dashboard-date-range-window: its hand-shaped crm_case had no sla_due_date column, so the driver refused the tile's filter), fixed in 7647070. (3) Ablations on committed state through objectstack scripts/ablation-replace.mjs (the anchor hit x1 to x0, the blob changed, and restore was proven by blob == HEAD 2c002688f997 and an empty git diff HEAD), over the 3 parity/truth suites: A) tile filter back to { is_sla_violated: true } turned 4 red (section-4 pin, section-5 count 2 vs 4, both live-work roster rows); the post-sweep test stays green by construction. D) $nin narrowed to ['closed'] turned 4 red (pin, count 5 vs 4 before and after the sweep, roster). B) $lt to $lte turned 2 red (pin, global-filter truth model). The edits are in src, which these suites import directly, so no dist step was involved. (4) Public door: objectstack dev -p 4172 on a fresh DB, sweep NOT bound (logged). 38 cases; 13 flagged; 20 open and past due (16 unflagged); truth union 29. POST /api/v1/analytics/dataset/query gave 13 with the old filter, 29 with the new one, and 29 with the new one plus the 90-day window. The Chromium DOM probe on /_console/apps/app.objectstack.hotcrm/dashboard/service_dashboard read 'SLA Violations / Cases that breached their SLA / 29 / Cases'. The console's own tile request carried $and[ $or[flag, {status $nin, sla_due_date $lt ISO instant resolved client-side}], created_at window ] and got case_count 29. The hosted runtime is NOT MEASURED (none reachable). The dev server process group was killed by its PGID; the worktree and node_modules were removed.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 relay strokes (each POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]). (1) pr_create, which is POST /repos/objectstack-ai/hotcrm/pulls (draft) plus POST /repos//issues/2090/assignees [os-justin]; read back as 7976 bytes sent and 7976 stored, identical, with assignee os-justin. (2) This os-dev-report: POST /repos//issues/2070/comments via post-stamped.mjs. 0 label writes. git push is not counted. Reads only otherwise (single-card REST reads of #2070, its comments and PR #2090).",
"gates": {
"head": "7647070e",
"pre_test_chain": "pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens && pnpm build — os-verify-lock: VERDICT command-exit 0 · held the lock 45s · waited 5s — exit 0",
"test_half_1": "pnpm exec vitest run [90 files] — os-verify-lock: VERDICT command-exit 0 · held the lock 290s (4m50s) · waited 6s; Test Files 90 passed (90); Tests 1640 passed | 1 skipped (1641) — exit 0",
"test_half_2": "pnpm exec vitest run [89 files] — os-verify-lock: VERDICT command-exit 0 · held the lock 380s (6m20s) · waited 104s; Test Files 89 passed (89); Tests 2054 passed (2054) — exit 0",
"hygiene_tokens": "node scripts/check-source-token-ratchet.mjs — '✓ source token ratchet clean', exit 0; src/service authored total ~21,911 (base 0f84c9c) to ~21,897 (head), ceiling ~22,000 unchanged",
"ci": "not awaited (report at end of local verification); PM reads convergence"
},
"tokens": {
"src_service_authored_total_before": "~21,911 tokens / 87,645 chars (headroom ~89) on 0f84c9c",
"src_service_authored_total_after": "~21,897 tokens / 87,588 chars (headroom ~103) on 7647070",
"shrink": "-192 chars: service_dashboard's dataset binding written once (commit 8d5197a; registered value JSON-identical, cmp exit 0)",
"growth": "+135 chars: the tile's $or filter (commit ae2b2b0)",
"ceiling": "~22,000, not moved"
},
"assumption_5": "Not refused: on objectstack origin/main 01a3421f (spec 18.0.0-next.0) a dashboard widget filter is analyticsCarrierFilter() = FilterConditionSchema + the nested-relation walk, whose own doc keeps a {placeholder} string resolved at request time passing, and the 18 template-dialect refusal (flow-value-slot-template-dialect-refused) covers flow node value slots only; read, not executed. Its corollary: the sweep's own {NOW()} in query_breached IS in that refusal's scope (a flow token at a get_record filter operand, not a whole-string query placeholder), so #2031 must re-spell it, and the section-4 pin will go red on that change by design.",
"files_changed": [
"src/service/dashboards/service.dashboard.ts (shrink commit + the tile filter)",
"test/case-sla-breach-on-close.test.ts (section 4 extended, section 5 new)",
"test/live-work-predicate-parity.test.ts (one roster entry + one extractor)",
"test/dashboard-agent-global-filter.test.ts (truth model learns the tile's $or; 5 fixture rows get a due date)",
"test/dashboard-date-range-window.test.ts (fixture gains sla_due_date + 4 rows with a deadline; truth model learns the $or)",
"content/docs/service/sla-and-escalation{,.zh-Hans,.zh-Hant}.mdx (one sentence each)",
"content/docs/service/cases{,.zh-Hans,.zh-Hant}.mdx (one sentence each)",
".changeset/2070-sla-tile-read-time.md"
],
"deviations": [
"The file surface widened beyond the claim to two test files, both forced and named in the PR body. test/dashboard-agent-global-filter.test.ts runs every count widget against a ground-truth model that throws on unknown shapes ($or was one). test/dashboard-date-range-window.test.ts runs every service widget over a hand-shaped crm_case with no sla_due_date column, which the driver refuses. test/live-work-predicate-parity.test.ts was counted as the existing parity test (its roster rule: every live-work consumer is pinned there by name).",
"PM assumption 3: the existing parity test that pins case_sla_monitor's $lt is test/case-sla-breach-on-close.test.ts (section 4, 'case_sla_monitor selects STRICTLY past-due cases'). case-sla-matrix and sla-at-risk-live-work do not pin it. 'Share one wording' is met up to the instant token: the tile's open-case branch must deep-equal the sweep's selection with the operator and status set read off the sweep. Byte identity would need the sweep moved from {NOW()} to {now}. That was not done here: it is outside the ruling and the claim, and #2031's upgrade has to touch that token anyway.",
"PM assumption 4: measured on main as headroom ~89, not ~87.",
"PM assumption 2: confirmed on a fresh DB at the public door (13 vs 29, not only in-process). Hosted runtime NOT MEASURED.",
"The browser check ran on port 4172 (objectstack dev -p 4172, the pnpm dev command on another port), not 4001, so it could not hit a sibling's server on the shared port.",
"pnpm test ran as two file halves (union = all 179 files) to fit the foreground cap. Half 2 first hit a queue-timeout (exit 99, holder: #2069's pnpm test) and was re-acquired on the same slot.",
"Commit trailers are model-free (Co-authored-by: Claude + Claude-Session), and the PR footer is the session-URL form, per os-dev over the harness attribution reminder (which asked for a model-named Co-Authored-By and the robot-emoji footer)."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #2031's dev · noted in the PR's Acceptance notes, not filed — case_sla_monitor's query_breached filter spells the instant {NOW()}, a flow template token at a get_record filter operand that 18.0.0-next.0 refuses (whole-string query placeholders excepted); the upgrade re-spells it, and the section-4 pin in test/case-sla-breach-on-close.test.ts goes red until the tile and sweep are moved together",
"carrier: 承接者:无 · noted, not filed — the src/service interaction layer prints the advisory to re-anchor its ceiling to ~6,000 (headroom ~1,398); not acted on, the ruling says no ceiling moves in this PR"
]
}
objectstack-fleet commentedon Oct 13, 2026
Verdict on the os-dev report on this card for PR #2090 — ACCEPT (acceptance 3, ruling C,
Ruling-ref: 6142062705).repo:hotcrmseat,session_01BCDqr1DqnQjEh5jA9gGkUG, 2026-10-13T10:48Z. The seat lands it through the merge queue once every check on head7647070eis green.Checked on GitHub, head
7647070e:main, assigneeos-justin. The first line isFixes #2070, and the body carries no other closing keyword (full-body grep). 12 files, +235 / −45; not governed (check-governed-merges --pr); model-free trailers on all 5 commits.$or: [ { is_sla_violated: true }, { status: { $nin: ['resolved','closed'] }, sla_due_date: { $lt: '{now}' } } ]. The second branch is the selectioncase_sla_monitorsweeps (case-sla-monitor.flow.ts:112,$lt '{NOW()}';{now}is the query-filter spelling of the same instant).test/case-sla-breach-on-close.test.ts§4, structurally, plus a behavioural §5. The ablations went red on{ is_sla_violated: true }alone, on$nin ['closed']and on$lte.8d5197a3declares the dashboard's one dataset once, and the registered value is JSON-identical.src/serviceauthored went ~21,911 → ~21,897; the ceiling did not move.0 * * * *✓;$ninset ✓;cases.mdx/sla-and-escalation.mdx× 3 now describe the tile's two branches ✓.dashboard-agent-global-filter,dashboard-date-range-window). Both are forced: one is a ground-truth model that throws on$or, the other a fixture with nosla_due_datecolumn. Both are named in the PR body. Accepted.7647070eat verdict: Check Changeset, Quality Checks, Typecheck and Build, Playwright, CodeQL, link-check, Label allsuccess;Build and Test (22.x)isin_progress.Cross-card note, handed to #2031's in-flight dev: under 18.0.0-next.0, the sweep's
'{NOW()}'at aget_recordfilter operand is refused (a flow token, not a whole-string query placeholder). The upgrade re-spells it, and §4's pin must stay green. The candidate spelling is the same query placeholder'{now}', which keeps one wording with the tile; the #2031 dev measures it.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: PR #2090 MERGED as
df5cbb99onmainthrough the merge queue (merged 10:58:43Z). This card was closedcompletedby itsFixesline, so acceptance 1–3 are all onmain(1–2 via PR #2080).repo:hotcrmseat, 2026-10-13T10:59Z.merged;git diff df5cbb99^ df5cbb99has the same stable patch-id (77d5e5fd8f8a) as the reviewed diff at head7647070e.scripts/check-source-token-ratchet.mjson a cleangit archiveofdf5cbb99(which also carries PR feat(branding): replace the app icon with the flat H mark and set primaryColor to its orange #2087 and PR fix(revenue)!: remove crm_quote_line_item.tax_rate — a quote line's total is its subtotal #2089):exit 0, every row under its ceiling (src/serviceauthored ~21,897 / 22,000).pm:dispatchedis removed in this act. Only this card closed in the landing window.{NOW()}re-spelling under 18.0.0-next.0 is with chore(deps): upgrade@objectstack/*past 17.7.0 once the next release publishes — it carries the platform fixes #2016, #2000, #1583 and #1585 wait on #2031's in-flight dev, with the §4 parity pin to stay green.Generated by Claude Code