Skip to content

F3 审查超时提醒仍按固定 30 天触发;应读合同类型的审查时限,与看板「审查超时限」一致 - #206

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-203-f3-type-sla
Nov 4, 2026
Merged

zhuangjianguo merged 1 commit into
mainfrom
claude/issue-203-f3-type-sla

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #203

F3 (legal_review_sla) now reminds on each contract type's own review SLA, using the same rule as the legal workbench's 「审查超时限」 tile (PR #201). It no longer uses a fixed 30/60 days.

What changed

  • src/flows/legal-review-sla.flow.ts: one daily query in place of the two fixed bands. It selects status: 'in_review' and review_sla_days_offset: { $ne: null }, and review_started_at: { $lt: { $now: true, addDays: { $field: 'review_sla_days_offset' } } }. The anchor is a literal operand, so the flow executor hands it to the query unchanged (crud-nodes.ts resolveNodeFilter evaluates CEL envelopes only), and the ObjectQL read path pins $now (resolveFilterTokens, core filter-tokens.ts). DESIGN §06 F3's 「超一倍抄送 clm_legal_head」 is a per-row CEL decision over the two projected fields: datetime(review_started_at) earlier than addDays(now(), offset + offset). Its two edges are exact complements, so each selected row gets exactly one notification per run: over SLA goes to the legal owner; over double the SLA goes to the legal owner with the head of legal copied. That is F3's existing de-duplication (a daily nudge, one notice per row per run), kept as it was.
  • NULL offset does not remind. At 18.3.0 the SQL driver reads a NULL addDays column as zero days (driver-sql cross-field-conformance-cases.ts, #25808 table, row 4). An upgraded database holds NULL until the contract is next written (objectstack#29961). The $ne: null conjunct is the filter grammar's has-a-value predicate.
  • src/datasets/contract.dataset.ts (review_over_sla_count only, claim amendment): the same review_sla_days_offset: { $ne: null } guard, so the tile and the reminder count the same rows.
  • src/email-templates/legal-review-sla.email-template.ts + index.ts: clm.legal_review_over_30_days / _over_60_days become clm.legal_review_over_sla / clm.legal_review_over_double_sla (en + zh-CN). Topics are renamed to match: clm_legal_review_over_sla / clm_legal_review_over_double_sla. The wording no longer says 30/60 days.
  • clm_contract_type.review_sla_days help text (object + en + zh-CN): it now says what F3 does, and that a contract keeps the value it was last saved with.
  • src/data/plan-contracts.ts (handBackInReview only, claim amendment): the one unassigned in-review contract has to sit in F3's over-SLA band (legal owner as sole recipient) to keep the 「Nobody to tell」 edge exercised. Every dealt in-review row is past twice its type's SLA, so the chosen row (shortest review first) has its whole timeline shifted to SLA + ⌈SLA/2⌉ days in review. The function then re-proves the band on the result. On this corpus it is MSA-2026-0004 at 8 days (SLA 5).
  • No app-side stamp, hook or mirror column. _daily-sweep.ts is untouched.

Evidence: browser + persona readings, 18.3.0, pnpm demo:zh, port 3170, throwaway DBs

Fixture. The card's three cases need rows a persona cannot write: review_started_at is a lifecycle stamp, and the offset is a stored expression. So after boot and seed, the same three seeded in-review rows were edited directly in each throwaway SQLite DB, identically on both trees (2026-11-03T23:48Z before, 23:55Z after):

  • DPA-2026-0005 (SLA 5): review started exactly 6 days ago.
  • MSA-2026-0001 (SLA 5): review started exactly 1 day ago.
  • NDA-2026-0003: review_sla_days_offset = NULL, the upgraded-DB state; review is 38 days old.

F3 cron was overridden to * * * * *, uncommitted and reverted (git diff HEAD empty afterwards). Inboxes were read as each persona from GET /api/v1/data/sys_inbox_message, signed in with the demo password.

Case origin/main 7257a23 this branch 99c58b1
DPA-2026-0005, 6 days, SLA 5 (counsel2) no reminder clm_legal_review_over_sla · warning, 1 per tick (23:56:00, 23:57:00); head not copied
MSA-2026-0001, 1 day, SLA 5 (counsel1) no reminder no reminder
NDA-2026-0003, NULL offset, 38 days (counsel1) reminded 1 per tick (over_30_days) no reminder
the 8 other owned rows, 16–57 days (SLA 3–10) of main's 8 owned others, the 4 in its 30–60-day band reminded; the rest silent (main's unassigned row was MSA-2026-0005, 56 days: nobody to tell) over_double_sla · critical, 1 per tick each, legal.head copied (8 per tick)
MSA-2026-0004, unassigned, 8 days (after seed change) n/a no message anywhere (「Nobody to tell」 edge)

The fresh seed with no fixture, read the same way: 11 rows in the doubled band, each 1 notice per tick to its counsel plus 11 to legal.head; the unassigned MSA-2026-0004 sends nothing.

NULL offset reads as zero days, measured on the REST door. As counsel1, GET /api/v1/data/clm_contract with the unguarded anchor filter returns 11 rows including NDA-2026-0003. With review_sla_days_offset: { $ne: null } added it returns 10, without it.

Workbench tile 审查超时限, read in Chromium (zh-CN) as counsel1 on /_console/apps/app.objectstack.hotclm/dashboard/legal_workbench, from POST /api/v1/analytics/dataset/query (200):

Reading total 主服务协议 双向保密协议 供应商协议 数据处理协议 租赁协议 独立承包人协议
origin/main, fresh seed 12 6 2 1 1 1 1
branch, fresh seed (moved row still over its SLA) 12 6 2 1 1 1 1
branch, with fixture 10 5 1 1 1 1 1

With the fixture, the tile's 10 equals F3's set for that tick: DPA over SLA, 8 doubled, and MSA-2026-0004 over SLA with nobody to tell. Browser console on both trees: one 404 (Not Found) resource error, the same before and after. Boot banner on both: Flows: 13, seeds 1130 rows, no degraded / no such table.

Gates (final commit 99c58b1, each exit code captured before any pipe)

pnpm install                     EXIT=0
pnpm validate                    EXIT=0   ✓ Validation passed (674ms)
pnpm lint                        EXIT=0   6 suggestion(s), 0 warnings (same as origin/main)
pnpm typecheck                   EXIT=0
pnpm lint:i18n-gate              EXIT=0   COVERAGE : 0 missing keys across 2 locale(s)
CLM_DEMO_SEED=1 pnpm validate    EXIT=0   ✓ Validation passed (834ms)

pnpm validate prints 7 ⚠ lines (approval-approver and capability notices) on both origin/main and this branch; none name legal_review_sla.

Acceptance notes

  • Governed text is not in this PR. The proposed DESIGN.md §06 F3 row goes to the seat for PR Sync the governed design text with the rulings landed on 2026-10-25 (#96 #100 #11 #20 #38 #82) #120. The flow's old header cited §12 gap fix: make the app browsable — declare auth, pin better-auth, require browser verification #7 ("no daysBetween in CEL"). That is stale on 18.3: @objectstack/formula registers daysBetween, addDays, now() and datetime(), and this PR's decision edge uses them.
  • Notification language. The demo personas have sys_user.locale = null, so F3's notices (and every other bundle's) fall to the en-US member even under demo:zh, as _bundle.ts documents. The same holds on origin/main.
  • Raw timestamp in the notice. The {{ review_started_at }} hole renders the raw ISO instant (2026-10-09T00:00:00.000Z). This is unchanged from origin/main.
  • A type SLA edit doesn't reach running reviews. An edit of a type's review_sla_days reaches a contract already in review only when that contract is next saved (stored expression, recomputed on the contract's own write). The new help text says so.
  • Stale seed comment. src/data/contract.seed.ts says "two contracts in review are handed back"; UNASSIGNED_IN_REVIEW is 1. This was stale before this PR, and the file is outside the claim, so it is left as is.
  • Renamed topics and templates. No other file reads the old names (grep over src/).

🤖 Generated with Claude Code

https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn

F3 fired at a fixed 30 days while the legal workbench counts a review as
over SLA by its type's review_sla_days (2/3/5/10 days) since #201. F3 now
selects with the same rule: in review, review_started_at earlier than the
$now anchor shifted by the stored review_sla_days_offset. The head of legal
is copied past twice the SLA (DESIGN.md §06 F3, 超一倍) via a per-row CEL
decision over the two projected fields.

At 18.3.0 the SQL driver reads a NULL offset column as zero days, and an
upgraded database holds NULL until the contract is next written
(objectstack#29961), so both F3 and the workbench's review_over_sla_count
require review_sla_days_offset to have a value.

The demo seed moves its one unassigned in-review contract into
(SLA, 2 x SLA] so F3's "nobody to tell" edge stays exercised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · head 99c58b1 · 2026-11-04T00:03Z

check reading
shape draft → main, Fixes #203; base 7257a23, mergeable clean
CI on the head both Validate check-runs success on 99c58b1 = PR head.sha
scope 8 files, inside the claim as amended (seat call A: the head-of-legal copy at 2× the type SLA, as §06 F3 already says). F3 selects in-review contracts past $now + review_sla_days_offset with review_sla_days_offset: { $ne: null }; the 2× band is a per-row CEL decision; review_over_sla_count gains the same NULL guard; the seed's one unassigned in-review row moves to SLA + ⌈SLA/2⌉ days so the 「Nobody to tell」 edge stays covered. No stamp, hook or mirror column. Lint 0 warnings
measured (18.3.0, every-minute cron override, reverted) a DPA review 6 days in (SLA 5) reminds its counsel, no head copy; an MSA 1 day in does not; a NULL-offset contract does not (main reminded it once per tick); 8 rows past 2× reach legal.head; the unassigned row sends nothing. Tile on a fresh seed: 12, the same per type as main; with the fixture the tile and F3's set agree at 10
seat calls on the open questions §06 F3 row: A, carried on PR #120. §12 「CEL 日期算术」 row: B, left until a card needs date formulas
residuals demo notifications render in English because demo staff have no locale (existing cards #172 / #142); the F3 notice prints the raw ISO start time (polish, not filed)

Landing: under the maintainer's merge authorization (project thread: 「你可以自己审核的就自己合并」).


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review November 4, 2026 00:03
@zhuangjianguo
zhuangjianguo merged commit ad4bdbc into main Nov 4, 2026
2 checks passed
zhuangjianguo pushed a commit that referenced this pull request Nov 4, 2026
Code: PR #206.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

F3 审查超时提醒仍按固定 30 天触发;应读合同类型的审查时限,与看板「审查超时限」一致

2 participants