Repository navigation
Commit b5eb61d
feat(objectql): the lifecycle sweep keeps a last-swept watermark in sys_lifecycle_state, so a reboot skips a sweep the interval has not come due for (#30751)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D9gB7dhaBzxAcWk95SYN9CE)_
domain:server#1 · os-elon-musk · session_016WV4cxAY2io7s5aVJa6YL4
Fixes #29918
Fixes #30641
Clause-②: yes (widening) —
`PLATFORM_OBJECTS_BY_PACKAGE['platform-objects']` (and with it
`PLATFORM_PROVIDED_OBJECT_NAMES` / `isPlatformProvidedObjectName`) lists
one more real platform object, `sys_lifecycle_state`, and the exported
`LifecycleEngineLike` gains three optional members (`findOne`, `insert`,
`update`); nothing that was accepted is refused.
Metadata-protocol: added
Skills-docs: none — `skills/**` and `content/docs/**` enumerate no
platform-object roster and teach nothing about the lifecycle sweep's
schedule; the object is internal clock state with no data API, so there
is nothing for an author to write against it.
Claim: 6672243354 (amended 6672382820) · Branch:
`claude/project-thread-ie3sda` · Ruling-ref: 6663872259 (letter B),
confirmed 6670551310 · Claim correction: 6670586478 · Spec consent:
6669304514, 6670346930 · Resumes: release 6671108523
(`claude/project-thread-0jjbbb@4a9c529`)
## 维护者速读
- 改了什么:objectql 生命周期清扫记住「上次扫完的时间」,存在新的系统状态对象
`sys_lifecycle_state`(引擎自有、无租户列、每个数据库一行)。内核启动 60
秒时读这一行:距上次扫完不到一小时就不扫,只花这一条读;超过一小时、没有这一行、读失败,照旧扫。全新建库那一轮仍按 #30214
延后一个间隔,并顺手写下这一行。spec 名册加一行 `sys_lifecycle_state`,与对象同一提交。
- 为什么改:每次内核重启都会在第一分钟跑满一遍清扫(cloud 托管内核上 130 条语句),cloud#3413 要求第一分钟不超过约 10
条。裁决 B(6663872259,确认 6670551310):水位线是引擎状态,不是设置。
- 风险与回滚:小。最坏情况是水位线写不进去,下次启动多扫一遍(等于今天的行为),写失败只记一次 warn。新增一个系统表(无数据
API、无设置键、无环境变量、不进 Setup)。回滚即撤 PR,多出的空表无害。
-
先看:`packages/objectql/src/lifecycle/lifecycle-service.ts`(`runFirstScheduledSweep`、`watermarkIsYoung`、`writeWatermark`,以及
`sweep()`
末尾只在完整成功时推进水位线),`packages/platform-objects/src/system/sys-lifecycle-state.object.ts`,`packages/spec/src/system/constants/platform-object-names.ts`
那一行。
- 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run
src/lifecycle/lifecycle-service.test.ts -t "sweep watermark"`,预期 9
条全绿,其中「younger than the interval」断言初始延迟时只发出一次读。
- 需要你做的:无,席位落地。
## What changes
**`@objectstack/objectql` — `LifecycleService`**
(`lifecycle/lifecycle-service.ts`)
- New exported constants `LIFECYCLE_STATE_OBJECT =
'sys_lifecycle_state'` and `LIFECYCLE_STATE_ROW_ID = 'lifecycle'`.
- `LifecycleEngineLike` gains optional `findOne` / `insert` / `update`.
An engine missing any of the three, or whose registry does not register
`sys_lifecycle_state`, keeps no watermark and sweeps exactly as before;
the object is never read when unregistered.
- The scheduler's first tick (`runFirstScheduledSweep`):
1. a store this boot created from empty (#30214's
`wasDatastoreCreatedFromEmpty`) still defers one interval, and now seeds
the row at now without reading it;
2. otherwise one primary-key `findOne` of the row: a `last_swept_at`
younger than the interval ⇒ no sweep, the read is the only statement;
the interval is armed either way;
3. an older, missing, unparsable or future watermark, or a read that
throws (logged once at `warn`) ⇒ sweep at the initial delay, as before.
- `sweep()` advances the row (insert the first time, update after; the
row's presence is remembered so no second read) only for a COMPLETED
sweep: no policy error, the reference-audit leg ran (or was never meant
to), and no teardown cut it short. Stamped with the sweep's start, which
the cutoffs were taken from. A failed write is a functional degradation
(the next boot sweeps again), logged at `warn` once and `debug` after,
never thrown.
- A direct `sweep()` call always sweeps. `OS_LIFECYCLE_DISABLED=1` still
arms nothing.
**`@objectstack/platform-objects`** — new `sys_lifecycle_state`
(`system/sys-lifecycle-state.object.ts`): `isSystem`, `managedBy:
'engine-owned'`, `systemFields: { tenant: false }` (ADR-0131 D7
deployment-level state), `requiredPermissions:
['manage_platform_settings']`, `apiEnabled: false`, no `lifecycle` block
(never swept), fields `id`, `last_swept_at`, `created_at`, `updated_at`.
Registered by `PlatformObjectsPlugin` beside `sys_migration` and
exported from `system/index.ts`.
**`@objectstack/spec`** — the one roster line `'sys_lifecycle_state'` in
`PLATFORM_OBJECTS_BY_PACKAGE['platform-objects']` (sorted between
`sys_jwks` and `sys_member`), in the same commit as the object, per
`domain:spec#3`'s consent (6669304514, 6670346930): the roster test
holds the roster to the declared `*.object.ts` set both ways, so #30641
cannot land alone. Nothing else under `packages/spec` moves.
**`@objectstack/cli`** — `ORGANIZATION_OWNERSHIP_INVENTORY` gains the
`sys_lifecycle_state` row (deployment-level, `column-drop`).
**`scripts/platform-object-tenancy-census.json`** — the new row (`reach:
out`, `systemFields.tenant: false`) and the totals (registered 94, out
of reach 47).
**Changeset** — `minor` for spec / objectql / platform-objects / cli /
plugin-audit, carrying both declaration lines and the `Skills-docs:`
line.
## Tests
- `packages/objectql/src/lifecycle/lifecycle-service.test.ts`, new
describe "LifecycleService sweep watermark (#29918)", 9 pins: a younger
watermark issues only the read; an interval-old watermark sweeps and
advances; no row sweeps and writes it; a created-from-empty store
defers, seeds without reading, and sweeps one interval later; a failed
policy or a throwing audit leaves the row unchanged; a failing read
sweeps and warns; an engine without the object is never asked; a direct
`sweep()` sweeps and advances; `OS_LIFECYCLE_DISABLED` reads and writes
nothing.
- `packages/platform-objects`: `plugin.test.ts` (registration order, six
infra objects, no routing triple),
`deployment-level-no-tenant-column.test.ts` (no tenant column,
platform-only reads).
- Roster (`platform-object-names.test.ts`) and CLI inventory
(`organization-ownership-inventory.test.ts`) unchanged and green with
the new rows.
## Acceptance notes
- **The carried head is one commit.** The released branch held the
object (`28a633a3`) and the roster line (`2de76a73`) in two commits; the
dispatch order requires them in one because the roster test holds both
directions, so this branch squashes the released work onto current
`main` (`6ef7f583`). The released branch is untouched.
- **Docblock reattached.** On the released head,
`runFirstScheduledSweep`'s docblock had been inserted between
`firstSweepDueAtInitialDelay`'s docblock and its method, leaving that
method undocumented and two docblocks stacked; this branch moves the new
methods above it. No code moved.
- **`Skills-docs:` spelling.** The release and the dispatch order
spelled the value `no move`; the declared grammar (pm-dispatch SKILL.md)
is `updated here | follow-up owed — FILES | follow-up #N | none —
REASON`, so the changeset and this body say `none — REASON` with the
same reason.
- **A fresh store now issues one write at the initial delay** (the seed
insert), not zero. The card's done-when allows "at most the watermark
read"; the seed is the price of making a reboot inside the first
interval skip too (card Expected item 2). One statement against the 130
measured.
- **plugin-audit hunk (amendment 6673909090).** The first head's
watermark writes were audited like any business write, so every
completed sweep added a `sys_audit_log` and a `sys_activity` row about
its own clock, which the next sweep archived (CI: the
`audit-key-material` / `audit-mail-bodies` archive integration tests
counted 2 archived rows where 1 is right). Ruling 6663872259 forbids an
audit row per sweep and ADR-0057 section 3.3 forbids cleanup re-feeding
the tables it drains. No write option bypasses the code-registered audit
hooks and there is no object-level opt-out, so `sys_lifecycle_state`
joins `SKIP_OBJECTS` beside `sys_job_queue`; `audit-writers.test.ts`
pins zero rows for its insert and update, and the changeset gains
`'@objectstack/plugin-audit': minor`.
## Verification
Rework head `29c59f4d`: the rework report 6674667474 on #29918 records
96/96 derived gates and `pnpm lint` exit 0 (0 NOT-MEASURED), full
dogfood 421 files / 3513 tests green, both `cli` archive integration
tests 16/16, and `plugin-audit` 1146/1146. The block below is the first
head's.
All on head `f9f4c1b6` (base `main` `6ef7f583`), after a whole-tree
`turbo run build` (119/119, lock VERDICT command-exit 0).
<details><summary>Targeted suites and typecheck — all exit 0</summary>
- `pnpm --filter @objectstack/objectql test`: Test Files 556 passed
(556), Tests 11078 passed (11078)
- `pnpm --filter @objectstack/objectql typecheck`: exit 0
- `pnpm --filter @objectstack/platform-objects test`: Test Files 116
passed (116), Tests 1581 passed (1581); `typecheck` exit 0
- `pnpm --filter @objectstack/spec exec vitest run
src/system/constants/platform-object-names.test.ts`: 14 passed (14)
- `pnpm --filter @objectstack/spec check:generated`: all 14 generated
artifacts up to date
- `pnpm --filter @objectstack/cli exec vitest run --project unit
src/utils/organization-ownership-inventory.test.ts`: 26 passed (26);
`cli typecheck` exit 0
- Lock VERDICT command-exit 0.
</details>
<details><summary>Ablations — both gates turn their pin red; restore
proven against HEAD</summary>
Through `scripts/ablation-replace.mjs`, `vitest run
src/lifecycle/lifecycle-service.test.ts -t "sweep watermark"` (the suite
imports the service from source, so no build sits between mutation and
run):
- Read gate: the line in `watermarkIsYoung` that returns false for an
unparsable, future or interval-old watermark (the `Number.isFinite` /
`intervalMs` check) replaced by `return Boolean(0);` (anchor 1 to 0,
blob `ba3f2b02` to `2f1db1a3`): 1 failed, 8 passed, the failure being "a
reboot whose watermark is younger than the interval issues only the
watermark read at the initial delay". A first attempt with the
replacement `return false;` was refused by the tool as a no-op (the
replacement is a substring of the anchor, count 7 to 7) and measured
nothing.
- Write gate: `if (audited && report.errors.length === 0 &&
!this.stopped) {` replaced by `if (true) {` (blob `ba3f2b02` to
`7bd5ebd0`): 1 failed, 8 passed, the failure being "a sweep that fails a
policy, or whose audit throws, leaves the watermark unchanged".
- Each restore: blob equals HEAD `ba3f2b02`, `git diff HEAD` empty.
</details>
<details><summary>Derived gates — 94 of 94 run, all exit 0, plus pnpm
lint</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 94 families on `f9f4c1b6`; each ran
sequentially, exit code captured before any pipe, plus `pnpm lint` (exit
0). `--ran`: "94 derived famil(ies) accounted for — 94 run, 0
NOT-MEASURED"; `pnpm lint` listed as outside the derivation.
</details>
<details><summary>Full dogfood — green</summary>
`pnpm --filter @objectstack/dogfood test` under the verify lock on
`f9f4c1b6`: Test Files 421 passed, 3 skipped (424); Tests 3513 passed,
36 skipped (3549); VERDICT command-exit 0.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4cxAY2io7s5aVJa6YL4)_
<!-- signed: domain:server#1 · os-elon-musk ·
session_016WV4cytLmJxfAmgPVjUbbW -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 612468c commit b5eb61d
13 files changed
Lines changed: 655 additions & 15 deletions
File tree
- .changeset
- packages
- cli/src/utils
- objectql/src/lifecycle
- platform-objects/src
- system
- plugins/plugin-audit/src
- spec/src/system/constants
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
759 | 759 | | |
760 | 760 | | |
761 | 761 | | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
762 | 770 | | |
763 | 771 | | |
764 | 772 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2638 | 2638 | | |
2639 | 2639 | | |
2640 | 2640 | | |
| 2641 | + | |
| 2642 | + | |
| 2643 | + | |
| 2644 | + | |
| 2645 | + | |
| 2646 | + | |
| 2647 | + | |
| 2648 | + | |
| 2649 | + | |
| 2650 | + | |
| 2651 | + | |
| 2652 | + | |
| 2653 | + | |
| 2654 | + | |
| 2655 | + | |
| 2656 | + | |
| 2657 | + | |
| 2658 | + | |
| 2659 | + | |
| 2660 | + | |
| 2661 | + | |
| 2662 | + | |
| 2663 | + | |
| 2664 | + | |
| 2665 | + | |
| 2666 | + | |
| 2667 | + | |
| 2668 | + | |
| 2669 | + | |
| 2670 | + | |
| 2671 | + | |
| 2672 | + | |
| 2673 | + | |
| 2674 | + | |
| 2675 | + | |
| 2676 | + | |
| 2677 | + | |
| 2678 | + | |
| 2679 | + | |
| 2680 | + | |
| 2681 | + | |
| 2682 | + | |
| 2683 | + | |
| 2684 | + | |
| 2685 | + | |
| 2686 | + | |
| 2687 | + | |
| 2688 | + | |
| 2689 | + | |
| 2690 | + | |
| 2691 | + | |
| 2692 | + | |
| 2693 | + | |
| 2694 | + | |
| 2695 | + | |
| 2696 | + | |
| 2697 | + | |
| 2698 | + | |
| 2699 | + | |
| 2700 | + | |
| 2701 | + | |
| 2702 | + | |
| 2703 | + | |
| 2704 | + | |
| 2705 | + | |
| 2706 | + | |
| 2707 | + | |
| 2708 | + | |
| 2709 | + | |
| 2710 | + | |
| 2711 | + | |
| 2712 | + | |
| 2713 | + | |
| 2714 | + | |
| 2715 | + | |
| 2716 | + | |
| 2717 | + | |
| 2718 | + | |
| 2719 | + | |
| 2720 | + | |
| 2721 | + | |
| 2722 | + | |
| 2723 | + | |
| 2724 | + | |
| 2725 | + | |
| 2726 | + | |
| 2727 | + | |
| 2728 | + | |
| 2729 | + | |
| 2730 | + | |
| 2731 | + | |
| 2732 | + | |
| 2733 | + | |
| 2734 | + | |
| 2735 | + | |
| 2736 | + | |
| 2737 | + | |
| 2738 | + | |
| 2739 | + | |
| 2740 | + | |
| 2741 | + | |
| 2742 | + | |
| 2743 | + | |
| 2744 | + | |
| 2745 | + | |
| 2746 | + | |
| 2747 | + | |
| 2748 | + | |
| 2749 | + | |
| 2750 | + | |
| 2751 | + | |
| 2752 | + | |
| 2753 | + | |
| 2754 | + | |
| 2755 | + | |
| 2756 | + | |
| 2757 | + | |
| 2758 | + | |
| 2759 | + | |
| 2760 | + | |
| 2761 | + | |
| 2762 | + | |
| 2763 | + | |
| 2764 | + | |
| 2765 | + | |
| 2766 | + | |
| 2767 | + | |
| 2768 | + | |
| 2769 | + | |
| 2770 | + | |
| 2771 | + | |
| 2772 | + | |
| 2773 | + | |
| 2774 | + | |
| 2775 | + | |
| 2776 | + | |
| 2777 | + | |
| 2778 | + | |
| 2779 | + | |
| 2780 | + | |
| 2781 | + | |
| 2782 | + | |
| 2783 | + | |
| 2784 | + | |
| 2785 | + | |
| 2786 | + | |
| 2787 | + | |
| 2788 | + | |
| 2789 | + | |
| 2790 | + | |
| 2791 | + | |
| 2792 | + | |
| 2793 | + | |
| 2794 | + | |
| 2795 | + | |
| 2796 | + | |
| 2797 | + | |
| 2798 | + | |
| 2799 | + | |
| 2800 | + | |
| 2801 | + | |
| 2802 | + | |
| 2803 | + | |
| 2804 | + | |
| 2805 | + | |
| 2806 | + | |
| 2807 | + | |
| 2808 | + | |
| 2809 | + | |
| 2810 | + | |
| 2811 | + | |
| 2812 | + | |
| 2813 | + | |
| 2814 | + | |
| 2815 | + | |
| 2816 | + | |
| 2817 | + | |
| 2818 | + | |
| 2819 | + | |
| 2820 | + | |
| 2821 | + | |
| 2822 | + | |
| 2823 | + | |
| 2824 | + | |
| 2825 | + | |
| 2826 | + | |
| 2827 | + | |
| 2828 | + | |
| 2829 | + | |
| 2830 | + | |
| 2831 | + | |
| 2832 | + | |
| 2833 | + | |
| 2834 | + | |
| 2835 | + | |
| 2836 | + | |
| 2837 | + | |
| 2838 | + | |
| 2839 | + | |
| 2840 | + | |
| 2841 | + | |
| 2842 | + | |
| 2843 | + | |
| 2844 | + | |
| 2845 | + | |
| 2846 | + | |
| 2847 | + | |
| 2848 | + | |
| 2849 | + | |
| 2850 | + | |
| 2851 | + | |
| 2852 | + | |
| 2853 | + | |
| 2854 | + | |
| 2855 | + | |
| 2856 | + | |
| 2857 | + | |
| 2858 | + | |
| 2859 | + | |
| 2860 | + | |
| 2861 | + | |
| 2862 | + | |
| 2863 | + | |
| 2864 | + | |
| 2865 | + | |
| 2866 | + | |
| 2867 | + | |
| 2868 | + | |
| 2869 | + | |
| 2870 | + | |
| 2871 | + | |
| 2872 | + | |
| 2873 | + | |
| 2874 | + | |
| 2875 | + | |
| 2876 | + | |
| 2877 | + | |
| 2878 | + | |
| 2879 | + | |
| 2880 | + | |
| 2881 | + | |
| 2882 | + | |
| 2883 | + | |
| 2884 | + | |
| 2885 | + | |
| 2886 | + | |
| 2887 | + | |
| 2888 | + | |
| 2889 | + | |
| 2890 | + | |
| 2891 | + | |
| 2892 | + | |
| 2893 | + | |
| 2894 | + | |
| 2895 | + | |
| 2896 | + | |
| 2897 | + | |
| 2898 | + | |
| 2899 | + | |
| 2900 | + | |
| 2901 | + | |
| 2902 | + | |
| 2903 | + | |
| 2904 | + | |
| 2905 | + | |
| 2906 | + | |
| 2907 | + | |
| 2908 | + | |
| 2909 | + | |
| 2910 | + | |
| 2911 | + | |
2641 | 2912 | | |
2642 | 2913 | | |
2643 | 2914 | | |
| |||
0 commit comments