Repository navigation
Commit 0721848
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
- .changeset
- packages
- lint/src
- spec/liveness
- state-counts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
8 | 7 | | |
9 | 8 | | |
10 | 9 | | |
| |||
1632 | 1631 | | |
1633 | 1632 | | |
1634 | 1633 | | |
1635 | | - | |
1636 | | - | |
1637 | | - | |
1638 | | - | |
1639 | | - | |
1640 | | - | |
1641 | | - | |
1642 | | - | |
1643 | | - | |
1644 | | - | |
1645 | | - | |
1646 | | - | |
1647 | | - | |
1648 | | - | |
1649 | | - | |
1650 | | - | |
1651 | | - | |
1652 | | - | |
1653 | | - | |
1654 | | - | |
1655 | | - | |
1656 | | - | |
1657 | | - | |
1658 | | - | |
| 1634 | + | |
| 1635 | + | |
| 1636 | + | |
| 1637 | + | |
| 1638 | + | |
| 1639 | + | |
| 1640 | + | |
| 1641 | + | |
| 1642 | + | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
1659 | 1653 | | |
1660 | 1654 | | |
1661 | | - | |
| 1655 | + | |
1662 | 1656 | | |
1663 | 1657 | | |
1664 | 1658 | | |
1665 | | - | |
1666 | | - | |
1667 | | - | |
1668 | | - | |
1669 | | - | |
1670 | | - | |
| 1659 | + | |
| 1660 | + | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
1671 | 1665 | | |
1672 | 1666 | | |
1673 | | - | |
| 1667 | + | |
1674 | 1668 | | |
1675 | 1669 | | |
1676 | 1670 | | |
| |||
1838 | 1832 | | |
1839 | 1833 | | |
1840 | 1834 | | |
1841 | | - | |
1842 | | - | |
1843 | | - | |
| 1835 | + | |
| 1836 | + | |
1844 | 1837 | | |
1845 | | - | |
1846 | | - | |
1847 | | - | |
1848 | | - | |
| 1838 | + | |
1849 | 1839 | | |
1850 | 1840 | | |
1851 | | - | |
1852 | | - | |
| 1841 | + | |
| 1842 | + | |
1853 | 1843 | | |
1854 | | - | |
| 1844 | + | |
1855 | 1845 | | |
1856 | 1846 | | |
1857 | 1847 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
960 | 960 | | |
961 | 961 | | |
962 | 962 | | |
963 | | - | |
| 963 | + | |
964 | 964 | | |
965 | 965 | | |
966 | 966 | | |
| |||
0 commit comments