Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions docs/qa/platform-checklist/areas/platform-core.json
Original file line number Diff line number Diff line change
Expand Up @@ -2418,7 +2418,7 @@
"title": "ADR-0057 lifecycle sweep: backdated telemetry rows past retention are reaped BOUNDED under a system context on the hourly sweep, onlyWhen scoping spares live rows, an archive-declaring object is NEVER hot-deleted unarchived (retained + reported archive-pending when no archive datasource exists), the transient class stays on the primary, and OS_LIFECYCLE_DISABLED=1 disarms the sweep",
"since": "v16",
"status": "active",
"revision": 2,
"revision": 3,
"priority": "P1",
"surface": "mixed",
"personas": ["operator (local shell + direct sqlite access)", "seeded admin (API reads)"],
Expand All @@ -2433,15 +2433,15 @@
"the ARCHIVE-SUCCESS leg (rows copied to the archive datasource, then hot-deleted) needs a registered 'archive' datasource, which no stock boot has — that leg rides the unit pin (lifecycle-service.test.ts) and stays blocked(fixture) live; what IS live on stock is the SAFETY side: sys_audit_log rows older than 90d are RETAINED and the sweep reports the object skipped archive-pending",
"class-routing to a dedicated 'telemetry' datasource (ADR-0057 §3.6) engages only when a telemetry datasource is registered — absent on stock, so the routing leg is asserted at the unit/source level (engine.ts getDriver priority + SYSTEM_LEDGER_LIFECYCLE_CLASSES), not live",
"the ≤ REAP_BATCH_SIZE × REAP_MAX_BATCHES_PER_SWEEP bound (500×20 per object per sweep) is proven by the unit suite; staging >10k backdated rows live is not required — the live clause asserts the reap happened via the bounded per-id path (the aggregate log line), not the bound's edge",
"RULING OWED on the idle-proof step, steps[4] (QA run #23166 C1, parked on #23121, carried by #23169): an idle sweep on a clean DB still logs '[lifecycle] sweep: 18 policy(ies) applied, ~0 rows reaped, …' at objectstack 36b2889c, and the source at 6d7f4df3c reads the same. The print gate tests report.swept.length, and the reaper pushes a report.swept entry for every policy it applies, deleted 0 included (lifecycle-service.ts; the LifecycleSweepReport.swept docblock reads 'Policies applied, one entry per (object, policy)'). So steps[4]'s 'an idle sweep logs nothing' is either a stale step (one line per sweep is the design) or a product defect (the line should print only when rows moved). Until the maintainer rules, a run records the line verbatim and scores steps[4] neither pass nor fail. No acceptance clause rests on it"
"RULED, no longer a gap — the idle-proof step, steps[4] (QA run #23166 C1, parked on #23121, carried by #23169): the maintainer ruled (decision 6147446653, ruling A recorded in 6160866003) that the step was stale and the product is correct, and revision 3 rewrites steps[4] as a positive heartbeat check. The line the run saw is the design: an idle sweep on a clean DB logs '[lifecycle] sweep: 18 policy(ies) applied, ~0 rows reaped, …' (objectstack 36b2889c), because the print gate in lifecycle-service.ts tests report.swept.length and the reaper pushes a report.swept entry for every policy it applies, deleted 0 included. ADR-0057 §3.3 bounds that trace ('Aggregate one summary row at most'); the one-line-per-sweep wording is the code comment at the print site, not ADR text. A run scored against revision 2 or earlier that saw this line on an idle sweep observed correct behaviour, not a fail"
]
},
"steps": [
"boot once on the file DB so schema exists; stop the server",
"hand-seed via sqlite: (a) N sys_automation_run rows with created_at 40 days ago, status 'completed' (past the 30d window, terminal); (b) 2 rows created_at 40 days ago with status 'paused'/'running' (past the window but NOT terminal); (c) 1 sys_audit_log row created_at 100 days ago (past archive.after 90d); (d) 1 sys_inbox_message row (transient class). Record exact counts and ids",
"boot again (control boot, no lifecycle flag) with OS_LOG_LEVEL=info exported (or `--log-level info`): the aggregate sweep line is logged at INFO, and `os dev` defaults the kernel logger to warn, so a default-level boot never prints it; wait ≥90s; grep the log for '[lifecycle] sweep: … policy(ies) applied, ~… rows reaped …' (the ONE trace a sweep leaves — cleanup must not re-feed the tables it drains)",
"re-read via API/sqlite: the terminal 40d-old automation rows are GONE; the paused/running 40d-old rows SURVIVE (onlyWhen scoping); the 100d-old audit row SURVIVES and the sweep report lists sys_audit_log skipped archive-pending (no 'archive' datasource registered → retained, never dropped unarchived); the inbox row's fate follows its own declared policy only — the transient class got no cross-class treatment",
"idle proof: a second sweep interval later (or a fresh boot on the now-clean DB + 90s), the log carries NO sweep line — an idle sweep logs nothing (the line only prints when something was swept/errored/alerted)",
"idle heartbeat (ruled on #23169: an idle sweep still logs its line): let one more sweep run on the now-clean DB — keep steps[2]'s boot up for one more sweep interval (hourly, DEFAULT_LIFECYCLE_SWEEP_MS), or boot fresh on the same file DB with OS_LOG_LEVEL=info exported as in steps[2] and wait ≥90s. That sweep still logs its ONE aggregate INFO line, '[lifecycle] sweep: N policy(ies) applied, ~0 rows reaped, …': the line prints whenever a policy was applied, and every applied policy counts, deleted 0 included, so N counts (object, policy) pairs, not rows, and does not fall to 0 when nothing is due (with 0 error(s) it equals steps[2]'s N on the same composition; the archive-pending sys_audit_log is reported skipped and never counted). Record the line verbatim, then re-read: the rows steps[3] saw reaped stay gone and its survivors still survive. A missing line on an info-level boot means no sweep reached its print (not armed, disabled in settings, or aborted — e.g. the '[lifecycle] governance snapshot could not be loaded' warn), never 'idle'; a default warn-level boot never prints the line at all, so it proves nothing here",
"kill switch: re-seed one terminal 40d-old automation row; boot with OS_LIFECYCLE_DISABLED=1 AND OS_LOG_LEVEL=info (the same level as the control boot, or the missing line proves nothing); wait ≥90s; the row SURVIVES and no sweep line appears — and this absence is meaningful only because step 2 proved the sweep active on the same composition and log level (absence-inference guarded)",
"run the unit pin for the legs live cannot stage: pnpm --filter @objectstack/objectql exec vitest run src/lifecycle/lifecycle-service.test.ts — the archive-declared skip ('skips hot deletion entirely while an archive is declared (retain → archive → delete)', report.skipped == [{object, reason: 'archive-pending'}]), the bounded-reap cases, and the rotation fallback"
],
Expand Down Expand Up @@ -2527,6 +2527,12 @@
"date": "2026-10-13",
"change": "steps[2] (the control boot) and steps[5] (the kill-switch boot) now boot with OS_LOG_LEVEL=info, and a knownGap records the open ruling on steps[4], the idle proof (stale step + ruling owed, #23166 C1; parked on #23121, carried by #23169). The '[lifecycle] sweep:' line is logged at INFO, and `os dev` defaults the kernel logger to warn (packages/cli/src/commands/dev.ts --log-level, serve.ts), so the grep in steps[2] could never match on a default boot, and the kill-switch boot must match its level for its missing line to mean anything. steps[4] is not rewritten: an idle sweep still logs 'N policy(ies) applied, ~0 rows reaped', because report.swept carries one entry per applied policy and the print gate tests its length. Whether that is the design or a defect is the maintainer's call; the knownGap says how a run scores it meanwhile. No acceptance clause changed",
"ref": "#23169"
},
{
"revision": 3,
"date": "2026-10-15",
"change": "the idle-proof ruling is applied: decision 6147446653, answered 「23169 我同意。」 and recorded as ruling A in 6160866003 (the checklist step was stale; the product is correct). steps[4] is rewritten from 'an idle sweep logs nothing' to a positive heartbeat: one more sweep on the now-clean DB, at OS_LOG_LEVEL=info as in steps[2], still logs its one aggregate line with ~0 rows reaped, and the reaped rows stay gone. Grounds, re-read at objectstack c5dd7fce3: the print gate in packages/objectql/src/lifecycle/lifecycle-service.ts tests report.swept.length (or errors, or alerts), and reap() pushes a report.swept entry for every applied policy, deleted 0 included (a deleted: 0 entry is pinned in lifecycle-service.no-tenant-column.test.ts); an archive-pending object goes to report.skipped instead. Attribution, corrected from the ruling record: 'one aggregate log line per sweep is the entire trace it leaves' is the code comment at that print site, while ADR-0057 §3.3's own text is that the service's deletes and rotations are not audited or activity-logged, and 'Aggregate one summary row at most'. fixtures.knownGaps[3], which recorded the owed ruling, now reads RULED and keeps the reason, so a run record scored against an earlier revision stays interpretable. steps[5] (the kill switch, still bracketed by steps[2]'s armed control), every acceptance clause, negative, variants and traps are unchanged: no clause rested on steps[4] before, and none does now. No product change",
"ref": "#23169"
}
]
},
Expand Down
Loading