Repository navigation
Commit 314ad95
fix(objectql): defer the first lifecycle sweep one interval on a datastore this boot created (#30214)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgHwNYAiJoiu2ymqfmWX9z1V)_
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
## 维护者速读
- 改了什么:全新建好的数据库,开机 60
秒那次生命周期清扫(保留期读取、表计数、悬空引用审计)不再跑,推迟一个周期(默认一小时)再扫;已有数据的库照旧 60 秒首扫。
- 为什么改:新库里没有任何一行早于这次开机,最短保留窗口是一小时,首扫注定什么都删不掉,却在 cloud 托管内核上花掉第一分钟 160
条语句里的 130 条。
- 风险与回滚:只改调度器的首次触发,直接调用 sweep() 照扫;最坏情况是新库的过期行晚一个周期被清,回滚即还原
lifecycle-service.ts 一处判断。
- 先看:`packages/objectql/src/lifecycle/lifecycle-service.ts` 的 `start()`
与 `firstSweepDueAtInitialDelay()`;再看同目录测试文件末尾 `LifecycleService timers`
里的四个 #29918 用例。
- 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run
src/lifecycle/lifecycle-service.test.ts -t 29918`,预期 4 个用例全绿。
- 需要你做的:无,席位落地。卡的条目 1(持久化最后清扫时间,让重启也省掉首扫)另待裁定,本 PR 不关卡。
Refs #29918 (item 2). Item 1, the persisted last-swept watermark, is not
in this PR and waits on a decision (report 6646684881 on the card), so
this PR does not close the card.
## What changes
- `LifecycleService.start()` now asks, when the initial delay fires,
whether the first sweep is due yet (`firstSweepDueAtInitialDelay()`). It
is not due when the engine reports that this process created its
datastore from empty: `ObjectQL.wasDatastoreCreatedFromEmpty()`, which
answers from every driver's schema-sync stats, in process and without
issuing a statement. In that case the scheduled first sweep is skipped,
and the interval is armed as before, so the first sweep runs one
interval later.
- `LifecycleEngineLike` gains the optional member
`wasDatastoreCreatedFromEmpty?(): boolean`. The real engine already has
it. An engine that omits it, or whose answer throws, is treated as not
fresh and keeps the old schedule.
- The gate sits on the scheduler's first tick, not at the entry of
`sweep()`. Direct `sweep()` callers (the dogfood growth gate,
`db:clean`-style tooling, and more than a dozen suites in objectql,
service-knowledge, service-queue and service-messaging that call it on
purpose) keep sweeping. The reference audit is a leg of `sweep()` with
no timer of its own (`auditReferences()`; its only other caller is the
CLI's orphan-detail sweep), so it is deferred with the same tick.
- `OS_LIFECYCLE_DISABLED`, `enabled: false`, `initialDelayMs` and
`sweepIntervalMs` keep their meaning. One changeset, `patch` on
`@objectstack/objectql`.
## Why
A fresh store holds only rows this process wrote, so none is older than
the initial delay (60 s), and the duration grammar's smallest window is
one hour (`parseLifecycleDuration`). The first sweep on such a store
therefore cannot reap anything. The card measured it on cloud's hosted
tenant composition as 130 statements in the 50 to 60 s slice of a first
boot (17 retention reads, 16 counts, the reference audit and the
governance settings reads). Deferring it costs at most one interval of
overstay, which the hourly cadence already allows any row.
## Tests
Four cases added to the existing `LifecycleService timers` describe in
`lifecycle-service.test.ts`. Each counts every data-plane door the sweep
reaches: reaps, governance settings reads and the reference audit.
- Fresh store: nothing at the initial delay (0 deletes, 0 audits, 0
settings reads), then one sweep one interval later.
- Store that existed before the boot, or an engine whose answer throws:
sweeps at the initial delay as before.
- Direct `sweep()` on a fresh store: still sweeps.
- `OS_LIFECYCLE_DISABLED=1` on a fresh store: nothing is armed.
## Acceptance notes
- Card "Done when", clause 1 (no lifecycle-sweep statements in a fresh
kernel's first minute): met for the measured first-boot reading. With
this change no statement is issued at all, not even a watermark read.
cloud re-measures under cloud#3413.
- Clause 2 (a kernel whose watermark is older than the interval still
sweeps) belongs to item 1 and is not met here. A re-boot on an existing
store still sweeps at the initial delay. Item 1 has no existing
persistence seam that needs neither a new config key nor a new `sys_*`
object, so the seat sends it to a decision.
- Deviation from the seat's note: the seat asked for the gate at the
entry of `sweep()`. It sits on the scheduler's first tick instead,
because a gate at the entry would stop every direct `sweep()` caller
from sweeping on a fresh store. The reference audit is still deferred,
since it only runs inside `sweep()`.
## Verification
All readings on head `07cef192` (after one merge of `origin/main`), in
the worktree, with the dependency closure built.
- `pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2
src/lifecycle/`: Test Files 5 passed (5), Tests 135 passed (135).
- `pnpm --filter @objectstack/objectql test`: Test Files 548 passed
(548), Tests 10867 passed (10867), lock VERDICT command-exit 0.
- `pnpm --filter @objectstack/objectql typecheck` (`tsc --noEmit` and
`check:test-typecheck`): VERDICT command-exit 0.
- `pnpm lint`: exit 0.
- Derived gate list (`node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`): 59 families, 58 run and exit 0, 1
NOT MEASURED. `pnpm check:dual-build-cjs-loads` exits 3 (PREREQUISITE
NOT MET: it needs the whole workspace built) and is left to CI.
`check-plugin-teardown-shape.mjs --self-test` exited 3 on the shallow
clone, then exit 0 (48 cases) after fetching the commit its fixture
reads. `--ran` reconciliation: "59 derived famil(ies) accounted for — 58
run, 1 NOT-MEASURED".
<details><summary>Ablation of the freshness gate</summary>
Committed fix first (`027c6689`), then `node
scripts/ablation-replace.mjs` replaced `if
(this.firstSweepDueAtInitialDelay()) void this.sweep();` with an
unconditional `void this.sweep();` plus a marker comment (anchor 1 to 0,
blob `026c59ebf9a8` to `f36281d0bba1`) and ran `vitest run
src/lifecycle/lifecycle-service.test.ts -t 29918`:
```
× [#29918] issues no sweep at the initial delay on a store this boot created, and sweeps one interval later
Tests 1 failed | 3 passed | 114 skipped (118)
```
Restore: blob after restore `026c59ebf9a8` equals HEAD, `git diff HEAD`
empty. The other three cases stay green under the mutation, as they
should: they pin the unchanged paths. The suite imports the service from
source by relative path, so no build sits between the mutation and the
run.
</details>
Clause-②: yes (widening) — the exported `LifecycleEngineLike` gains the
optional member `wasDatastoreCreatedFromEmpty?(): boolean` (an engine
without it keeps the old schedule); no accept set changes.
Metadata-protocol: none
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6Ca4cwEoenneeTMpCvy)_
<!-- signed: domain:server#4 · os-bill ·
session_01Utp6CZMkHvuDeVYgTD73qF -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5fa9375 commit 314ad95
3 files changed
Lines changed: 161 additions & 2 deletions
File tree
- .changeset
- packages/objectql/src/lifecycle
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2541 | 2541 | | |
2542 | 2542 | | |
2543 | 2543 | | |
| 2544 | + | |
| 2545 | + | |
| 2546 | + | |
| 2547 | + | |
| 2548 | + | |
| 2549 | + | |
| 2550 | + | |
| 2551 | + | |
| 2552 | + | |
| 2553 | + | |
| 2554 | + | |
| 2555 | + | |
| 2556 | + | |
| 2557 | + | |
| 2558 | + | |
| 2559 | + | |
| 2560 | + | |
| 2561 | + | |
| 2562 | + | |
| 2563 | + | |
| 2564 | + | |
| 2565 | + | |
| 2566 | + | |
| 2567 | + | |
| 2568 | + | |
| 2569 | + | |
| 2570 | + | |
| 2571 | + | |
| 2572 | + | |
| 2573 | + | |
| 2574 | + | |
| 2575 | + | |
| 2576 | + | |
| 2577 | + | |
| 2578 | + | |
| 2579 | + | |
| 2580 | + | |
| 2581 | + | |
| 2582 | + | |
| 2583 | + | |
| 2584 | + | |
| 2585 | + | |
| 2586 | + | |
| 2587 | + | |
| 2588 | + | |
| 2589 | + | |
| 2590 | + | |
| 2591 | + | |
| 2592 | + | |
| 2593 | + | |
| 2594 | + | |
| 2595 | + | |
| 2596 | + | |
| 2597 | + | |
| 2598 | + | |
| 2599 | + | |
| 2600 | + | |
| 2601 | + | |
| 2602 | + | |
| 2603 | + | |
| 2604 | + | |
| 2605 | + | |
| 2606 | + | |
| 2607 | + | |
| 2608 | + | |
| 2609 | + | |
| 2610 | + | |
| 2611 | + | |
| 2612 | + | |
| 2613 | + | |
| 2614 | + | |
| 2615 | + | |
| 2616 | + | |
| 2617 | + | |
| 2618 | + | |
| 2619 | + | |
| 2620 | + | |
| 2621 | + | |
| 2622 | + | |
| 2623 | + | |
| 2624 | + | |
| 2625 | + | |
| 2626 | + | |
| 2627 | + | |
| 2628 | + | |
| 2629 | + | |
| 2630 | + | |
| 2631 | + | |
| 2632 | + | |
| 2633 | + | |
| 2634 | + | |
| 2635 | + | |
| 2636 | + | |
| 2637 | + | |
| 2638 | + | |
2544 | 2639 | | |
2545 | 2640 | | |
2546 | 2641 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
121 | 130 | | |
122 | 131 | | |
123 | 132 | | |
| |||
503 | 512 | | |
504 | 513 | | |
505 | 514 | | |
506 | | - | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
507 | 520 | | |
508 | 521 | | |
509 | 522 | | |
| |||
513 | 526 | | |
514 | 527 | | |
515 | 528 | | |
516 | | - | |
| 529 | + | |
517 | 530 | | |
518 | 531 | | |
519 | 532 | | |
520 | 533 | | |
521 | 534 | | |
522 | 535 | | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
523 | 574 | | |
524 | 575 | | |
525 | 576 | | |
| |||
0 commit comments