Skip to content

Commit 0721848

Browse files
fix(spec): grade permission rowLevelSecurity label/description live — Studio's permission editor shows them (#21607)
Fixes #20299 Clause-②: no ## What changed `permission.rowLevelSecurity.label` and `permission.rowLevelSecurity.description` move `dead` → `live` in the liveness ledger. Studio's permission editor now draws both on every row-level security policy card. That came from objectui#11199, landed as PR objectui#11215 (merge `f8334f8777`). The rows cite that reader at the `.objectui-sha` pin `89cad75d557`, measured in the pinned tree and never on objectui `main`. This is the last remainder the landing record `5909245536` left on this card. The `view` container `label` row stays `dead` under ruling A (`5907340127`, Q1) and is not touched here. Files (5, +59 / −51): - `packages/spec/liveness/permission.json`: the two rows. Each carries `evidence` (the reader), `producer` (registration → route → data read → framework answer → an authored producer) and a note that keeps the superseded verdict as history. - `packages/spec/liveness/state-counts/permission.md`: regenerated by `gen:liveness-counts`, never hand-edited. `permission` is now 38 live / 4 dead (was 36 / 6). - `packages/spec/liveness/README.md`: the `permission` Notes cell listed these two keys in the dead set. One dated clause records the re-grade and the dead set that remains (`priority`, `tags`, and the two `objects.allowRestore` / `allowPurge` tombstones). The `check:liveness` failure text names this cell as owed when a count moves. - `packages/lint/src/lint-liveness-properties.test.ts`: two pins re-subjected; see below. - `.changeset/20299-rls-policy-rows-live.md`: `@objectstack/spec` `patch`. The ledger ships in the package (measured below). ## The reading, at the pin The pin on this branch's base `6c5697dff` is `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842`. `git merge-base --is-ancestor f8334f8777 89cad75d557` answers exit 0, which proves itself. Every line below is `git show 89cad75d557:PATH` in the objectui object store. - **Reader.** `packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx:352-357` (`PermissionAdvancedFacets`): each policy card opens with `pol.label`, and `pol.description` sits beneath it, both verbatim. A policy whose values are absent or empty draws nothing. The section is a collapsible that opens closed (`FacetSection`, `useState(!!defaultOpen)`, and no `defaultOpen` is passed). So an author sees the policy count, then both values after one click. - **Mount.** `PermissionMatrixEditor.tsx:1373` (`PermissionMatrixEditPage`) mounts the facet with no condition, in the main body after the `loading` early return at `:1030`. - **Registration.** `services/builtinComponents.tsx:182-187` registers `EditPage: PermissionMatrixEditPage` for `permission`. The later `permission` registration in `anchors.ts:373` sets only `anchors`. `registry.ts:398-409` merges defined keys only, so the EditPage stands. - **Dispatch and route.** `ResourceEditPage.tsx:332-335` (`MetadataResourceEditPage`) returns the custom EditPage for every non-create item. It is the element of `console/AppContent.tsx:991` (`metadata/:type/:name`, no active app) and `:1091` (`:type/:name` under `metadata`, inside an app). There is a second mount: the Studio Access pillar, `studio-design/StudioDesignSurface.tsx:5275`, renders `PermissionMatrixEditPage` embedded. - **Data.** `PermissionMatrixEditor.tsx:512` reads `client.layered(type, name)`, which is `packages/data-objectstack/src/metadata-client.ts:1289`, `GET /meta/:type/:name/layers`. The draft is `{ ...effective, ... }` at `:559-560`, so `rowLevelSecurity` arrives whole. - **Control.** The same file at the superseded pin `db11afd4967` reads `pol.label` 0 times and `pol.description` 0 times. At the new pin it reads them 2 and 3 times. The positive control `pol.name` reads once in both trees. Non-ancestry of `f8334f8777` to `db11afd4967` answers exit 1 on this shallow store. Its control leg, `5b2ea17570`, which is older than the fix and known to be in that history, answers exit 0. The fix is also dated after the old pin. - **Not used.** `PermissionPreview` still draws both values, but no production route mounts it for `permission` at this pin. That is the gap the superseded note recorded. It is not cited. ## Producer, framework side (this branch's HEAD) - `packages/rest/src/meta-item-read-gate.ts#createMetaLayeredAnswer` judges each layer through `createMetaItemReadGate`. That gate has no `permission` arm and falls through to `serve(document)` (`:1464`). - The ADR-0106 mask is not applicable to a non-object type: `packages/runtime/src/domains/meta.ts:348` (`resolveObjectMasker`). - `getMetaItemLayered` in `packages/metadata-protocol/src/protocol.ts` serves the stored item raw, folding only object extenders. - `RowLevelSecurityPolicySchema` declares both keys (`rls.zod.ts:281`, `:291`), so a parse keeps them. `examples/app-showcase/src/security/permission-sets.ts#ContributorPermissionSet` authors both on all three of its policies. - **This run did not boot Studio.** The reading is static, closed by hand. objectui#11215's own booted Playwright probe, against the objectstack showcase, found both values on `/apps/studio/metadata/permission/showcase_contributor`, `/apps/setup/metadata/permission/showcase_contributor` and the Studio Access pillar. ## Author-facing effect, measured at the public door Since the dead-verdict ruling, a `dead` row draws `liveness-dead-property` by itself, so this flip silences two warnings. `pnpm --filter @objectstack/example-showcase validate` (`os validate`) at `018e3971db` prints no `liveness: dead` line. With the RLS `label` row forced back to `dead` on disk (through `scripts/ablation-replace.mjs`, restored, blob `2906221a2b91` equal to HEAD), the same command prints `⚠ permission 'showcase_contributor': sets rowLevelSecurity.label but this permission property has no runtime effect (liveness: dead).` The `planned` warning on `externalSharingModel` appears in both runs, which shows the liveness lint ran each time. A warning is not a refusal, so the accept set is unchanged. ## Lint pins re-subjected (a file-surface extension, declared) Two pins in `lint-liveness-properties.test.ts` used these rows as their authorable `dead` sample. One is "the dead and live-elsewhere verdicts warn on their own"; the other is "the authored dead … keys show the dead default hint, not the ledger note". Their own comment prescribes the move: "If either row changes verdict, re-subject this pin to another `dead` row of a type the walk visits". - After the flip and before the test edit, at `e4281055`: `Tests 2 failed | 93 passed (95)`, with `expected undefined to be 'liveness-dead-property'` and `label: expected undefined to be defined`. - The only authorable `dead` rows left in walked types are the `view` container's own `name` and `label`. Every other `dead` row in those types is a `retiredKey` tombstone. Both parse on a container through the shipped `ViewSchema`, neither has an `authorHint`, and the `name` note carries a tracker id, which is the leak the hint pin guards. - The pins now use them. The collection walk still has to reach past index 0, and the `live` keys beside them (`object`, `list.type`, `list.data`, `list.columns`) stay silent. The unused `PermissionSetSchema` import is gone. - At `4d54abd0`: `Tests 95 passed (95)`. - **Reverse verification, one-shot.** I forced the `view` container `label` row to `live` through `scripts/ablation-replace.mjs` in WRAP mode. The anchor went 1 → 0 and the blob `02fa2030` → `3d93a0b8`. Result: `Tests 2 failed | 93 passed (95)`, exactly the two re-subjected pins, both on `label`. The tool restored the file: blob equal to HEAD `02fa2030` and `git diff HEAD` empty. Predicted direction: red. Observed: red. ## Tests and gates, at `018e3971db` - `pnpm --filter @objectstack/spec check:liveness`: exit 0. `symbol anchors: 875 pointer(s) written path#symbol, 875 naming a symbol the cited file contains`, and `state-counts/ is current`. - `@objectstack/lint` whole package (`vitest run --maxWorkers=2`): `Test Files 119 passed (119)`, `Tests 5620 passed (5620)`. `typecheck` exit 0. `tsc -p tsconfig.test.json --listFilesOnly` names the edited test file once, and it is not in `test-typecheck-debt.json`. - `@objectstack/spec`, `local` project, `scripts/liveness/`: `10 passed`, `269 passed`. `repo` project ledger readers (`evidence`, `proof-registry`, `count-shards-merge`): `3 passed`, `92 passed`. This is a declared narrowing to the files that read the ledgers; CI runs both projects whole. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 71 commands. All 71 ran, with exit codes captured before any pipe. 67 exited 0 on the first pass. Four exited 3 (`PREREQUISITE NOT MET`): `check-plugin-teardown-shape --self-test` (pinned commit absent from the shallow store), `check:docs-transcript-drift` (needs `@objectstack/lint` dist), `check:dual-build-cjs-loads` and `check:lean-entry-closure` (need built entry points). Each prerequisite was met (the one commit fetched; lint built; a full turbo build, 72 tasks with 71 cache hits) and each re-ran to exit 0. `--ran` over the final record: `71 derived, 71 run, 0 NOT-MEASURED, 0 UNRUN`. - eslint on the one changed TS file, `--no-inline-config --format json`: 1 file, 0 errors, 0 warnings. `--print-config` resolves rules for it, so it is in the linted population. This repo's config never enables type-aware linting (no `parserOptions.project` or `projectService`), so the diff cannot move any untouched file's verdict. The repo-wide `pnpm lint` is CI's. - **Ships, measured.** `npm pack --dry-run --ignore-scripts --json` in `packages/spec` after the build lists `liveness/permission.json`, `liveness/state-counts/permission.md` and `liveness/README.md`. Positive control: `dist/index.js` is listed. Negative control: `scripts/liveness/check-liveness.mts` is absent. So this is a `patch` changeset, and `skip-changeset` does not apply. - `check:nul-bytes` exit 0, and a control-byte scan of the five edited files finds 0 hits. ## Acceptance notes - **The `view` container `label`** stays `dead` (ruling A). The landing record asked triage to route its enforce-or-remove question. Closing this card does not carry that routing, so it needs its own carrier if triage has not filed one. - **`packages/spec/liveness/validation.json`**: the notes of `label`, `description` and `tags` each end with a dated 2026-09-07 sentence: "PermissionPreview only COUNTS its rowLevelSecurity array … which is why permission.rowLevelSecurity.label / .description / .tags stay dead on this same instrument." That was true of the instrument when written, so it is left as history and is outside this card's surface. - **`PermissionPreview`** is still registered for `permission` and mounted by no production route at the pin. objectui#11215's own notes record this; it is not a defect here. - **The facet is collapsed by default**, so both values appear after one click on the Row-Level Security trigger. That is a UI choice, not a reachability gap. - **Attribution footer.** The body ends with the AGENTS.md session-URL form rather than the harness's attribution reminder; the repo's instruction file outranks the reminder. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 37442d4 commit 0721848

5 files changed

Lines changed: 59 additions & 51 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": patch
3+
---
4+
5+
Liveness ledger: a permission set's row-level security policy `label` and `description` (`rowLevelSecurity[].label` / `.description`) are now `live`, not `dead`. Studio's permission editor shows both on every policy. Ledger data and its generated count shard only.
6+
7+
Clause-②: no
8+
9+
- **What shows them.** These are display keys, so under the ledger's "Designer previews count as consumers" ruling, being shown to a human is the whole of their claimed effect. The Row-Level Security section of the permission editor (`PermissionAdvancedFacets` in objectui) now heads each policy card with the policy's `label` and, beneath it, its `description`, exactly as written. Both rows cite that reader at the `.objectui-sha` pin `89cad75d557`. The registered permission preview also draws both, but no route mounts it for `permission`, so it is not cited.
10+
- **Where the values come from.** Each row names its producer: the `permission` edit page registration and the Studio edit route that mounts it, the editor's `GET /api/v1/meta/permission/:name/layers` read, and this repo's shared layered answer (`createMetaLayeredAnswer`), which serves a permission set whole. The showcase's contributor permission set authors both keys on all three of its policies.
11+
- **Author-facing effect.** `os lint` / `os validate` no longer warn `liveness-dead-property` on a policy that sets `label` or `description`. A warning is not a refusal, so the accept set is unchanged.
12+
- **Still kept.** The re-grade reverses no ADR-0033 decision. Both rows stay docs-shaped annotation, deliberately kept and not `authorWarn`'d.
13+
- The regenerated liveness count is the `liveness/state-counts/permission.md` shard: `permission` has 38 live and 4 dead (was 36 and 6). The `view` container's own `label` stays `dead`.
14+
- ⛔ No schema, parse, `.describe()`, export or accept-set change.

‎packages/lint/src/lint-liveness-properties.test.ts‎

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { afterAll, describe, it, expect } from 'vitest';
44
import { cpSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
55
import { tmpdir } from 'node:os';
66
import { join } from 'node:path';
7-
import { PermissionSetSchema } from '@objectstack/spec/security';
87
import { ViewSchema } from '@objectstack/spec/ui';
98
import {
109
authorWarnedProperties,
@@ -1632,45 +1631,40 @@ describe('a per-type ledger that could not be READ is reported once (#19276)', (
16321631
// These pins test it, against the real ledgers, so a ledger change that leaves
16331632
// either id with no reachable row fails here by name.
16341633
describe('the dead and live-elsewhere verdicts warn on their own (#16094)', () => {
1635-
// `rowLevelSecurity.label` / `.description` are `dead` rows (no mounted
1636-
// surface draws a policy's label or description) that an author really
1637-
// writes: the fixture parses through the shipped `PermissionSetSchema`, so
1638-
// neither key is a tombstone. If either row changes verdict, re-subject this
1639-
// pin to another `dead` row of a type the walk visits; if none is left, the
1640-
// rule id is unreachable again and that is what this pin exists to say.
1641-
const permissionSet = {
1642-
name: 'fx_reader',
1643-
label: 'Fx Reader',
1644-
objects: { fx_account: { allowRead: true, readScope: 'org' } },
1645-
rowLevelSecurity: [
1646-
// The first policy authors neither dead key, so the findings below can
1647-
// only come from the second: the dotted path fans out past index 0.
1648-
{ name: 'fx_any_rows', object: 'fx_account', operation: 'select', using: 'name == current_user.email' },
1649-
{
1650-
name: 'fx_own_rows',
1651-
label: 'Own rows',
1652-
description: 'Readers see their own rows.',
1653-
object: 'fx_account',
1654-
operation: 'select',
1655-
using: 'name == current_user.email',
1656-
},
1657-
],
1658-
};
1634+
// The `view` container's own `name` / `label` are `dead` rows (Studio
1635+
// enumerates view items, never the aggregated container, so nothing draws or
1636+
// keys on them) that an author really writes: the fixture parses through the
1637+
// shipped `ViewSchema`, so neither key is a tombstone. If either row changes
1638+
// verdict, re-subject this pin to another `dead` row of a type the walk
1639+
// visits; if none is left, the rule id is unreachable again and that is what
1640+
// this pin exists to say. (It was re-subjected here when
1641+
// `permission.rowLevelSecurity.label` / `.description` went `live`, #20299.)
1642+
const list = (object: string) => ({
1643+
type: 'grid',
1644+
data: { provider: 'object', object },
1645+
columns: [{ field: 'name' }],
1646+
});
1647+
const views = [
1648+
// The first container authors neither dead key, so the findings below can
1649+
// only come from the second: the collection walk reaches past index 0.
1650+
{ object: 'fx_account', list: list('fx_account') },
1651+
{ name: 'fx_contact', label: 'Contacts', object: 'fx_contact', list: list('fx_contact') },
1652+
];
16591653

16601654
it('the fixture is authorable — it parses through the shipped schema (the keys are not tombstones)', () => {
1661-
expect(PermissionSetSchema.safeParse(permissionSet).success).toBe(true);
1655+
for (const view of views) expect(ViewSchema.safeParse(view).success, view.object).toBe(true);
16621656
});
16631657

16641658
it('END TO END: an authored dead key produces liveness-dead-property, and the live keys beside it stay silent', () => {
1665-
const findings = lintLivenessProperties({ permissions: [permissionSet] });
1666-
expect(ruleOf(findings, 'rowLevelSecurity.label')).toBe(LIVENESS_DEAD_PROPERTY);
1667-
expect(ruleOf(findings, 'rowLevelSecurity.description')).toBe(LIVENESS_DEAD_PROPERTY);
1668-
// `live` is silent — same policy, same ledger load, so this is a verdict
1669-
// and not a walk that never ran.
1670-
for (const live of ['rowLevelSecurity.name', 'rowLevelSecurity.object', 'rowLevelSecurity.operation', 'rowLevelSecurity.using']) {
1659+
const findings = lintLivenessProperties({ views });
1660+
expect(ruleOf(findings, 'name')).toBe(LIVENESS_DEAD_PROPERTY);
1661+
expect(ruleOf(findings, 'label')).toBe(LIVENESS_DEAD_PROPERTY);
1662+
// `live` is silent — same container, same ledger load, so this is a
1663+
// verdict and not a walk that never ran.
1664+
for (const live of ['object', 'list.type', 'list.data', 'list.columns']) {
16711665
expect(ruleOf(findings, live), live).toBeUndefined();
16721666
}
1673-
expect(findings.map((f) => f.where)).toEqual(["permission 'fx_reader'", "permission 'fx_reader'"]);
1667+
expect(findings.map((f) => f.where)).toEqual(["view 'fx_contact'", "view 'fx_contact'"]);
16741668
});
16751669

16761670
// The control the triage notes asked for: a TOMBSTONED key is still refused
@@ -1838,20 +1832,16 @@ describe('the hint a warned row shows an author (#16094, #21096)', () => {
18381832
}
18391833
});
18401834

1841-
it('END TO END: the authored dead RLS keys show the dead default hint, not the ledger note', () => {
1842-
const ledger = JSON.parse(readFileSync(join(shippedLedgerDir(), 'permission.json'), 'utf8'));
1843-
const rls = ledger.props.rowLevelSecurity.children;
1835+
it('END TO END: the authored dead view-container keys show the dead default hint, not the ledger note', () => {
1836+
const ledger = JSON.parse(readFileSync(join(shippedLedgerDir(), 'view.json'), 'utf8'));
18441837
const findings = lintLivenessProperties({
1845-
permissions: [{
1846-
name: 'fx_reader',
1847-
rowLevelSecurity: [{ name: 'p', label: 'Own rows', description: 'Readers see their own rows.', object: 'fx_account' }],
1848-
}],
1838+
views: [{ name: 'fx_contact', label: 'Contacts', object: 'fx_contact' }],
18491839
});
18501840
const deadDefault = checkItemAgainstWarnMap('gadget', { name: 'g1', gizmo: 'x' }, "gadget 'g1'", gizmoEntry({ status: 'dead' }))[0].hint;
1851-
for (const key of ['label', 'description']) {
1852-
const f = findings.find((x) => x.message.includes(`sets \`rowLevelSecurity.${key}\``));
1841+
for (const key of ['name', 'label']) {
1842+
const f = findings.find((x) => x.message.includes(`sets \`${key}\``));
18531843
expect(f, key).toBeDefined();
1854-
expect(f!.hint, key).not.toBe(rls[key].note);
1844+
expect(f!.hint, key).not.toBe(ledger.props[key].note);
18551845
expect(f!.hint, key).toBe(deadDefault);
18561846
}
18571847
});

‎packages/spec/liveness/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ marker where the Notes cell goes, never a guess at what belongs there.
960960
| flow | dead count = **5 tombstone entries**: `active`/`template`/nodes.`outputSchema`/errorHandling.`fallbackNodeId` REMOVED 2026-07-30 (#3896 close-out sweep — `active: false` never stopped a flow, `status` is the enforced lifecycle; faults route via per-node fault edges), plus errorHandling.`retryDelayMs` RENAMED to `backoffMs` 2026-08-04 (#4964). The rename is why the dead column moved while live did not: a rename is a removal on this ledger, so the old spelling is tombstoned (`retiredKey` keeps it in the walked shape) and the new spelling enters as its own `live` row. Read it beside the four above as the one entry here that cost an author nothing — the block was a THIRD encoding of the retry policy #4661 converged, invisible to that pass because it is an anonymous inline block with no exported name, and #4964 spelled its base delay `backoffMs` to match `job.retryPolicy` and a `try_catch` node's `retry`. `description` is the kept docs field — KEPT deliberately, docs-shaped, exempt from enforce-or-remove — and it left the dead set in #20299: the Studio metadata list's default columns and the metadata quick-find draw it for every flow, which for a display key is the whole of the claimed effect (the #7131 ruling above). Still not authorWarn'd |
961961
| action | `type:'form'` CORRECTED to live (objectui ActionRunner.executeForm, #2377); dead `timeout` REMOVED (#2377); `disabled` live since objectui#2863; `undoable` CORRECTED to live (#3714); `shortcut` + `bulkEnabled` REMOVED 2026-07-30 (#3896 close-out sweep — no keydown path dispatches shortcuts; the multi-select toolbar reads the view's bulkActions). **#7367** (PR #7430) adds `description` as an authorable key, `live` on arrival — the only row this type has gained since that sweep. **Commit cae2169cf** makes the dead set three: `execute` joins it, re-classified `live` → `dead` 2026-08-29 with no key added or removed. Its `live` verdict rested on a `.transform` lowering `execute` → `target` that protocol 17 (#3855) removed along with the alias; the key has been a `retiredKey` tombstone since 2026-07-28, so the row stays (the `rls.priority` precedent) while the verdict does not. The rot was invisible to every citation check — the pointer was in range, in the right file, and the file names the key — and the entry carried no `verifiedAt`, so nothing ever re-asked. **#20323** makes the dead set four: `aria` re-classified `live` → `dead` 2026-09-28 and tombstoned (the `rls.priority` precedent again). Its `live` verdict rested on an uncited 「PARTIAL — honored by a few objectui renderers」 note; re-measured at the `.objectui-sha` pin, no surface that renders an action reads an action's `aria`, and each takes the accessible name from the required `label` |
962962
| hook | model-healthy; label/description KEPT deliberately (2026-07-30 sweep) — docs-shaped annotation fields, exempt from enforce-or-remove — and **`live` since #20299**: `hook` has no registered preview, but the Studio metadata list's default columns and the metadata quick-find draw both keys for every hook, which for a display key is the whole of the claimed effect (the #7131 ruling above). Still not authorWarn'd |
963-
| permission | CRUD/FLS/RLS live; dead `contextVariables` REMOVED (ADR-0105 D11 — RLS resolves only the `current_user.*` built-ins plus runtime-staged `rlsMembership` sets). 2026-07-30 security-subset re-verification (all 33 entries `verifiedAt`-stamped): `rowLevelSecurity.enabled` was live-with-wrong-evidence and UNREAD — a disabled policy kept contributing its OR-branch grant; ENFORCED same day in rls-compiler (`getApplicablePolicies`), the `positions` ADR-0049 resolution repeated. `rowLevelSecurity.priority` CORRECTED to dead+authorWarn — semantically void under OR-combination (no conflict exists to order), a REMOVE candidate. `rls.label`/`description`/`tags` CORRECTED to dead (benign display, no consumer in either repo). `tabPermissions` was UNDERSTATED ("only hidden read" → the rank merge reads all four values; me-apps dogfood test exercises it). `allowExport` re-verified TRUE end-to-end (server-side 403 gate, not just the /me projection). `objects.allowRestore`/`allowPurge` REMOVED 2026-08-26 (#12497, ADR-0049 — the `restore`/`purge` ops never existed; the 2026-07-30 'live' verdict cited only the evaluator pre-mapping, retired in the same batch; `retiredKey` tombstones, keys return with M2 per the #1883 ruling). `rowLevelSecurity.tags` REMOVED 2026-09-27 (#20321, ADR-0049 — graded RETIRE by the maintainer's criterion: no mainstream platform tags a row-level policy; a `retiredKey` tombstone, so the row stays `dead` beside `priority`'s) |
963+
| permission | CRUD/FLS/RLS live; dead `contextVariables` REMOVED (ADR-0105 D11 — RLS resolves only the `current_user.*` built-ins plus runtime-staged `rlsMembership` sets). 2026-07-30 security-subset re-verification (all 33 entries `verifiedAt`-stamped): `rowLevelSecurity.enabled` was live-with-wrong-evidence and UNREAD — a disabled policy kept contributing its OR-branch grant; ENFORCED same day in rls-compiler (`getApplicablePolicies`), the `positions` ADR-0049 resolution repeated. `rowLevelSecurity.priority` CORRECTED to dead+authorWarn — semantically void under OR-combination (no conflict exists to order), a REMOVE candidate. `rls.label`/`description`/`tags` CORRECTED to dead (benign display, no consumer in either repo). `tabPermissions` was UNDERSTATED ("only hidden read" → the rank merge reads all four values; me-apps dogfood test exercises it). `allowExport` re-verified TRUE end-to-end (server-side 403 gate, not just the /me projection). `objects.allowRestore`/`allowPurge` REMOVED 2026-08-26 (#12497, ADR-0049 — the `restore`/`purge` ops never existed; the 2026-07-30 'live' verdict cited only the evaluator pre-mapping, retired in the same batch; `retiredKey` tombstones, keys return with M2 per the #1883 ruling). `rowLevelSecurity.tags` REMOVED 2026-09-27 (#20321, ADR-0049 — graded RETIRE by the maintainer's criterion: no mainstream platform tags a row-level policy; a `retiredKey` tombstone, so the row stays `dead` beside `priority`'s). `rowLevelSecurity.label` / `description` RE-GRADED `live` 2026-10-03 (#20299 — the permission editor's Row-Level Security section draws both on each policy card, read at the `.objectui-sha` pin 89cad75d557; still benign display, KEPT, not authorWarn'd), so the dead set is now `priority` and `tags` beside the two `objects.allowRestore` / `allowPurge` tombstones |
964964
| position | (role's ADR-0090 successor) fully live; all 4 `verifiedAt`-stamped 2026-07-30 |
965965
| agent | dead `tenantId` + `planning.strategy`/`allowReplan` REMOVED (#2377); the autonomy tier is experimental no longer — `structuredOutput` (#21277) and `memory` (#20274) flipped `live` on the cloud AI runtime's reading, and `lifecycle` REMOVED 2026-10-02 (#21320; ruled D on objectstack-ai/cloud#2569 — the conversation state machine was parsed and never read; phases are skills with `triggerConditions`, orchestration is Flow, record transitions are the `state_machine` validation rule; the XState `StateMachineSchema` family, which only it still reached, left with it), so no `agent` row is experimental; `knowledge` REMOVED 2026-07-30 (#3896 close-out sweep — declaring sources never scoped retrieval; AIKnowledgeSchema removed with it, the topics→sources rename absorbed pre-release); **#18304** re-classifies `tools` `live` -> `dead` with no key added or removed — the row asserted `live` on a key `agent.zod.ts` had tombstoned in protocol 17 (#3894), and it sat that way from the 2026-06 audit because its citation was EXEMPT from resolution rather than resolved (`packages/services/service-ai/...` matched `FOREIGN_PATH_PREFIXES`; the `cloud` realm marker that replaced it in #13309 is equally unresolvable, so no gate could ever fail on it). The load-bearing evidence is local and re-measurable — the `retiredKey` tombstone plus the `agent-tools-to-skills` strip cover authored and stored input respectively, so nothing can carry a value for any consumer to read; the cloud zero-consumer census (cloud @cb8ee7ff, #13272, 2026-09-15) is attributed, not re-taken. `live-elsewhere` is refused for want of a foreign enforcer, not left undeclared |
966966
| tool | the inert authoring surface is now REMOVED, not merely marked: `category`/`permissions`/`active`/`builtIn` retired 2026-07-30 (#3896 close-out) after `requiresConfirmation` set the precedent (#3715, ADR-0033 §2). `permissions` promised an invocation gate nothing enforced and `active:false` withdrew nothing — false compliance, same shape as rls.enabled. The `.strict()` ToolSchema rejects each retired key with its prescription; the `tool-inert-authoring-keys-removed` conversion strips them from authored sources |

0 commit comments

Comments
 (0)