Commit 7953832
* docs(adr): ADR-0057 header/rollout sync — P0 shipped (#2079), P1–P4 tracked in #2786
The ADR header still said 'Proposed' with no pointer to the shipped P0
implementation or the new P1–P4 tracking issue. Per repo convention
(cf. ADR-0030/0055/0069 headers), reflect implementation reality:
- Status: Accepted — P0 shipped with this ADR (#2079); P1–P4 proposed,
tracked in #2786
- §4 Rollout: link #2079 on the P0 row; add the #2786 tracking pointer
Doc-only change; no code or policy content altered.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
* feat(lifecycle): ADR-0057 P1 — lifecycle contract, LifecycleService Reaper, liveness gate, dogfood growth gate (#2786)
The platform can declare an object's structure, validations and permissions —
but not how long its data lives. P1 makes data lifecycle a first-class,
declarable, runtime-enforced metadata concern:
spec:
- `lifecycle` object property (LifecycleSchema): class (record | audit |
telemetry | transient | event) + retention.maxAge / ttl / storage(rotation)
/ archive / reclaim, with duration literals ('6h', '14d', '7y')
- §3.5 enforce-or-remove invariants in a superRefine: a non-record class with
no bounding policy is rejected; record-class policies are rejected; the
archive window must start where the hot window ends
- IDataDriver.reclaimSpace?() contract for post-sweep space reclamation
objectql:
- LifecycleService (Reaper), registered by ObjectQLPlugin as a platform
service (default-on, OS_LIFECYCLE_DISABLED=1 / lifecycle.enabled=false to
opt out): hourly sweep deletes rows past retention.maxAge (created_at) and
past ttl.field + expireAfter, bounds rotation-declared telemetry by
shards×unit until the P2 Rotator lands, and never hot-deletes an
archive-declared audit ledger before the Archiver copies it out
- Sweeps run bulk multi-deletes under a system context — at most ONE
afterDelete hook per object per sweep, and telemetry sys_* objects are in
the audit writer's SKIP_OBJECTS, so cleanup never re-feeds the tables it
drains (the ADR-0052 self-audit trap)
driver-sql:
- reclaimSpace(): SQLite `PRAGMA incremental_vacuum` (the P0 ADR text
claimed this hook shipped; it had not — this closes it), no-op on
Postgres/MySQL
platform objects (11 annotated): sys_activity 14d+rotation,
sys_audit_log 90d→archive 7y, sys_metadata_audit 365d→archive,
sys_job_run/sys_automation_run/sys_http_delivery 30d, notification pipeline
(sys_notification / delivery / receipt / inbox) one 90d window,
sys_device_code expires_at+1d.
gates:
- spec-liveness: object/lifecycle classified + bound to a dogfood proof
(HIGH_RISK_CLASSES 'data-lifecycle')
- dogfood storage-growth gate (@proof: adr0057-lifecycle-bounded-growth):
boots showcase, proves contract coverage, reaps a backdated runaway
writer, asserts record-class data untouched and archive-declared ledgers
retained
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
* feat(lifecycle): ADR-0057 P2 — Rotator: physical time-sharding with O(1) shard DROP (#2786)
High-frequency telemetry declared with lifecycle.storage.strategy='rotation'
is now physically time-sharded on SQLite (the ServiceNow Table Rotation
model, ADR-0057 §3.3):
driver-sql:
- rotateShards(objectDef, nowMs): idempotent Rotator entry — ensures the
current shard (<table>__r<key>: day/week/month UTC keys), adopts a legacy
pre-rotation table as its first shard (no data loss), column-syncs every
retained shard so the UNION stays uniform, DROPs shards past the
shards×unit time window (O(1) reclaim), and rebuilds the read view
- The base name becomes a READ-ONLY UNION ALL view (SQLite views reject
writes and RETURNING), so every write path redirects shard-wise instead:
create/bulkCreate/upsert target the current shard; by-id update/delete
probe shards newest-first; updateMany/deleteMany/bulkDelete fan out and
sum — reads (find/count/aggregate) are untouched
- Per-shard bookkeeping aliases (datetime/date/json/boolean coercion, tenant
scope, timestamp stamping) so a shard builder behaves exactly like the view
- initObjects routes rotation-declared objects to the Rotator (the plain
create/alter DDL path would collide with the view)
- supportsRotation gates on the dialect; declared indexes get per-shard names
objectql LifecycleService:
- Rotator invocation each sweep when the driver supports it; the fallback
age-based reap covers every other dialect so the declared bound holds
everywhere, only the reclamation mechanics differ
- An explicit retention.maxAge still trims inside live shards after rotation
(shard drops are unit-granular; this also immediately bounds a legacy
table the Rotator just adopted whole)
- Dropped shards trigger the datasource incremental-vacuum pass
Transient TTL expiry (the other P2 line item) shipped with the P1 Reaper —
TTL and age reaping are one mechanism (§3.3).
Proof: sql-driver-rotation.test.ts drives the full shard lifecycle (create →
rotate → window drop → adoption → cross-shard writes); the dogfood
storage-growth gate now shards a probe stream through the real
engine→driver path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
* feat(lifecycle): ADR-0057 P3 — datasource separation + Archiver cold store (#2786)
Separation (§3.6): objects whose lifecycle.class is telemetry / event /
audit now route to a dedicated 'telemetry' datasource whenever one is
registered — engine getDriver() step 3, between datasourceMapping rules and
the package-manifest defaultDatasource. Opt-in purely by the datasource's
existence: no 'telemetry' driver ⇒ resolution is byte-for-byte what it was.
Even on SQLite that is a separate file, so platform-generated growth can
never again pollute the business DB. 'transient' deliberately stays on the
primary: those objects are user-session data and some (better-auth's
sys_device_code) are accessed outside the engine — splitting their storage
would split their brain.
Archiver (§3.3): audit-class objects with 'archive' declared now get the
full retain → archive → delete flow instead of the P1 retain-only skip:
- copies rows past archive.after to the archive datasource in bounded
batches (500 × 20/sweep — a backlog drains across sweeps), as per-row
idempotent upserts so an interrupted sweep re-converges
- hot-deletes exactly the copied ids (bulkDelete), then the reclaim pass
vacuums the hot store
- archive.keep prunes the cold store itself
- the cold schema is mirrored via idempotent syncSchema
- no archive datasource registered ⇒ rows retained, object reported
'archive-pending' — a compliance ledger is never dropped unarchived
Proof: engine-lifecycle-datasource.test.ts (routing incl. explicit-datasource
precedence), Archiver unit tests (copy/delete id-exactness, keep-pruning,
missing-datasource retention), and a dogfood case that provisions a real
second SQL store mid-run and watches the ledger drain hot → cold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
* feat(lifecycle): ADR-0057 P4 — governance: quotas, growth alerts, tenant retention overrides (#2786)
Adds the operations layer over the P1-P3 enforcement machinery, resolved
from a new 'lifecycle' settings namespace (registered by ObjectQLPlugin at
kernel:ready when a SettingsService is present; absent settings = declared
policies apply unmodified):
- enabled — runtime master switch (on top of OS_LIFECYCLE_DISABLED)
- retention_overrides — per-object window overrides { object: { maxAge,
expireAfter } }. TENANT-scoped: the sweep enumerates organizations, and a
tenant whose override is genuinely tenant-scoped (not inherited) gets its
own cutoff on its own rows, while the global pass covers everyone else
INCLUDING NULL-org rows (a bare $nin would silently skip them). This is
ADR-0057 §3.2 verbatim: regulated tenants set years, dev sets days — the
same knob at different settings. An unparseable override keeps the
declared window (never fails open into unbounded growth).
- quotas / quota_defaults — per-object and per-class row ceilings
- growth_alert_rows — per-sweep growth spike threshold
Quota and growth breaches are OBSERVE-AND-ALERT only (report.alerts +
onAlert sink, default logger warning) — governance never deletes beyond the
declared policy; an operator decides.
Every settings read is best-effort and snapshotted once per sweep; the
tenant scan is capped (200) and skipped entirely on single-tenant kernels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
* docs(adr)+chore: ADR-0057 marked P0–P4 implemented; changeset for the lifecycle release (#2786)
- ADR-0057 header + §4 rollout table now record P1–P4 as implemented in
#2791; §3.3 gains the archive-safety hard rule and §3.6 the as-implemented
'telemetry' datasource routing note (transient exclusion rationale).
- Changeset: minor bumps across spec / objectql / driver-sql /
driver-sqlite-wasm / platform-objects / metadata-core / service-messaging /
service-automation / plugin-audit, documenting the bounded-by-default
behavior change and the override knobs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
* chore(spec): regenerate api-surface snapshot for the ADR-0057 lifecycle exports
The 5 additions (LifecycleSchema / LifecycleClassSchema / Lifecycle /
LifecycleClass / LIFECYCLE_DURATION_REGEX) are the intentional P1 spec
surface; the api-surface gate flagged them as uncommitted snapshot drift.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1c19139 commit 7953832
31 files changed
Lines changed: 2605 additions & 28 deletions
File tree
- .changeset
- docs/adr
- packages
- dogfood/test
- metadata-core/src/objects
- objectql/src
- lifecycle
- platform-objects/src
- audit
- identity
- plugins
- driver-sql/src
- plugin-audit/src/objects
- services
- service-automation/src
- service-messaging/src/objects
- spec
- liveness
- scripts/liveness
- src
- contracts
- data
| 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
167 | 173 | | |
168 | 174 | | |
169 | 175 | | |
| |||
192 | 198 | | |
193 | 199 | | |
194 | 200 | | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
195 | 209 | | |
196 | 210 | | |
| 211 | + | |
| 212 | + | |
197 | 213 | | |
198 | 214 | | |
199 | | - | |
200 | | - | |
201 | | - | |
202 | | - | |
203 | | - | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
204 | 220 | | |
205 | 221 | | |
206 | 222 | | |
| |||
| 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
0 commit comments