Skip to content

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

Draft
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-ie3sda
Draft

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-ie3sda

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

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@4a9c5296)

维护者速读

  • 改了什么:objectql 生命周期清扫记住「上次扫完的时间」,存在新的系统状态对象 sys_lifecycle_state(引擎自有、无租户列、每个数据库一行)。内核启动 60 秒时读这一行:距上次扫完不到一小时就不扫,只花这一条读;超过一小时、没有这一行、读失败,照旧扫。全新建库那一轮仍按 fix(objectql): defer the first lifecycle sweep one interval on a datastore this boot created #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 (fix(objectql): defer the first lifecycle sweep one interval on a datastore this boot created #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, carrying both declaration lines and the Skills-docs: line.

Tests

  • packages/objectql/src/lifecycle/lifecycle-service.test.ts, new describe "LifecycleService sweep watermark (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)", 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.

Verification

All on head f9f4c1b6 (base main 6ef7f583), after a whole-tree turbo run build (119/119, lock VERDICT command-exit 0).

Targeted suites and typecheck — all exit 0
  • 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.
Ablations — both gates turn their pin red; restore proven against HEAD

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.
Derived gates — 94 of 94 run, all exit 0, plus pnpm lint

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.

Full dogfood — green

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.


Generated by Claude Code

…ys_lifecycle_state

A kernel that reboots onto an existing datastore reads one row at the
initial delay and skips a first sweep the interval has not come due for.
The watermark lives in a new engine-owned, tenant-less object,
sys_lifecycle_state, declared and registered by platform-objects, with
its roster line, census row and ownership-inventory row in this same
commit because the roster test holds both directions.

Carries the released branch claude/project-thread-0jjbbb@4a9c5296
(session_01Utp6CaGhTo68T8rcfBh8Zv) onto current main as one commit, and
reattaches the docblock of firstSweepDueAtInitialDelay to its method.

Claude-Session: https://claude.ai/code/session_016WV4cxAY2io7s5aVJa6YL4
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:system tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cli, @objectstack/objectql, @objectstack/platform-objects, @objectstack/spec, touching 28 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/platform-objects/src/system/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

79 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 05a30e6ed0989d3fb604686db4f2a93093c52ad0.

⛔ 18 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/platform-objects/src/system/index.ts) — pages documenting those are invisible to this run
  • 1 cross-cutting symbol(s) contributed no route anchor: isSystem (5 routes)
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 179 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 05a30e6ed0989d3fb604686db4f2a93093c52ad0 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9e8c6dd64a15dae2b523dbe5a2ce360181197996 — the merge of head f9f4c1b60342b2da2ab079f88d55b42f76943507 into base 05a30e6ed0989d3fb604686db4f2a93093c52ad0, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9e8c6dd64a15dae2b523dbe5a2ce360181197996 && git checkout 9e8c6dd64a15dae2b523dbe5a2ce360181197996
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 05a30e6ed0989d3fb604686db4f2a93093c52ad0 f9f4c1b60342b2da2ab079f88d55b42f76943507 && git checkout -B drift-repro 05a30e6ed0989d3fb604686db4f2a93093c52ad0 && git merge --no-ff f9f4c1b60342b2da2ab079f88d55b42f76943507

node scripts/docs-audit/affected-docs.mjs --json 05a30e6ed0989d3fb604686db4f2a93093c52ad0

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 05a30e6ed0989d3fb604686db4f2a93093c52ad0 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

Contract-review-claim: session_016WV4cytLmJxfAmgPVjUbbW · head f9f4c1b · 2026-11-06T00:41Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f9f4c1b60342b2da2ab079f88d55b42f76943507
Local-runs: none
Scope: packages/spec/src/system/constants/platform-object-names.ts + packages/spec/src/system/constants/platform-object-names.test.ts, packages/platform-objects/src/system/sys-lifecycle-state.object.ts, packages/platform-objects/src/plugin.ts, packages/platform-objects/src/system/index.ts, packages/cli/src/utils/organization-ownership-inventory.ts, scripts/platform-object-tenancy-census.json, packages/objectql/src/lifecycle/lifecycle-service.ts, packages/objectql/src/lifecycle/lifecycle-service.test.ts (widened to the whole non-prose diff; the cited engine code on main — engine.ts findOne/insert/update, engine-validate.ts system-context strip bypass, engine-migration-flags.ts wasDatastoreCreatedFromEmpty, migration-flag.ts write precedent — was read, not reviewed)

Governing text: ruling B 6663872259 (confirmed 6670551310); spec consent 6669304514 / 6670346930; claim 6672243354 + amendment 6672382820; dev report 6673429255. Read-only shape held: diff at the head, both cards and every comment, the head's check-runs. PR user.login is objectstack-fleet[bot].

① Derived judgments

  1. Roster line (the schema face). PLATFORM_OBJECTS_BY_PACKAGE['platform-objects'] gains 'sys_lifecycle_state' in sorted position between 'sys_jwks' and 'sys_member'; with it PLATFORM_PROVIDED_OBJECT_NAMES and isPlatformProvidedObjectName answer true for one more real object. A reference to that name now resolves instead of drawing the unknown-platform-name advisory; nothing accepted before is refused. Right: widening, as declared. Nothing else under packages/spec moves (file list and diff confirm: one hunk, one line). The roster test on main holds the roster to the declared *.object.ts set both ways, and the declaration is in this diff, so the pair is consistent.
  2. Same commit as the object. Verified on the commit API, not the body: 04cab785 holds all 11 files (object, registration, index export, roster line, inventory row, census row, service, tests, changeset); f9f4c1b6 touches only the changeset's Skills-docs: word. Right, per the consent's "same commit" term.
  3. The new object is what ruling B says. sys-lifecycle-state.object.ts: isSystem: true, managedBy: 'engine-owned', systemFields: { tenant: false } (ADR-0131 D7), requiredPermissions: ['manage_platform_settings'], apiEnabled: false, no lifecycle block, fields id / last_swept_at / created_at / updated_at; declared in packages/platform-objects/src/system/ beside sys-migration.object.ts and registered in the same PlatformObjectsPlugin manifest ([SysMigration, SysMigrationJournal, SysSecret, SysExchangeRate, SysTrustedKey, SysLifecycleState], no routing triple). Compared field-for-field with sys_migration on main: same posture. No settings key, no env override, no audit row, no Setup nav. Right: engine-owned, tenant-less, beside sys_migration.
  4. @objectstack/platform-objects public surface. SysLifecycleState is exported from system/index.ts, which the package root re-exports with export *. One new named export; nothing removed. Widening, covered by the minor.
  5. @objectstack/objectql public surface. LifecycleEngineLike (exported from the package root on main) gains three OPTIONAL members findOne / insert / update; an engine missing any keeps the old schedule. Widening, as declared. LIFECYCLE_STATE_OBJECT / LIFECYCLE_STATE_ROW_ID are module-level exports of lifecycle-service.ts and are NOT re-exported from src/index.ts, so they are not in the published accept set (the body's "new exported constants" overstates them; harmless).
  6. Sweep scheduling honours the watermark. Read against the live engine on main:
    • runFirstScheduledSweep: fresh store (wasDatastoreCreatedFromEmpty, which on main requires zero existing tables and at least one created, so an UPGRADE boot onto an existing store that merely gains the new table is NOT fresh) ⇒ defer one interval and seed the row at now, no read. Otherwise one findOne by primary key under SYSTEM_CTX; young ⇒ no sweep, the read is the only statement; old, absent, unparsable, negative age (clock moved back), unregistered object, missing engine verbs, or a throwing read (warn once) ⇒ sweep at the initial delay, exactly as before. The interval is armed in start() either way. Right.
    • First boot / cold start on an existing store with no row: sweep, then insert. Right.
    • Multi-instance: two kernels on one store both read; a kernel that boots after another's completed sweep skips inside the interval; two that both find it old both sweep (idempotent reaps) and both write. A concurrent first boot of two kernels onto one empty store makes the second seed insert fail on the primary key: logged once at warn, presence reset to unknown, the next write re-reads. Degradation only. Acceptable.
    • Clock skew across instances: a future stamp reads as negative age ⇒ sweep; an ahead-clock instance reading a correct stamp sees it older ⇒ sweep. Both directions fail toward sweeping. Right.
    • Failed watermark write: never thrown, warn once then debug, presence reset; the next boot sweeps again (today's behaviour). Right, and the ruling's "the row can only ever save work, never withhold it" holds.
    • The write advances only a COMPLETED sweep: audited && report.errors.length === 0 && !this.stopped, stamped with report.at (the sweep's start, which the cutoffs were taken from). Early returns in sweep() (re-entrant, disabled, torn down, no engine, governance snapshot failed, governance disabled, leg-boundary stop) all precede the write. auditReferences now returns true when it ran or was never meant to, false on throw or teardown. Right.
    • Engine contract: findOne(object, { where: { id }, context }), update(object, { id, last_swept_at }, { context }) (the engine extracts the id from data), insert(object, row, { context }) — the same shapes migration-flag.ts uses on main for sys_migration; isSystem: true bypasses the static readonly strip (engine-validate.ts), so the readonly: true, required: true last_swept_at is written as sys_migration.last_run_at is. Right.
    • Direct sweep() always sweeps (one presence read on first write, documented); OS_LIFECYCLE_DISABLED=1 arms nothing. Right.
  7. Nothing narrows. No accepted input is refused; no default moves; the fresh-store deferral of fix(objectql): defer the first lifecycle sweep one interval on a datastore this boot created #30214 is kept; a kernel without the object or the verbs behaves as on main.
  8. Census / inventory treatment of a tenant-less object. Census row reach: out, tenantField: null, reason systemFields.tenant: false; totals 93→94, outOfReach 46→47, the reason bucket 20→21, inReach unchanged. Inventory row census: PMAIN, fate: 'column-drop', citing CENSUS_MAIN and D7 — the same shape as sys_trusted_key's row directly above it. deployment-level-no-tenant-column.test.ts adds the object to the D7 table (no injected or declared organization_id, opt-out via systemFields.tenant, platform-only capability). check:platform-object-tenancy-census ran in the green "Lint · repo & tooling gates". Right.

② Semver level

  • Changeset .changeset/29918-lifecycle-sweep-watermark.md: minor for @objectstack/spec, @objectstack/objectql, @objectstack/platform-objects, @objectstack/cli. No major (pre-GA rule of check-changeset-no-major.mjs). Level axis: Metadata-protocol: added requires at least one package whose published source moves at minor or above — spec and platform-objects both do; added sits beside Clause-②: yes (widening), no contradiction. The Check Changeset run on this head concluded success.
  • Declaration lines are identical in the PR body and the changeset: Clause-②: yes (widening) — …, Metadata-protocol: added, Skills-docs: none — REASON. Fixes #29918 and Fixes #30641 are both in the body. Every consent term of 6669304514 / 6670346930 is met.
  • Skills-docs spelling. The declared grammar (pm-dispatch SKILL.md, the Metadata-protocol row) is updated here | follow-up owed — FILES | follow-up #N | none — REASON. no move (the order's and the release's word) is not in it. No script reads the line (the only Skills-docs hit under scripts/ and .github/ is a comment in check-skills-token-ratchet.mjs), so the gates are indifferent; the dev's none — REASON is the grammar the seat's ACCEPT checks against, and the reason given (no roster enumerated in skills/** or content/docs/**; no data API) matches the diff. Right.

③ Boundary flags

open_questions: []. The dev's four deviations and one acceptance note, each answered:

  1. Squash of the released branch into one commit — verified (①.2); the released branch claude/project-thread-0jjbbb@4a9c5296 compares to the same 11 files against 6ef7f583, so the squash dropped nothing. The release note's "regenerated translation bundles" (6671108523) described files that were never on that branch; none are needed (see ④.4). Accepted.
  2. Skills-docs: none — REASON instead of no move — the right grammar (②). Accepted.
  3. firstSweepDueAtInitialDelay docblock reattached, no code moved — confirmed in the diff (the new methods sit above it). Accepted.
  4. Assignee / reviewer not set (no MCP write) — the seat's stroke at hand-over. Noted.
  5. Acceptance note: a fresh store now issues ONE write (the seed insert) at the initial delay, where the card's done-when says "at most the watermark read". The seed is card Expected item 2 ("seed the watermark when the tables are created") placed at the initial delay, one statement against the 130 measured, and it is what makes a reboot inside the first interval skip too. Ruling B's own done-when (a re-booted kernel with a young watermark issues only the read; an older one still sweeps) is met exactly. Accepted as a declared deviation; the seat names it at ACCEPT and cloud re-measures under cloud#3413.

Author note: PR user.login is objectstack-fleet[bot]; no outside author.

④ Design judgment

  1. Roster line against its first consumer. platform-object-names.test.ts on main scans every *.object.ts and asserts equality both ways per package; the declaration and the line land together, so the test is green only with both — the consent's reason for riding one PR holds in the tree. The inventory test (organization-ownership-inventory.test.ts, "a platform object without a row reds here, by name") is satisfied by the new inventory row. No generated spec artifact derives from the roster (nothing under packages/spec/scripts, packages/spec/generated or scripts/ reads PLATFORM_OBJECTS_BY_PACKAGE), so check:generated has nothing to drift. Holds.
  2. Object against the ruling and its first consumer. The sole reader and writer is LifecycleService, which asks registry.getObject before any statement, so the object is never read where it is not registered. Single-row shape per the ruling of record (the keyed id column leaves room for a second clock without a schema change). No lifecycle block, so the row that records the sweep is never reaped by it. Holds.
  3. Pins are real. Read against the gates they pin: the "younger watermark issues only the read" pin asserts statements() === 1 and deletes === 0, which the read-gate ablation (watermarkIsYoung forced false) turns red by sweeping; the "failed policy or throwing audit leaves the row unchanged" pin asserts zero inserts/updates on both paths, which the write-gate ablation (if (true)) turns red. The dev reports both ablations 1 failed / 8 passed with restore proven against HEAD (ba3f2b02), and the first read-gate attempt refused as a no-op is recorded honestly. The remaining pins (no row ⇒ sweep + insert; fresh ⇒ seed without read, sweep one interval later; failing read ⇒ sweep + one warn; unregistered ⇒ never asked; direct sweep ⇒ sweeps and advances; disabled ⇒ zero statements) each name a distinct branch of runFirstScheduledSweep / writeWatermark. Holds.
  4. Dogfood cell and derived gates. "Dogfood Regression Gate", "Governed Surface Queue Guard", "Check Changeset", "PR-body guards", "Lint · repo & tooling gates" (census check), "Lint · PM dispatch-gates", "Lint · ESLint", "Spec property liveness", "Type Check · source gates" all success on this head. Still in_progress at review time: Build Core, Test Core 1–8, Type Check · workspace / debt ledger / consumer gates (the last runs check:i18n and the coverage ratchet), Lint · CI release & engine gates, Lint · scoped slot lookup, the UI jobs. On check:i18n: i18n-extract.config.ts imports an explicit object list that already omits sys_trusted_key and sys_migration_journal, so the new object produces no bundle drift and is outside the ratchet's population; it has no UI (apiEnabled: false, no nav), so untranslated labels reach no user. The dev reports full dogfood 421 files / 3513 tests green under the verify lock and 94/94 derived gates exit 0. Landing precondition ② (every check green) is the dispatching seat's to confirm once the in-progress runs conclude; nothing read here predicts a red.
  5. Tried to break it. Watermark older than the interval but a sweep aborted by stop() mid-leg: no write, next boot sweeps — correct. Row present with last_swept_at as a driver Date object: String(date) parses; as an epoch number it parses to NaN ⇒ sweep (conservative). sweepIntervalMs shortened between boots: compared against the new interval — correct. Governance enabled: false via settings: sweep() returns before the write, so the watermark does not advance while sweeping is switched off — correct (a later re-enable sweeps at once). A kernel that boots inside the interval repeatedly (cloud's 30-minute residency against the hourly interval): each boot reads one row and skips until the last completed sweep is an interval old, then sweeps at the initial delay — the "brief, frequent tenant is never swept" failure the card warned about does not occur. No must-fix found.

Should-fix (non-blocking, for the seat's ACCEPT note or a follow-up): (a) system/index.ts's header docblock still lists what the module hosts without the new object — prose only; (b) the body's "new exported constants" are module exports, not package exports — wording only.

Implemented-by: claude/project-thread-ie3sda
Reviewed-by: session_016WV4cytLmJxfAmgPVjUbbW

VERDICT: PASS

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant