From cad70efd0f7283c13b81c461a2bc6a8301b8e2df Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 10:08:22 +0000 Subject: [PATCH] docs(qa): lifecycle-retention-sweep steps[4] re-expects the idle sweep's one aggregate line Ruling A on decision 6147446653: the checklist step was stale and the product is correct. An idle sweep on a clean database still logs its one INFO aggregate line ('[lifecycle] sweep: N policy(ies) applied, ~0 rows reaped, ...'): the print gate tests report.swept.length, and reap() pushes a report.swept entry for every applied policy, deleted 0 included. steps[4] becomes a positive heartbeat check; fixtures.knownGaps[3] (the RULING OWED entry) now reads RULED and keeps its reason; revision 2 -> 3 with one appended history entry. No other field, item or file changes; no product code. Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t Co-authored-by: Claude --- docs/qa/platform-checklist/areas/platform-core.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 09535044b5e..87ea4d2ed4d 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -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)"], @@ -2433,7 +2433,7 @@ "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": [ @@ -2441,7 +2441,7 @@ "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" ], @@ -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" } ] },