Repository navigation
Description
Current state
The spec declares soft-delete capability in two places, but neither has any behavior-changing reader — every delete today is a physical hard delete at the driver level.
enable.trash(defaulttrue, described as "Enable soft-delete with restore capability") —packages/spec/src/data/object.zod.ts(~L110)SoftDeleteConfigSchema(enabled/field: 'deleted_at'/cascadeDelete) —packages/spec/src/data/object.zod.ts(~L288)- The permission model also reserves a Restore (soft-delete recovery) semantic (
packages/spec/src/security/permission.zod.ts)
The June 2026 liveness audit (docs/audits/2026-06-objectschema-property-liveness.md) confirms:
trashis one of the 10 flags with zero behavior-changing readers in either repo;softDelete↔enable.trash— duplicated, both dead.
The default trash: true is actively misleading: schema authors reasonably assume deleted records are recoverable, but they are gone.
Note: deleted_at on some identity objects (e.g. sys_user) is a better-auth convention local to those objects, not a platform mechanism.
Proposal
Implement platform-level soft delete honoring the existing spec surface:
- Delete path: when soft delete is enabled for an object,
deletestamps the configured timestamp field (defaultdeleted_at) instead of removing the row. - Query path: default-scope all reads to exclude soft-deleted rows; provide an explicit opt-in to include/only-trashed (recycle-bin views).
- Restore: a restore operation clearing the marker, gated by the reserved Restore permission.
- Purge: a real (physical) delete for emptying the trash, plus optional retention/TTL via the ADR-0057 LifecycleService.
- Cascade: honor
cascadeDeletefor related records (or explicitly descope it for v1). - Spec reconciliation: collapse the duplicated
enable.trashvssoftDeletesurface into one canonical config (and fix the misleadingtrash: truedefault until the runtime actually enforces it).
Open questions: interaction with unique indexes (soft-deleted rows still occupy unique keys), lookup integrity when a target is trashed, and REST/console surface for the recycle bin.
Activity
fix(spec): register the missing migration surface for the enable.tras…
fix(spec): author-visible refusals and prescriptions state each decis…
github-actions commentedon Oct 19, 2026
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.
objectstack-fleet commentedon Nov 4, 2026
coordinator#1 · hotlong · session_6eb9ec2e0e3f410b91239dab7a337b9e
Ruling · 2026-11-04T11:50Z · maintainer, in session (cloud coordinator thread): direction for this card, and unparked.
Maintainer's words, in order:
Why now: cloud#3565 rules that the AI chat may write records after a confirm card (Salesforce Agentforce as the reference). Its step 3, deleting from chat, is blocked until deletes are recoverable. The console's delete is permanent today.
What is ruled
deleted_atcolumn on every table. On delete, the engine snapshots the rows the delete removes (the cascade set fromengine-delete-cascade.ts) into one system object, for examplesys_recycle_bin, as one entry per delete operation, and then deletes physically as today. Restore writes the rows back under their original ids. Purge removes the entry. Every read path stays untouched: query, aggregate, REST, the analytics engine's SQL, rollups, lookup pickers, search, AI tools, MCP and export. Unique keys free immediately, and the per-tenant schema change is one table, not a column on every object. This is how Dataverse (a "bin" data source, restored with the same record id) and ServiceNow (sys_audit_delete) do it. Salesforce'sIsDeletedcolumn is the other shape, and it is not chosen here.OS_RECYCLE_BIN_RETENTION_DAYS. The framework knows days, never plans (ADR-0012). A host may inject retention through a resolver hook, the same pattern asresolveFeatures.Proposed v1 scope, adopting mainstream rules rather than inventing new ones
sys_*);sys_filetombstone pattern (attachment-lifecycle.ts, 30-day reap);created_by/created_at(ServiceNow's script-restore pitfall);set_nullcleared is re-linked only if nothing has changed it since (Salesforce: 「only restores lookup relationships that have not been replaced」). Flow enrollments are not restored (HubSpot). A restore emits its own 「restored」 event, never a create, so record-after-create flows do not fire, and rollups recompute.sys_secretrows until purge (today a delete releasessys_secret, objectql: deleting a record does not release the sys_secret row its secret field names #27581); member-facing 「我删除的」.Spec and ADR
enable.trash,softDeleteandallowRestore/allowPurgewere removed in 16.0 under ADR-0049 (enforce-or-remove), because nothing read them. This card brings back a new surface: new keys, not the tombstoned ones. It needs an ADR, or an ADR-0049 amendment, naming the readers that will exist.Risks to plan for
Labels:
status:parked→ removed;pm:queue,priority:p2.🤖 Generated with Claude Code
objectstack-fleet commentedon Nov 4, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p1 ·
domain:serverkept · 2026-11-04T13:08Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: split by lane · this card becomes the tracking parent · 2026-11-05T02:55Z
⛔ Not a claim.
domain:server#4'sRequest: triage · #3146. The ruled shape (6629858351) spans four landing sites, so it is split into four children, eachPart of #3146, chained byBlocked-by::domain:skills. First.domain:spec. Blocked by docs(adr): the recycle bin — a separate store, restore under original ids, retention in days (#3146 slice 1) #30234.domain:server. Blocked by spec: declare the recycle-bin surface — the sys_recycle_bin object, the retention hook and the restore refusals (#3146 slice 2) #30235.domain:ui. Blocked by engine: snapshot deletes into the recycle bin, restore under original ids, purge, and the TTL reap (#3146 slice 3) #30237.trackingand is never dispatched. It closes when its children land. The cloud enterprise layer stays cloud's own card, as ruled.Generated by Claude Code