Repository navigation
The renewal-notice and expiration-sweep jobs produce nothing although 11 contracts carry is_expiring, and five of the six daily flows report acted: 0 while they notify - #102
Draft
objectstack-fleet[bot] wants to merge 3 commits into
Conversation
On a fresh demo `renewal_notice` acted on 0 of 25 and `expiration_sweep` selected nothing: every active contract inside its notice window was seeded already flagged `is_expiring` (F12's once-per-contract key), and no active contract's term had ended. The jobs were correct and the renewal and expiry reminders M3 asks to see in the inbox were never sent. Read the fixture as the book at the start of boot day: a notice deadline on an earlier day was flagged by that day's run, one of today is not yet. Move one backfilled contract to a deadline of today (F12) and two to a term that ended yesterday, one per F13 branch, and re-prove the selections and that section 10's dated counts did not move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HihZ11bQSqjCgjzHbpv4M1
…y reading The non-renewing F13 row, once expired, is refused on re-seed (819 ok / 1 error) — state the measurement where the row is placed. The "start of boot day" reading is claimed for F12 and F13 only; F11's seeded partial instalments past their planned date are not read that way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HihZ11bQSqjCgjzHbpv4M1
The placement counts are the fixture's own, not section 10's, so they are refused with a message naming the job that would go quiet instead of the "DESIGN.md section 10 pins" wording assertCount uses for the table's counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HihZ11bQSqjCgjzHbpv4M1
This was referenced Oct 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #97
The run-summary
acted: 0on flows that did notify stays with the platform (objectstack-ai/objectstack#22590 item 2, per the card); nothing here touches or works around it.Verdict: the two flows are correct, and the demo data gave them nothing to do
Measured on
main@46e65f0(17.7.0): freshpnpm demo --port 3497, thenPOST /api/v1/automation/NAME/triggerfor both jobs, signed in as the dev admin.renewal_notice(F12)notice_select25 · gateInside the windowskipped 25 ·notice_flag0 runsis_expiring = 1, and that flag is F12's once-per-contract key (is_expiring: { $ne: true }), so the query never returns them.expiration_sweep(F13)expire_select0 ·autorenew_select0activecontract has an end date before today. The nearest ends in 4 days.I applied DESIGN.md §06 F12 / F13 to all 60 seeded active contracts for the run date, computing from the seed plan rather than the database. F12's window holds exactly 11 contracts: the card's "11 contracts carry
is_expiring", all seeded already flagged. F13's window holds 0. So the queries match §06, and the card's either/or comes out as "the demo's dates". PR #42 measured both write paths on probe rows on 17.4, and run 1 below measures them again on 17.7. The consequence: a fresh demo never sends a renewal reminder or an expiry reminder, which is half of M3's acceptance (DESIGN.md §11: 「到期、逾期提醒在收件箱可见」).What changed:
src/data/**only, no flow touchedThe fix follows the pattern
handBackInReview(F3) andleaveUnassigned(F10) already use: pick a row by a rule, rewrite it, then check the result again.placeInSweepWindowsinsrc/data/plan-contracts.ts, applied afterhandBackInReview:end_dateset torenewal_notice_daysdays from boot, so its notice deadline is today:ICA-2026-0002, end moved from +81 to +60, notice 60 days. The rule only takes a notice period over 30 days and an end date already inside 90, so neither dated count below moves.NDA-2026-0006(does not auto-renew, so F13 expires it) andSUP-2026-0009(auto-renews, so F13 drafts its renewal). Both originally ended more than 90 days out.pnpm validate: exactly 1 unflagged contract with a deadline of today; exactly 1 + 1 active contracts past their end date; §10's "10 within 30 days" is still 10; the 管理层expiring_90_daysband is still 12; no moved contract starts in the future.renewalFlaggedreplacesisExpiringincontract.seed.tsand changes the predicate fromendDate - renewalNoticeDays <= 0to... < 0. For F12 and F13, the fixture now describes the book as a daily run leaves it at the start of boot day: a deadline on an earlier day was flagged by that day's run, and a deadline of today is still waiting for today's run. So the seeded flag is the value F12 itself left (_shared.tsrule 2). For every row that already existed, the two predicates agree (the latest in-window deadline was day −3), so only the new row changes.is_expiringgoes from 11 to 13, because both F13 rows were flagged back when their windows opened.After: measured on
029c9efThe later commits change only comments and error text. The seed output is byte-identical on both commits: the sha256 of
JSON(CONTRACT_PLAN)isc58f14d9001b19beand the sha256 of the contract seed is1eaf54465270b941, on029c9efand on7d8a059.Sequence: wipe
.objectstack/data,pnpm demo --port 3497, do the README operator setup (create the five named accounts, grantclm_requesterto the three requesters, giveclm_legal_counselto the two counsel), then runpnpm demoagain. The handover reads owners 43 / 43 / 34 and legal owners 34 / 34. Before the first trigger:sys_inbox_message0.Run 1 (both triggers answered
200,success: true,failed: 0)renewal_noticenotice_select23 ·notice_flag1 run ·notice_tellsent to 2 recipientsexpiration_sweepexpire_select1 ·expire_flag1 ·expire_tellto 2 ·autorenew_select1 ·auto_existing0 ·auto_create1 ·auto_tellto 2Database after the run, read with
better-sqlite3opened read-only:clm_renewal_notice_dueclm_renewal_notice_dueclm_contract_expiredclm_contract_expiredclm_contract_auto_renewedclm_contract_auto_renewedRun 2 (both jobs triggered again):
renewal_noticenotice_select22,notice_flag0 runs.expiration_sweepexpire_select0,autorenew_select1,auto_existing1, so no second draft. Inbox stays at 6 and renewal drafts stay at 1.Browser: the right people see the reminders
Playwright with the pre-installed Chromium on
http://localhost:3497/_console/. Two personas signed in through the login form.1. The panel reads "Renewal decision due: Independent Contractor Agreement — Aisha Bello — Contract ICA-2026-0002 ends on 2026-12-09, and notice of non-renewal is owed by 2026-10-10 (60 days before). Start a renewal from the contract page, or give notice." Clicking it opens/_console/apps/app.objectstack.hotclm/clm_contract/record/4nhAxpFoBuKhTsYi, the ICA-2026-0002 page, with Start Renewal in the header, Business owner Business Requester 2 and Legal owner Legal Counsel 2.2, showing "Expired: Mutual Non-Disclosure Agreement — Basalt Energy …" and the same renewal notice. Clicking the first message opens the NDA-2026-0006 record.GET /api/v1/data/sys_approval_request … -> 403 [PERMISSION_DENIED]on the contract record page, for both personas. This is already reported in the Full browser test on 17.7.0main: drive the whole contract lifecycle as every audience, in en and zh-CN, and report what a real user hits #87 pass (its finding 3) and is not touched here.Trade-offs, measured
pnpm demostops being re-runnable once the daily jobs have run: the seed re-asserts statuses the state machines refuse to go back to #41's set grows by one row, and that row is reachable on boot day. Afterexpiration_sweephas expiredNDA-2026-0006, re-runningpnpm demoprints819 ok / 1 error:contract_state_machinerefuses the row loudly ("A expired contract is closed; its status cannot change to active"). This is the shapepnpm demostops being re-runnable once the daily jobs have run: the seed re-asserts statuses the state machines refuse to go back to #41 describes, andpnpm demostops being re-runnable once the daily jobs have run: the seed re-asserts statuses the state machines refuse to go back to #41 remains open as the decision covering every contract the jobs move. Without this row, the contract half ofpnpm demostops being re-runnable once the daily jobs have run: the seed re-asserts statuses the state machines refuse to go back to #41 only appears once the database is more than 4 days old.ICA-2026-0002readsis_expiring = 0again, so the next F12 run would send its renewal notice a second time. Same class (the fixture re-asserting a column a job wrote), no state machine to refuse it, and covered by whateverpnpm demostops being re-runnable once the daily jobs have run: the seed re-asserts statuses the state machines refuse to go back to #41 decides.Proof the checks can fail (ablation, run from a commit)
node scripts/ablation-replace.mjs(the objectstack tool) mutatedrenewalFlaggedback to<= 0and ranpnpm validateunder the shared verify lock. The anchor hit once, the blob changedbf1965a692ef -> 059271db73c2, and validate exited 1 with:The restore was checked by the bytes, not the exit code: the blob equals HEAD (
bf1965a692ef) andgit diff HEADis empty.Gates, on the final head
7d8a059pnpm verify(= the four CI steps) under the shared verify lock:VERDICT command-exit 0.The 7 validate warnings and 6 lint suggestions all name either the
hierarchy-securitycapability orcontract_approvalnodes. This diff touches neither.Acceptance notes
@objectstack/*dependencies from 17.4.0 to 17.7.0, walking the official upgrade checklists of 17.5, 17.6 and 17.7 #82 is the decision on that); every run above was a manual trigger. No platform workaround.selecteddisagrees with the node-level count (for example, a summary of 25 against anotice_selectof 23). That counter belongs to the platform, so every number above is node-level.partialinstalments. The 18partialinstalments planned 15 to 205 days ago are not the state a daily F11 would have left; they flip tooverdueon the first run. That is whyrenewalFlaggedclaims the "start of boot day" reading for F12 and F13 only. Carrier: none.ICA-2026-0002), which skipped review and approval by definition. This sits next to 34 backfilled contracts carry alegal_owner, andis_backfilledsays on the same row that no review ever happened #53's open question about backfilled contracts. Carrier: none.Generated by Claude Code