Repository navigation
fix(objectql): defer the first lifecycle sweep one interval on a datastore this boot created - #30214
Conversation
…store this boot created Claude-Session: https://claude.ai/code/session_01Utp6Ca4cwEoenneeTMpCvy Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin eb7e83478fdbc4a15af71cd74b2c5cf3ba8c894e && git checkout eb7e83478fdbc4a15af71cd74b2c5cf3ba8c894e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f2a9007e93f899f8f1a114ed3de7729bd681741a 168b9ded00f5b061cd4a2420fa844e7cc19fec5a && git checkout -B drift-repro f2a9007e93f899f8f1a114ed3de7729bd681741a && git merge --no-ff 168b9ded00f5b061cd4a2420fa844e7cc19fec5a
node scripts/docs-audit/affected-docs.mjs --json f2a9007e93f899f8f1a114ed3de7729bd681741a
|
…a Clause-② widening Claude-Session: https://claude.ai/code/session_01Utp6Ca4cwEoenneeTMpCvy Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF 维护者速读
席位意见:ACCEPT。小而准,测试数到每一扇数据门,消融做了。 席位核对Head-sha:
|
Requested by Bill · project thread
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
维护者速读
packages/objectql/src/lifecycle/lifecycle-service.ts的start()与firstSweepDueAtInitialDelay();再看同目录测试文件末尾LifecycleService timers里的四个 objectql: the lifecycle sweep runs 60 s after every kernel boot, even on a database created seconds earlier, so a fresh tenant kernel pays 130 statements in its first minute #29918 用例。pnpm --filter @objectstack/objectql exec vitest run src/lifecycle/lifecycle-service.test.ts -t 29918,预期 4 个用例全绿。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.LifecycleEngineLikegains the optional memberwasDatastoreCreatedFromEmpty?(): 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.sweep(). Directsweep()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 ofsweep()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,initialDelayMsandsweepIntervalMskeep their meaning. One changeset,patchon@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 timersdescribe inlifecycle-service.test.ts. Each counts every data-plane door the sweep reaches: reaps, governance settings reads and the reference audit.sweep()on a fresh store: still sweeps.OS_LIFECYCLE_DISABLED=1on a fresh store: nothing is armed.Acceptance notes
sys_*object, so the seat sends it to a decision.sweep(). It sits on the scheduler's first tick instead, because a gate at the entry would stop every directsweep()caller from sweeping on a fresh store. The reference audit is still deferred, since it only runs insidesweep().Verification
All readings on head
07cef192(after one merge oforigin/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 --noEmitandcheck:test-typecheck): VERDICT command-exit 0.pnpm lint: exit 0.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-loadsexits 3 (PREREQUISITE NOT MET: it needs the whole workspace built) and is left to CI.check-plugin-teardown-shape.mjs --self-testexited 3 on the shallow clone, then exit 0 (48 cases) after fetching the commit its fixture reads.--ranreconciliation: "59 derived famil(ies) accounted for — 58 run, 1 NOT-MEASURED".Ablation of the freshness gate
Committed fix first (
027c6689), thennode scripts/ablation-replace.mjsreplacedif (this.firstSweepDueAtInitialDelay()) void this.sweep();with an unconditionalvoid this.sweep();plus a marker comment (anchor 1 to 0, blob026c59ebf9a8tof36281d0bba1) and ranvitest run src/lifecycle/lifecycle-service.test.ts -t 29918:Restore: blob after restore
026c59ebf9a8equals HEAD,git diff HEADempty. 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.Clause-②: yes (widening) — the exported
LifecycleEngineLikegains the optional memberwasDatastoreCreatedFromEmpty?(): boolean(an engine without it keeps the old schedule); no accept set changes.Metadata-protocol: none
Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code