Skip to content

Commit d9d42d4

Browse files
feat(spec): declare the recycle-bin surface: the sys_recycle_bin entry contract, the retention hook, the restore refusals, the restored event and two grant keys (ADR-0148) (#30577)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu8iKZzw1r2XCrNituG3deMH)_ Fixes #30235 Clause-②: yes (widening) Metadata-protocol: added Skills-docs: follow-up #30237 — `skills/objectstack-api/SKILL.md` teaches that the trash surface is retired and that there is no recycle bin (the `apiMethods` section and the delete note); both stay true after this PR, which declares and reads nothing, so the rewrite rides the engine slice that ships the reader. ## 维护者速读 - 改了什么:在 `@objectstack/spec` 里声明回收站的契约:`sys_recycle_bin` 条目的字段表与值形状、宿主保留期钩子(只认天数)、三个恢复拒绝码、`data.record.restored` 事件名、两个对象权限键;运行时一行未动。 - 为什么改:ADR-0148 D5 把这些拼写交给本卡,引擎(#30237)和控制台(#30239)要在它上面建;没有它,删除仍然不可恢复。 - 风险与回滚:纯新增,可选键无默认值,已有元数据和已解析的权限条目不变;回滚就是 revert 本 PR。 - 先看:`packages/spec/src/data/recycle-bin.zod.ts`(钩子签名与拒绝文案)、`packages/spec/src/security/permission.zod.ts`(两个新键)、`packages/spec/src/api/error-code-ledger.zod.ts`(三个拒绝码先于生产者登记)。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run --project local src/data/recycle-bin.zod.test.ts`,预期 1 个文件、16 个用例全过。 - 需要你做的:无,席位落地。 ## What changes The spec half of the recycle bin ([ADR-0148](docs/adr/0148-recycle-bin-separate-store-restore-under-original-ids-retention-in-days.md) D5, Rollout step 2; ruling 6629858351 on #3146, D7's eight choices ratified 6659364907 on #30234). Every name below is **declared here and read elsewhere**: nothing in this PR writes or reads the bin, and every delete is still a physical delete. - **`packages/spec/src/data/recycle-bin.zod.ts`** (new, exported from `@objectstack/spec/data`): - `RECYCLE_BIN_OBJECT` = `'sys_recycle_bin'` and `RECYCLE_BIN_ENTRY_FIELDS`, the entry's one field list (`id`, `object_name`, `record_id`, `record_count`, `snapshot`, `deleted_by`, `deleted_at`, `retention_days`, `purge_after`), all read-only; `RecycleBinEntrySchema` is its value shape, key for key. The object file, its plugin registration and its `PLATFORM_OBJECTS_BY_PACKAGE` row are the engine slice's (seat decision 2): that registry means "a table exists". - `RecycleBinSnapshotSchema` (with `RecycleBinSnapshotRowSchema`, `RecycleBinClearedReferenceSchema`): the cascade set, master first, secret-channel refs left out (D4), plus the `set_null` references the delete cleared (D2's re-link rule). - The retention hook (D3): `RecycleBinRetentionResolver = (token: string | undefined) => number | undefined`, the same token type `resolveFeatures` takes in `packages/cloud-connection/src/runtime-config-plugin.ts`; `RECYCLE_BIN_RETENTION_DAYS_ENV` = `'OS_RECYCLE_BIN_RETENTION_DAYS'`, `RECYCLE_BIN_DEFAULT_RETENTION_DAYS` = 30; `RecycleBinRetentionDaysSchema` / `RecycleBinRetentionDaysEnvSchema` refuse zero and every non-whole-number with `recycleBinRetentionRefusal(source, value)`, which names the source and the fix and no plan. - `RECYCLE_BIN_RESTORE_REFUSAL_CODES` (three codes) and `RECYCLE_BIN_RESTORE_REFUSAL_STATUS` = 409. - `RECORD_RESTORED_EVENT` = `'data.record.restored'`, a spec constant only (seat decision 1): `DataEventType` / `RealtimeEventType` list emitted events, so it joins them in the change that emits it. - `RECYCLE_BIN_GRANT_KEYS`. - **Error-code ledger** (`packages/spec/src/api/error-code-ledger.zod.ts`): `RESTORE_UNIQUE_VALUE_TAKEN`, `RESTORE_PARENT_PURGED`, `RESTORE_PACKAGE_UNINSTALLED` under `@objectstack/objectql`, ahead of their producer (seat decision 4). - **Object permissions** (`packages/spec/src/security/permission.zod.ts`): `allowRestoreOwnDeleted` and `manageRecycleBin`, optional with no default, so no parsed entry grows a key. Both enter `liveness/permission.json` as `planned`, naming their readers (#30237 evaluator mapping and engine doors, #30239 Console) (seat decision 3). The tombstoned `allowRestore` / `allowPurge` stay refused. - **Regenerated artifacts**: api-surface, export-origins, declaration-map, authorable-surface, json-schema manifest, `content/docs/references/**`, the strictness-ledger counts, liveness state counts, `llms.txt`, and the type-alias pin (+6 isomorphic pins, 831 → 837). - **Changeset**: `@objectstack/spec` minor. ⛔ Not here: the engine's delete path, restore, purge, empty, the TTL reap, the permission mapping, the REST doors (#30237); the Console (#30239); any tombstoned key (`enable.trash`, `softDelete`, `allowRestore`, `allowPurge`, the retired `apiMethods` values). ## Why A deleted record is gone for good today. ADR-0148 rules a separate bin store and leaves the spellings to this card (D6); the engine and Console slices build on them, and ADR-0049's enforce-or-remove gate is passed by naming each reader in its liveness row. ## Tests All readings at head `25af14cf`, after `origin/main` `c23eafa8` was merged in and the build state refreshed. - `packages/spec/src/data/recycle-bin.zod.test.ts` (new, 16 cases): the three names; the hook takes the `resolveFeatures` token and answers days or nothing; `RecycleBinRetentionDaysSchema` accepts 1/7/30/365 and refuses `0, -1, 1.5, NaN, Infinity, null, '30', true` with the refusal text; the env schema accepts `'1' '30' '365'` and refuses `'0' '030' '-7' '7.5' ' 7' 'thirty' ''`; the refusal names the source and the remedy and no plan; the snapshot round-trips and refuses zero rows; the field contract (types, required, read-only, `deleted_by` → `sys_user`) builds a valid `ObjectSchema` under `sys_recycle_bin`; `RecycleBinEntrySchema` matches the field keys one for one; the refusal codes are registered and answer 409; `data.record.restored` stays out of `DataEventType` / `RealtimeEventType`; the two grant keys parse, are never defaulted, and the retired `allowRestore` / `allowPurge` stay refused. - `packages/spec/src/api/error-code-ledger.test.ts`: the three codes parse as `ErrorCode`, sit under `@objectstack/objectql`, and need no standard-synonym waiver. - `packages/spec/src/type-alias-convention.pin.test.ts`: +6 isomorphic pins (831 → 837). <details><summary>Commands and results</summary> - `pnpm --filter @objectstack/spec build` · VERDICT command-exit 0 - `pnpm --filter @objectstack/spec check:generated` · exit 0 · all 14 generated artifacts up to date - `pnpm --filter @objectstack/spec test` (vitest `--project local`) · VERDICT command-exit 0 · Test Files 816 passed (816) · Tests 25660 passed, 1 todo - `pnpm --filter @objectstack/spec typecheck` (tsc, scripts typecheck, test typecheck) · VERDICT command-exit 0 - `turbo run build --filter=!@objectstack/docs` · 119 successful, 119 total - Downstream consumers (`...@objectstack/spec` direction), the three the diff's widened types reach (object permissions, the error-code union): `pnpm --filter @objectstack/plugin-security typecheck` · `@objectstack/core typecheck` · `@objectstack/objectql typecheck` · each VERDICT command-exit 0 - `pnpm --filter @objectstack/plugin-security test` · Test Files 244 passed (244) · Tests 5379 passed, 45 skipped - `@objectstack/core` `vitest run src/security` · Test Files 63 passed (63) · Tests 1221 passed - `@objectstack/lint` `vitest run src/validate-security-posture.test.ts src/validate-retired-permission-residue.test.ts` · Test Files 2 passed · Tests 209 passed </details> No ablation: the diff changes no tested runtime behaviour (declarations only). No dogfood battery: no `!` in the title and no `(narrowing)` arm. ## Acceptance notes - **Resumed work, re-verified.** The previous card thread pushed five commits to `claude/project-thread-jpl1gr` (last `884764a2`). Each hunk was re-read against ADR-0148 and the seat's four decisions; all are kept. One correction: the changeset's `Clause-②:` line carried a free-text arm, and now reads `yes (widening)`, the claim's form (`25af14cf`). The net diff touches no file outside `packages/spec`, `content/docs/references/**`, `docs/audits/2026-07-unknown-key-strictness-ledger*` and `.changeset/`; the object file the first commit wrote under `platform-objects` was moved out by the predecessor itself (seat decision 2). - **Why the grants are object-permission keys.** ADR-0148 D5 says `OPERATION_TO_PERMISSION` in `packages/plugins/plugin-security/src/permission-evaluator.ts` must map `restore` / `purge` to the new grants; that map targets object-permission keys, so both keys live on the per-object entry. `manageRecycleBin` is bin administration on that object; emptying purges every entry the holder manages. - **What the readers must add (the engine and Console cards, not this PR).** `EffectiveObjectPermission` (the `/auth/me/permissions` shape) gains both optional keys because it derives from the same base; nothing fills them yet. `GRANT_BITS` in `packages/core/src/security/effective-object-permissions.ts`, `ObjectPermissionVerbTarget` / `objectPermissionGrants` in `packages/spec/src/security/permission.zod.ts`, and `CAPS` in `ui/packages/app-shell/src/views/metadata-admin/previews/PermissionPreview.tsx` list neither key today. Carrier: #30237 (merge, fold, evaluator map, flipping both liveness rows to `live`) and #30239 (the Console columns). - **Strictness.** The four object sites in `recycle-bin.zod.ts` are `z.object` and triaged `wire` in the strictness ledger: persisted runtime state the engine writes and reads, never authored; the snapshot's `values` carry whatever fields the deleted rows had. - **No ADR anchor file.** ADR-0148's id sits in the code at each declaration; no `scripts/adr-anchors/` entry was added, because the claim's file surface does not name one. The seat may ask for it. - **Timestamps.** `deleted_at` / `purge_after` accept ISO 8601 with an offset (`datetime({ offset: true })`), a little wider than the repository's common `datetime()` form, so an engine writing a local offset is not refused. ## Verification `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at head `25af14cf` (29 paths vs merge base `c23eafa8`, +852 / -18) derived 106 commands. All 106 ran serially on that head after the full build, each exit code captured before any pipe: **106 × exit 0**. `--ran` reconciliation: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). `pnpm lint` and the rest of the CI farm are CI's run; the PR's check-runs are the record for them. <details><summary>The 106 derived commands, all exit 0</summary> - `check-adr-0087-registration --base origin/main` (no declared-breaking changeset; 1 non-breaking seen) and `--self-test`; `check-changeset-no-major --base origin/main` (no `major` bump; protocol lockstep 18 / 18.3.0; re-run offline with this body as the `--event` payload: LEVEL AXIS ✓, `yes (widening)` + `added`, no moved package graded `patch`); `check-empty-changeset`; `check-issue-citations` (13 citations resolve on the board); `check-adr-links`; `check-closing-keyword-parity`; `check-comment-mask-adoption`, `check-comment-mask-corpus`; `check-doc-route-spelling --advisory`; `check-docs-section-name`; `check-section-landing-index`; `check-plugin-teardown-shape` (fixture `621a4876` fetched for the self-test); `check-dev-prereqs`, `check-dts-emitted`, `assert-turbo-remote-cache`, `plan-test-core-shards`, `prune-turbo-cache`, `release-rehearsal-clone`, `release-pending-publish` self-tests; `docs-audit/check-affected-docs`, `check-drift-comment`. - `@objectstack/spec`: `check:api-surface`, `check:authorable-surface`, `check:browser-reachable-entries`, `check:docs`, `check:dual-source-exports`, `check:duration-unit-keys`, `check:empty-state`, `check:entry-nameability` (626 probes, 0 new unnameable mentions), `check:error-code-provenance`, `check:export-origins`, `check:exported-any`, `check:generated`, `check:liveness`, `check:llms-txt`, `check:skill-examples`, `check:skill-refs`, `check:strictness-ledger`, `check:variant-docs`, `check:yaml-examples`. - `@objectstack/lint`: `check:doc-formula-expressions`, `check:doc-security-posture`. - Root: `check:changeset-gate-self-tests`, `ci-filter-parity`, `closed-ceremony-text`, `corpus-claim-drift`, `cross-package-test-inputs`, `dispatcher-error-vocabulary`, `doc-anchors`, `doc-authoring`, `doc-frontmatter`, `docs-audit-scope`, `docs-redirects`, `docs-single-h1`, `docs-spec-enumerations`, `docs-transcript-drift`, `driver-memory-census`, `dts-closure`, `dual-build-cjs-loads`, `engine-double-contract`, `error-code-casing`, `error-status-conformance`, `gitlink-declared`, `issue-citations`, `keyed-text-bounds`, `lean-entry-closure`, `logger-receiver-detach`, `merge-driver`, `nul-bytes`, `objectql-double-limit`, `org-identifier`, `page-declaration-shape`, `platform-checklist`, `platform-object-tenancy-census`, `pm-prior-rulings`, `published-files`, `published-readme-links`, `query-options-erasure`, `react-page-adapter-contract`, `refd-timer-probe`, `registry-log-declared`, `rest-log-spy-declared`, `role-word`, `skill-identifier-liveness`, `slot-lookup`, `sourcemap-no-sources-content`, `spec-docblock-symbol-anchors`, `spec-parsed-alias`, `system-context-census`, `test-source-alias`, `tier-file-adoption`, `type-check-coverage`, `type-check-debt`, `undeclared-dep-imports`, `vendor-version-stamps`, `watch-hint-literal`, `where-matcher`. </details> Branch: `claude/project-thread-uwrsn3`, the card thread's harness-pinned branch (a measured platform exception to `claude/issue-30235-…`), resumed from `claude/project-thread-jpl1gr@884764a` by merge, never rebase; every picked-up hunk was re-read and every check re-run on this head. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01L6bhgWAJDEGE4g7p29M3zq)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L6bhgWAJDEGE4g7p29M3zq --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent bdbaf80 commit d9d42d4

29 files changed

Lines changed: 852 additions & 18 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): declare the recycle bin's spec surface (ADR-0148): the `sys_recycle_bin` entry contract, the host retention resolver hook, the snapshot shape, the restore refusal codes, the `restored` event name and the two grant keys
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
A protocol addition: new spellings with their readers named, and no tombstoned key returns. Declared, not yet read. Every delete is still a physical delete, and nothing writes or reads the recycle bin until the engine slice lands it (snapshot on delete, restore, purge, empty, the TTL reap, the permission mapping and the REST doors); the Console's admin page and its undo toast follow it. This release gives both something to build on.
11+
12+
- **`@objectstack/spec` (`@objectstack/spec/data`):** `RECYCLE_BIN_OBJECT` (`'sys_recycle_bin'`) and `RECYCLE_BIN_ENTRY_FIELDS`, the entry's field list the object is built from: `object_name`, `record_id`, `record_count`, `snapshot`, `deleted_by`, `deleted_at`, `retention_days` and `purge_after` (the deadline stamped at delete time), all read-only, with `RecycleBinEntrySchema` as the entry's value shape. The object itself is registered with the engine slice, so no table and no platform-object name exist until a writer does. `RECYCLE_BIN_RETENTION_DAYS_ENV` (`'OS_RECYCLE_BIN_RETENTION_DAYS'`) and `RECYCLE_BIN_DEFAULT_RETENTION_DAYS` (30). `RecycleBinRetentionResolver`, the host hook's contract: `(token: string | undefined) => number | undefined`, called with the same opaque token `resolveFeatures` receives, answering a positive whole number of days or `undefined` to keep the default. `RecycleBinRetentionDaysSchema` and `RecycleBinRetentionDaysEnvSchema` hold the answer and the variable to that rule, and `recycleBinRetentionRefusal(source, value)` is the refusal text: zero or anything that is not a positive whole number is a configuration defect, never "purge now" or "keep forever". The framework knows days, never plans. `RecycleBinSnapshotSchema` (with `RecycleBinSnapshotRowSchema` and `RecycleBinClearedReferenceSchema`): the cascade set an entry holds, the master first and its details in cascade order, secret-channel refs left out, plus the `set_null` references the delete cleared. `RECYCLE_BIN_RESTORE_REFUSAL_CODES` and `RECYCLE_BIN_RESTORE_REFUSAL_STATUS` (409). `RECORD_RESTORED_EVENT` (`'data.record.restored'`): a restore emits it, never a create; `DataEventType` and `RealtimeEventType` list only emitted events, so the name joins them in the change that emits it. `RECYCLE_BIN_GRANT_KEYS`.
13+
- **`@objectstack/spec`, error-code ledger:** `RESTORE_UNIQUE_VALUE_TAKEN`, `RESTORE_PARENT_PURGED` and `RESTORE_PACKAGE_UNINSTALLED`, registered under `@objectstack/objectql`, which answers them.
14+
- **`@objectstack/spec`, object permissions:** two new optional keys, `allowRestoreOwnDeleted` (restore the entries of this object the caller deleted) and `manageRecycleBin` (list every entry of this object, restore any, purge; emptying purges every entry the holder manages). No default, so no parsed entry grows a key. Not read yet: a dispatched `restore` or `purge` stays denied by the permission evaluator's fail-closed backstop whatever they say, and their schema descriptions say they are not read yet. The retired `allowRestore` / `allowPurge` stay tombstoned.

‎content/docs/references/api/auth.mdx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ const result = AuthProvider.parse(data);
110110
| **allowTransfer** | `boolean` | optional (default: `false`) | [RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id) |
111111
| **allowRestore** | `never` | optional | [REMOVED] `objects.<object>.allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
112112
| **allowPurge** | `never` | optional | [REMOVED] `objects.<object>.allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
113+
| **allowRestoreOwnDeleted** | `boolean` | optional | [PLANNED] Recycle bin: restore the entries of this object the caller deleted. Not read yet: until the engine's restore ships, a dispatched restore stays denied. |
114+
| **manageRecycleBin** | `boolean` | optional | [PLANNED] Recycle bin administration on this object: list every entry, restore any, and purge (emptying the bin purges every entry the caller manages). Not read yet: until the engine's restore ships, a dispatched restore or purge stays denied. |
113115
| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) |
114116
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. |
115117
| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org |
@@ -171,6 +173,8 @@ This schema accepts one of the following structures:
171173
| **allowTransfer** | `boolean` | optional (default: `false`) | [RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id) |
172174
| **allowRestore** | `never` | optional | [REMOVED] `objects.<object>.allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
173175
| **allowPurge** | `never` | optional | [REMOVED] `objects.<object>.allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
176+
| **allowRestoreOwnDeleted** | `boolean` | optional | [PLANNED] Recycle bin: restore the entries of this object the caller deleted. Not read yet: until the engine's restore ships, a dispatched restore stays denied. |
177+
| **manageRecycleBin** | `boolean` | optional | [PLANNED] Recycle bin administration on this object: list every entry, restore any, and purge (emptying the bin purges every entry the caller manages). Not read yet: until the engine's restore ships, a dispatched restore or purge stays denied. |
174178
| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) |
175179
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. |
176180
| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org |

‎content/docs/references/api/automation-api.mdx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ const result = AutomationApiErrorCode.parse(data);
417417

418418
| Property | Type | Required | Description |
419419
| :--- | :--- | :--- | :--- |
420-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +357 more>` | ✅ | The inner refusal's code in its WIRE spelling - the ADR-0112 member a record save of the same write answers in `error.code` (e.g. `UNIQUE_VIOLATION`); the closed set `ApiErrorSchema.code` parses against |
420+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +360 more>` | ✅ | The inner refusal's code in its WIRE spelling - the ADR-0112 member a record save of the same write answers in `error.code` (e.g. `UNIQUE_VIOLATION`); the closed set `ApiErrorSchema.code` parses against |
421421
| **message** | `string` | ✅ | The inner refusal's own diagnostic text, verbatim, without the node and operation prefixes the run's failure text adds |
422422

423423
### Allowed Values: `FlowInnerRefusal.code`
@@ -705,6 +705,9 @@ const result = AutomationApiErrorCode.parse(data);
705705
* `REQUEST_NOT_FOUND`
706706
* `RESEED_NO_ROWS`
707707
* `RESEED_SKIPPED`
708+
* `RESTORE_PACKAGE_UNINSTALLED`
709+
* `RESTORE_PARENT_PURGED`
710+
* `RESTORE_UNIQUE_VALUE_TAKEN`
708711
* `RESUME_CHILD_PENDING`
709712
* `RESUME_FAILED`
710713
* `RESUME_IN_PROGRESS`

‎content/docs/references/api/contract.mdx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const result = ApiErrorSchema.parse(data);
2828

2929
| Property | Type | Required | Description |
3030
| :--- | :--- | :--- | :--- |
31-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +357 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
31+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +360 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
3232
| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) |
3333
| **message** | `string` | ✅ | Readable error message |
3434
| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. |
@@ -323,6 +323,9 @@ const result = ApiErrorSchema.parse(data);
323323
* `REQUEST_NOT_FOUND`
324324
* `RESEED_NO_ROWS`
325325
* `RESEED_SKIPPED`
326+
* `RESTORE_PACKAGE_UNINSTALLED`
327+
* `RESTORE_PARENT_PURGED`
328+
* `RESTORE_UNIQUE_VALUE_TAKEN`
326329
* `RESUME_CHILD_PENDING`
327330
* `RESUME_FAILED`
328331
* `RESUME_IN_PROGRESS`

‎content/docs/references/api/error-code-ledger.mdx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ const result = ErrorCode.parse(data);
490490
* `REQUEST_NOT_FOUND`
491491
* `RESEED_NO_ROWS`
492492
* `RESEED_SKIPPED`
493+
* `RESTORE_PACKAGE_UNINSTALLED`
494+
* `RESTORE_PARENT_PURGED`
495+
* `RESTORE_UNIQUE_VALUE_TAKEN`
493496
* `RESUME_CHILD_PENDING`
494497
* `RESUME_FAILED`
495498
* `RESUME_IN_PROGRESS`

‎content/docs/references/api/protocol.mdx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,8 @@ This schema accepts one of the following structures:
11971197
| **allowTransfer** | `boolean` | optional (default: `false`) | [RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id) |
11981198
| **allowRestore** | `never` | optional | [REMOVED] `objects.<object>.allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
11991199
| **allowPurge** | `never` | optional | [REMOVED] `objects.<object>.allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
1200+
| **allowRestoreOwnDeleted** | `boolean` | optional | [PLANNED] Recycle bin: restore the entries of this object the caller deleted. Not read yet: until the engine's restore ships, a dispatched restore stays denied. |
1201+
| **manageRecycleBin** | `boolean` | optional | [PLANNED] Recycle bin administration on this object: list every entry, restore any, and purge (emptying the bin purges every entry the caller manages). Not read yet: until the engine's restore ships, a dispatched restore or purge stays denied. |
12001202
| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) |
12011203
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. |
12021204
| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org |
@@ -1658,6 +1660,8 @@ One changed key path of the pending draft, and the last write that changed it.
16581660
| **allowTransfer** | `boolean` | optional (default: `false`) | [RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id) |
16591661
| **allowRestore** | `never` | optional | [REMOVED] `objects.<object>.allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
16601662
| **allowPurge** | `never` | optional | [REMOVED] `objects.<object>.allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
1663+
| **allowRestoreOwnDeleted** | `boolean` | optional | [PLANNED] Recycle bin: restore the entries of this object the caller deleted. Not read yet: until the engine's restore ships, a dispatched restore stays denied. |
1664+
| **manageRecycleBin** | `boolean` | optional | [PLANNED] Recycle bin administration on this object: list every entry, restore any, and purge (emptying the bin purges every entry the caller manages). Not read yet: until the engine's restore ships, a dispatched restore or purge stays denied. |
16611665
| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) |
16621666
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. |
16631667
| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org |

‎content/docs/references/data/index.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Data Protocol — complete schema reference
33
navTitle: Data Protocol
4-
description: "The ObjectStack Data Protocol in 31 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
4+
description: "The ObjectStack Data Protocol in 32 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -36,6 +36,7 @@ This section contains all protocol schemas for the data layer of ObjectStack.
3636
<Card href="/docs/references/data/object" title="Object" description="Source: packages/spec/src/data/object.zod.ts" />
3737
<Card href="/docs/references/data/picklist" title="Picklist" description="Source: packages/spec/src/data/picklist.zod.ts" />
3838
<Card href="/docs/references/data/query" title="Query" description="Source: packages/spec/src/data/query.zod.ts" />
39+
<Card href="/docs/references/data/recycle-bin" title="Recycle Bin" description="Source: packages/spec/src/data/recycle-bin.zod.ts" />
3940
<Card href="/docs/references/data/seed" title="Seed" description="Source: packages/spec/src/data/seed.zod.ts" />
4041
<Card href="/docs/references/data/seed-file" title="Seed File" description="Source: packages/spec/src/data/seed-file.zod.ts" />
4142
<Card href="/docs/references/data/seed-loader" title="Seed Loader" description="Source: packages/spec/src/data/seed-loader.zod.ts" />

‎content/docs/references/data/meta.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"driver-turso",
3737
"field-value",
3838
"picklist",
39+
"recycle-bin",
3940
"seed-file"
4041
]
4142
}

0 commit comments

Comments
 (0)