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
Found while browser-verifying card 09 (#39). Not a defect in the jobs — a consequence of them existing that the seed does not account for. Filed unassigned, out of scope for #39.
The documented workflow that now breaks
README.md tells the evaluator to re-run the demo:
Every dataset is an upsert, so create the accounts and run pnpm demo again and their contracts are handed over.
That works on a freshly seeded database. It stops working once card 09's daily jobs have run once, because those jobs are the declared writer of two statuses the fixture also declares, and the state machines refuse the way back.
Reproduction, measured on claude/issue-39-post-signature-jobs @ 83eda0a, 17.4.0, better-sqlite3
pnpm demo on an empty database — 820 ok / 0 errors.
Run the sweeps once each: POST /api/v1/automation/expiration_sweep/trigger, POST /api/v1/automation/payment_overdue/trigger.
pnpm demo again.
Boot banner on step 3:
⚠ Seeds: app.objectstack.hotclm 818 ok / 2 errors ⚠
✗ Failed to write clm_contract record #99 (title=Data Processing Agreement — Fernway Cleaning):
hook 'contract_state_machine' threw: A expired contract is closed; its status cannot
change to active. Start a renewal or an amendment instead.
✗ Failed to write clm_payment_plan record #2 (contract+seq=["…","3"]):
hook 'payment_plan_state_machine' threw: Payment instalment status cannot go from
overdue to planned. Allowed from overdue: partial, paid.
Two rows are dropped and every other row still loads, so the run looks almost clean — the same "the summary reads like a clean load" shape #39's acceptance criteria warn about.
Why it happens
The fixture writes status as a literal on every upsert. Once a job has moved a row on:
expiration_sweep (F13) moves an active contract past its end date to expired. §03 makes expired terminal, so re-asserting the fixture's active is refused.
payment_overdue (F11) moves a due or partial instalment past its planned date to overdue. §03 gives overdue only → partial / → paid, so re-asserting planned is refused.
Both refusals are correct: the state machines are doing exactly what DESIGN.md §03 asks. It is the fixture's unconditional status that has no answer for "this row has since moved on".
The two rows are the ones whose dates the jobs act on, so the count grows as the demo database ages: any contract that expires and any instalment that falls into arrears joins the set.
What a fix has to decide (hence: not a rider on #39)
Whichever way it goes it is a change to card 08's seed, and it needs a product answer first:
A — leave status out of the upsert on re-seed. The fixture stops fighting the jobs, and a demo database shows the lifecycle actually moving. Costs the guarantee that pnpm demo restores a known corpus.
B — re-seed onto a clean database only, and say so in the README instead of promising re-runnability. Cheapest, and honest, but it takes away the account-handover workflow the README currently recommends.
Found while browser-verifying card 09 (#39). Not a defect in the jobs — a consequence of them existing that the seed does not account for. Filed unassigned, out of scope for #39.
The documented workflow that now breaks
README.mdtells the evaluator to re-run the demo:That works on a freshly seeded database. It stops working once card 09's daily jobs have run once, because those jobs are the declared writer of two statuses the fixture also declares, and the state machines refuse the way back.
Reproduction, measured on
claude/issue-39-post-signature-jobs@83eda0a, 17.4.0, better-sqlite3pnpm demoon an empty database —820 ok / 0 errors.POST /api/v1/automation/expiration_sweep/trigger,POST /api/v1/automation/payment_overdue/trigger.pnpm demoagain.Boot banner on step 3:
Two rows are dropped and every other row still loads, so the run looks almost clean — the same "the summary reads like a clean load" shape #39's acceptance criteria warn about.
Why it happens
The fixture writes
statusas a literal on every upsert. Once a job has moved a row on:expiration_sweep(F13) moves anactivecontract past its end date toexpired. §03 makesexpiredterminal, so re-asserting the fixture'sactiveis refused.payment_overdue(F11) moves adueorpartialinstalment past its planned date tooverdue. §03 givesoverdueonly→ partial/→ paid, so re-assertingplannedis refused.Both refusals are correct: the state machines are doing exactly what DESIGN.md §03 asks. It is the fixture's unconditional
statusthat has no answer for "this row has since moved on".The two rows are the ones whose dates the jobs act on, so the count grows as the demo database ages: any contract that expires and any instalment that falls into arrears joins the set.
What a fix has to decide (hence: not a rider on #39)
Whichever way it goes it is a change to card 08's seed, and it needs a product answer first:
statusout of the upsert on re-seed. The fixture stops fighting the jobs, and a demo database shows the lifecycle actually moving. Costs the guarantee thatpnpm demorestores a known corpus.overdue → paid → …is not reachable either, so this means a system-context reset, not a transition). Most machinery, and it puts a second writer on the columns Card 09 — F9 activation · F3/F4/F10–F14 scheduled jobs · F16 executed upload (M3) #39 gave exactly one.I have no view worth acting on between them; A looks smallest but it changes what "the demo dataset" means, which is a §10 question.