fix(plugin-security): project env-scope permission-set edits onto sys_permission_set (#2857)#2867
Merged
Merged
Conversation
…_permission_set (#2857) An environment-scope save('permission', ...) writes only the sys_metadata overlay; the metadata->sys_permission_set projection ran only at boot / on publish (and publish refuses env-authored rows), so the queryable record the admin/Setup surface reads went stale while the layered read showed the edit. Adds the environment door: subscribeEnvPermissionProjection hooks the protocol's post-persistence onMetadataMutation choke point; on an active permission save it re-reads the fresh effective body via the layered read and upsertEnvPermissionSet projects the six facet columns onto the record. Ownership is decided by the RECORD's managed_by (env-authored rows only; package records keep their declared baseline). Mirrors authored-translation-sync's mutation-listener pattern. Tested: 21 unit tests (projection for all six facets, env/package guards, the _packageId-provenance + direct-body-shape regressions, and the mutation wiring via a mock protocol); no regression (153 plugin-security tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
4 tasks
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes the split-brain where an environment-scope permission-set edit updates the metadata overlay but not the queryable
sys_permission_setrecord. Fixes #2857.Why
A
sys_permission_sethas two representations:client.save('permission', …)/client.layeredread-write (authoritative; enforcement + ADR-0090 explain read it)./api/v1/data/sys_permission_set, snake_case JSON-string columns, what the admin/Setup surface reads.The camelCase→snake projection (
toRowFields/upsertPackagePermissionSet) runs only at boot and on publish (the package door), and the publish path refuses env-authored rows. So an env-scopesaveMetaItem('permission', …)persists thesys_metadataoverlay (the layered read reflects it) but never touches the record → the admin surface goes stale. Enforcement stays correct (it reads the metadata), so this is display-freshness, but it breaks the loop for the objectui pure-model work (objectstack-ai/objectui#2403: Studio designs → Setup shows read-only).How
Adds the environment door, mirroring the existing
authored-translation-syncmutation-listener pattern:subscribeEnvPermissionProjection(protocol, ql, logger)— hooks the protocol's post-persistenceonMetadataMutationchoke point (wired once inSecurityPlugin.runBootstrap, guarded sorunBootstrap's re-run doesn't double-subscribe).projectEnvPermissionOnMutation— on an active (non-draft)permissionevent, re-reads the fresh effective body viagetMetaItemLayered(the boot-cachedMetadataManagerregistry would hand back a stale declared body for a seeded set), tolerating both the layered envelope ({effective|code}) and a direct body.upsertEnvPermissionSet— projects the six facets (objects→object_permissions, …,adminScope→admin_scope). Ownership is decided by the record'smanaged_by, never the body: the layered read stamps_packageIdprovenance on env-authored sets too, so only the record can distinguish the doors. Env-authored rows (platform/user/absent) are projected; apackage-owned record's baseline is left to boot re-seed / publish.Storage columns unchanged.
Tested
bootstrap-declared-permissions.test.ts): projection for all six facets; env upsert vs package-record refusal; the_packageId-provenance regression (record decides, not body); the direct-body-shape regression (getMetaItemLayeredcan return the body directly — a?? nullfallback would silently drop it); and the mutation wiring via a mock protocol (envelope + direct shapes, draft/non-permission skip, no-onMetadataMutation→ no-op).plugin-securitytests pass.I could not get a clean live end-to-end (start backend → env
save→ assert the record refreshes) in my local isolated worktree backend:require.resolve('@objectstack/plugin-security')fails from the example app, and the dev server loaded a staleplugin-securityvia pnpm/worktree resolution rather than my rebuiltdist, so the running backend never had this code (a marker file placed at the subscriber's first line never appeared across 7 rebuild/restart attempts). This is a worktree/pnpm-resolution artifact of my local setup, not a code issue — the projection + wiring logic is deterministically unit-tested above, and the wiring mirrors the provenauthored-translation-synclistener. Please confirm the live projection under CI / a clean install. (The two bugs the unit tests caught — the over-eager_packageIdguard and the dropped direct-body — were found during this debugging, so it was productive.)Refs
🤖 Generated with Claude Code