Skip to content
Draft
Show file tree
Hide file tree
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
33 changes: 33 additions & 0 deletions .changeset/remove-enable-trash-mru.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
"@objectstack/spec": minor
---

feat(spec)!: remove the dead `object.enable.trash` / `enable.mru` capability flags (#2377, ADR-0049 enforce-or-remove — close-out)

Both flags parsed and defaulted to `true` but had **no runtime consumer**:
every delete has always been a hard delete (no recycle bin), and no MRU
tracking was ever implemented. A default-true flag promising recoverability
is the worst kind of false affordance — first-party objects were authoring
`trash: false // Never soft-delete audit logs` in the belief that a
soft-delete existed to opt out of.

- `ObjectCapabilities` is now **`.strict()`** (pattern of the tenancy block,
#2763): an unknown `enable` key — the retired `trash`/`mru` or a typo like
`feedEnabled` — fails parse with upgrade guidance instead of stripping
silently (#1535). The retired-key tombstones live in
`CAPABILITIES_RETIRED_KEY_GUIDANCE`.
- ~45 first-party object definitions (platform-objects, plugin-security,
plugin-audit, plugin-approvals, plugin-sharing, metadata-core,
service-realtime, examples) dropped their inert `trash:`/`mru:` lines.
- Liveness ledger: both entries deleted (removal precedent: `tags`/
`recordName`); object row in the README count table now shows **0 dead**.
- Docs + skills no longer advertise a recycle bin / MRU tracking; the API
skill's "DELETE is soft-delete when `trash: true`" claim is corrected to
the real contract (hard delete; use per-field `trackHistory` or a
`lifecycle` policy for recoverability).

**Migration**: delete any `enable.trash` / `enable.mru` keys from object
metadata — they never changed behavior. `ObjectSchema.create()` /
`ObjectCapabilities.parse()` now reject them with this prescription. A real
recycle bin or MRU feature, if built, returns as a live enforced flag
(#1893 prune-or-build).
2 changes: 0 additions & 2 deletions content/docs/concepts/metadata-driven.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -470,8 +470,6 @@ export const Account = ObjectSchema.create({
files: true, // Enable file attachments
feeds: true, // Enable activity feed
activities: true, // Enable tasks and events
trash: true, // Enable soft delete
mru: true, // Track Most Recently Used
},
});
```
Expand Down
5 changes: 0 additions & 5 deletions content/docs/data-modeling/objects.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ enable: {
files: true, // File attachments
feeds: true, // Activity feed and comments
activities: true, // Tasks and events tracking
trash: true, // Soft delete with restore
mru: true, // Most Recently Used tracking
clone: true, // Deep record cloning
}
```
Expand All @@ -98,8 +96,6 @@ enable: {
| `files` | `false` | Enable file attachments |
| `feeds` | `true` | Enable social feed and comments |
| `activities` | `true` | Enable tasks and events tracking |
| `trash` | `true` | Enable soft delete with restore |
| `mru` | `true` | Track Most Recently Used list |
| `clone` | `true` | Allow record deep cloning |

### Enterprise Features
Expand Down Expand Up @@ -330,7 +326,6 @@ export const ProjectTask = ObjectSchema.create({
searchable: true,
trackHistory: true,
feeds: true,
trash: true,
},

validations: [
Expand Down
2 changes: 0 additions & 2 deletions content/docs/data-modeling/schema-design.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ enable: {
files: true, // Allow file attachments
feeds: true, // Enable activity feed (Chatter-like)
activities: true, // Track tasks and events
trash: true, // Soft delete with recycle bin
mru: true, // Track Most Recently Used
}
```

Expand Down
3 changes: 0 additions & 3 deletions content/docs/protocol/objectql/schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,6 @@ enable:
# Data Management
trackHistory: true # Track field history (who changed what, when)
searchable: true # Index for global search
trash: true # Enable soft-delete with restore capability

# API & Integration
apiEnabled: true # Generate REST/GraphQL endpoints

Expand All @@ -246,7 +244,6 @@ enable:
activities: true # Enable tasks and events

# User Experience
mru: true # Track Most Recently Used list
clone: true # Allow record deep cloning
```

Expand Down
2 changes: 0 additions & 2 deletions content/docs/references/data/object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@ const result = ApiMethod.parse(data);
| **files** | `boolean` | ✅ | Generic record Attachments panel (sys_attachment). Opt-in: true surfaces the panel and permits attachments targeting this object; otherwise creation is rejected. Field.file/Field.image are independent |
| **feeds** | `boolean` | ✅ | Record comments/collaboration feed. Default on; explicit false hides the feed UI and rejects new comments for this object |
| **activities** | `boolean` | ✅ | Record activity timeline (sys_activity mirror of CRUD). Default on; explicit false stops mirroring and hides the timeline |
| **trash** | `boolean` | ✅ | Enable soft-delete with restore capability |
| **mru** | `boolean` | ✅ | Track Most Recently Used (MRU) list for users |
| **clone** | `boolean` | ✅ | Allow record deep cloning |


Expand Down
2 changes: 0 additions & 2 deletions examples/app-todo/src/objects/task.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ export const Task = ObjectSchema.create({
files: true,
feeds: true,
activities: true,
trash: true,
mru: true,
},

// Database indexes for performance
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/utils/lint-liveness-properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ describe('lintLivenessProperties', () => {
// action.undoable). The object/field WALKER is still exercised by the
// silent-clean and default-on-suppression cases below.

it('does NOT warn on a default-on flag the author left alone (enable.trash: true)', () => {
const findings = lintLivenessProperties(objStack({ enable: { trash: true } }));
expect(paths(findings).some((m) => m.includes('enable.trash'))).toBe(false);
it('does NOT warn on a default-on flag the author left alone (enable.searchable: true)', () => {
const findings = lintLivenessProperties(objStack({ enable: { searchable: true } }));
expect(paths(findings).some((m) => m.includes('enable.searchable'))).toBe(false);
});

// #2707/#2727: every ObjectCapabilities flag is now LIVE (opt-out
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/utils/lint-liveness-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* `"authorWarn": true` (+ an optional `"authorHint"`). A property being merely
* `dead` is NOT enough — plenty of dead props are benign display/doc metadata.
* Only entries an author would be *misled* by are marked. Booleans warn only when
* set truthy (so schema defaults like `enable.trash` never trip it); object/
* set truthy (so schema defaults like `enable.searchable` never trip it); object/
* string/array props warn when present at all.
*/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,5 @@ export const SysMetadataAuditObject = ObjectSchema.create({
searchable: false,
apiEnabled: true,
apiMethods: ['get', 'list'],
trash: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,5 @@ export const SysMetadataCommitObject = ObjectSchema.create({
searchable: false,
apiEnabled: true,
apiMethods: ['get', 'list'],
trash: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,5 @@ export const SysMetadataHistoryObject = ObjectSchema.create({
searchable: false,
apiEnabled: true,
apiMethods: ['get', 'list'],
trash: false,
},
});
1 change: 0 additions & 1 deletion packages/metadata-core/src/objects/sys-metadata.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ export const SysMetadataObject = ObjectSchema.create({
// so the generic write verbs were a latent hole for a user-context raw write
// the bucket forbids. Reads stay open for the Setup "Data Model" grids.
apiMethods: ['get', 'list'],
trash: false,
},

// Named list views — power the Setup App "Data Model" group so admins
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,5 @@ export const SysViewDefinitionObject = ObjectSchema.create({
searchable: false,
apiEnabled: true,
apiMethods: ['get', 'list', 'create', 'update', 'delete'],
trash: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/audit/sys-attachment.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ export const SysAttachment = ObjectSchema.create({
// reap guard reclaims a file's bytes once its last join row is gone, so a
// "restore" would dangle. Declare `false` — the honest state — rather than
// claim a restore capability the runtime does not provide.
trash: false,
mru: false,
clone: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/identity/sys-account.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,5 @@ export const SysAccount = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get', 'list'],
trash: true,
mru: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/identity/sys-api-key.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,5 @@ export const SysApiKey = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get', 'list'],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,5 @@ export const SysBusinessUnitMember = ObjectSchema.create({
// entries. Reconcile-safe: import/export are not generic write verbs, so
// reconcileManagedApiMethods (managedBy:'platform') never strips them.
apiMethods: ['get', 'list', 'create', 'update', 'delete', 'import', 'export'],
trash: true,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,5 @@ export const SysBusinessUnit = ObjectSchema.create({
// and `effective_from/to` are designed for HRIS batch sync (#3025). Import
// reuses the create/update affordances this object already grants.
apiMethods: ['get', 'list', 'create', 'update', 'delete', 'import', 'export'],
trash: true,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,5 @@ export const SysDeviceCode = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get'],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,5 @@ export const SysInvitation = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get', 'list'],
trash: false,
mru: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/identity/sys-jwks.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,5 @@ export const SysJwks = ObjectSchema.create({
searchable: false,
apiEnabled: false,
apiMethods: [],
trash: false,
mru: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/identity/sys-member.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,5 @@ export const SysMember = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get', 'list'],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,5 @@ export const SysOauthAccessToken = ObjectSchema.create({
searchable: false,
apiEnabled: false,
apiMethods: [],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,5 @@ export const SysOauthApplication = ObjectSchema.create({
// bypass server-side OAuth validation. The Delete row action above is
// wired to /api/v1/auth/oauth2/delete-client.
apiMethods: ['get', 'list'],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,5 @@ export const SysOauthClientAssertion = ObjectSchema.create({
searchable: false,
apiEnabled: false,
apiMethods: [],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,5 @@ export const SysOauthClientResource = ObjectSchema.create({
searchable: false,
apiEnabled: false,
apiMethods: [],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,5 @@ export const SysOauthConsent = ObjectSchema.create({
searchable: false,
apiEnabled: false,
apiMethods: [],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,5 @@ export const SysOauthRefreshToken = ObjectSchema.create({
searchable: false,
apiEnabled: false,
apiMethods: [],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,5 @@ export const SysOauthResource = ObjectSchema.create({
searchable: false,
apiEnabled: false,
apiMethods: [],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,5 @@ export const SysOrganization = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get', 'list'],
trash: true,
mru: true,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,5 @@ export const SysScimProvider = ObjectSchema.create({
// under /api/v1/auth/scim/*; the generic data layer is read-only so the
// credential cannot be written/bypassed through it.
apiMethods: ['list'],
trash: false,
mru: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/identity/sys-session.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,6 @@ export const SysSession = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get', 'list'],
trash: false,
mru: false,
clone: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,5 @@ export const SysSsoProvider = ObjectSchema.create({
// the generic data layer is read-only so sysadmins cannot bypass
// server-side validation / secret handling.
apiMethods: ['get', 'list'],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,5 @@ export const SysTeamMember = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get', 'list'],
trash: false,
mru: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/identity/sys-team.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,5 @@ export const SysTeam = ObjectSchema.create({
// The HTTP layer now answers 405 (api-exposure) before the engine's
// 403 backstop. See #1591.
apiMethods: ['get', 'list'],
trash: true,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,5 @@ export const SysTwoFactor = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get'],
trash: false,
mru: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,5 @@ export const SysUserPreference = ObjectSchema.create({
searchable: false,
apiEnabled: true,
apiMethods: ['get', 'list', 'create', 'update', 'delete'],
trash: false,
mru: false,
},
});
2 changes: 0 additions & 2 deletions packages/platform-objects/src/identity/sys-user.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -760,8 +760,6 @@ export const SysUser = ObjectSchema.create({
// clamped to the profile-field whitelist ({name, image}) by the guard —
// `userActions.edit: true` above declares the affordance.
apiMethods: ['get', 'list', 'update'],
trash: true,
mru: true,
},

// Email uniqueness is enforced by the unique index above (and better-auth's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,5 @@ export const SysVerification = ObjectSchema.create({
// #1591 — reads only: writes are refused by the identity write guard
// (ADR-0092 D2) and owned by better-auth. HTTP answers 405 before the 403.
apiMethods: ['get'],
trash: false,
mru: false,
},
});
1 change: 0 additions & 1 deletion packages/platform-objects/src/system/sys-secret.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ export const SysSecret = ObjectSchema.create({

enable: {
trackHistory: false, // rotation events are recorded by sys_setting_audit
audit: true,
// [ADR-0103] Engine-owned: secrets are minted/rotated only by the settings /
// secret service (SYSTEM_CTX), never via the generic data API. Locked to
// reads (ciphertext only; decryption is a separate privileged path) — an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ export const SysSettingAudit = ObjectSchema.create({

enable: {
trackHistory: false,
audit: false, // this IS the audit; no recursion
// [ADR-0103] Engine-owned: appended by the settings service (SYSTEM_CTX),
// never via the generic data API. Reads stay open for compliance review.
apiMethods: ['get', 'list'],
Expand Down
Loading
Loading