fix(security)!: ADR-0090 follow-ups — driver tenant wall, batch-insert marshaling, scoped counts, vocabulary leftovers#2745
Merged
Conversation
…t marshaling, scoped counts, vocabulary leftovers Runtime (closes #2734, #2735, #2737): - driver-sql applyTenantScope: NULL organization_id = GLOBAL/platform row — (org = :tenant OR org IS NULL). Fresh deployments' admins saw ZERO rows in sys_position/sys_permission_set/sys_business_unit (Setup Access Control empty) and no first-boot seeds; cross-tenant rows stay hidden as before. - driver-sql bulkCreate: run each row through formatInput/applyWriteColumnMap like create() — raw {lat,lng} objects crashed the SQLite binder and silently failed whole seed batches (accounts/tasks/field-zoo empty). - engine count()/aggregate(): carry the AST on the opCtx so middleware- injected read filters (RLS/OWD) apply — total was an unscoped row-count oracle while records were scoped. Vocabulary (closes #2722, #2723, #2724): - PortalSchema.profiles → positions, with a loud tombstone rejecting the removed key (FROM→TO in the message); showcase Client Portal migrated and now admits a real declared position (client_portal_user). - RLSUserContextSchema.role → positions (dead field; runtime already used positions). API surface unchanged. - sys_record_share.recipient_type 'role' → 'position' + ShareRecipientType contract aligned with the spec zod enum; plugin-sharing translations regenerated (fixes the pre-stale sys_sharing_rule block) with zh/ja labels patched per the generated-file contract. Verified on a fresh `objectstack dev` boot: zero seed failures; admin sees all RBAC tables and seeds on FIRST boot with no workarounds; member total==records; platform-admin wildcard VAMA reads private rows (the old "admin sees less than the auditor" was the tenant wall hiding the member's org-less note). References regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 100 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jul 10, 2026
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.
Closes #2722, #2723, #2724, #2734, #2735, #2737 — every issue filed while dogfooding the ADR-0090 showcase permission zoo (#2739), each root-caused on the live runtime before fixing.
Runtime fixes
#2734 — driver tenant wall hid every global row (Setup Access Control empty on fresh deployments)
applyTenantScope(driver-sql) used strictorganization_id = :tenantId. Every platform bootstrap row (sys_position,sys_permission_set,sys_business_unit) and every pre-org first-boot seed is org-less → any logged-in admin saw ZERO rows, by-id reads 404'd, and the whole RBAC surface was un-administrable over REST. Now:(org = :tenant OR org IS NULL)— a NULL tenant column is a GLOBAL/platform row belonging to no other tenant; rows stamped with a different org stay invisible exactly as before (cross-tenant isolation tests unchanged + 3 new global-row tests).#2735 — bulkCreate skipped write-side marshaling (seed batches silently failed)
The batch insert path (the seed/import common case since #2678) handed raw object values to the SQLite binder ("Wrong API use: tried to bind a value of an unknown type") — showcase accounts/tasks/field-zoo seeded ZERO rows on every fresh boot.
bulkCreatenow mirrorscreate(): per-rowformatInput+applyWriteColumnMap+ timestamp stamp, andformatOutputon the read-back. New regression test withlocation/json/arrayfields.#2737 —
totalunscoped (row-count oracle) + "admin sees less than the auditor"engine.count()/aggregate()built a LOCAL ast inside the executor, discarding the RLS/OWD filters the security/sharing middlewares inject intoopCtx.ast.where→GET /data/:objectreturned scopedrecordswith a RAW-tabletotal. Both now carry the ast on the opCtx exactly likefind()(4 new tests asserting the driver receives the scoped where). The second observation (platform admin's wildcard VAMA "not widening" private reads) turned out to be the #2734 tenant wall hiding the member's org-less note — with the wall fixed, the admin correctly sees all private rows via the superuser wildcard, no evaluator change needed.Vocabulary leftovers (one-step renames, no aliases)
profiles#2723PortalSchema.profiles→positions(D2 removal miss). The removed key is a loud tombstone — authoringprofilesfails with the FROM→TO prescription instead of silently stripping an admission gate. Showcase Client Portal migrated; it now admits a REAL declared position (client_portal_user, added to the zoo — external portal audience for the D11externalSharingModeldial).rolefield #2722RLSUserContextSchema.role→positions(D3 miss). Dead field — the RLS compiler already resolvescurrent_user.positionsvia its own context type.check:api-surfaceunchanged ✓.rolerecipient enum + "角色及下级" translations #2724sys_record_share.recipient_type'role'→'position'+ theShareRecipientTypecontract type aligned with the (already-migrated) spec zod enum. No data migration needed: no reader expands non-userrecord-share rows (rules materialize per-user grants), so legacy'role'rows were inert. plugin-sharing translations regenerated viaos i18n extract— also fixing the pre-stalesys_sharing_ruleoptions block — with zh-CN/ja-JP labels patched in place per the generated-file contract (业务单元及下级 / ビジネスユニットと下位階層).Verification
showcase-d7-default-profilewas already migrated in feat(showcase,docs): ADR-0090 permission-model zoo + full docs alignment #2739).check:api-surfaceunchanged; spec json-schema +references/security/rls.mdx+references/ui/portal.mdxregenerated.objectstack dev --seed-adminboot, browser-verified with zero workarounds (previously required sqlite org-stamp hacks):total 1 | records 1, auditor/admintotal 2 | records 2.🤖 Generated with Claude Code