Skip to content

Commit e7c2211

Browse files
committed
fix(core): type the posture slice's access as unknown, grade the changeset minor
The coverage step reads access.default off each allSchemas entry. Typing the member unknown keeps every call that compiled before compiling; the element type still gains an optional member, a type widening, so the changeset is graded minor. Claude-Session: https://claude.ai/code/session_01Bvd69VPa6puiNzzPUroDBx Co-authored-by: Claude <noreply@anthropic.com>
1 parent 03c9590 commit e7c2211

2 files changed

Lines changed: 15 additions & 4 deletions

File tree

‎.changeset/20083-effective-map-plain-wildcard.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
'@objectstack/core': patch
2+
'@objectstack/core': minor
33
---
44

55
fix(core): the effective object-permission map covers what a plain `'*'` grant covers, so `current_user.can()` agrees with the server for a wall-less org admin (#20083)
@@ -16,7 +16,7 @@ The population it hit: `organization_admin_no_bypass`, which a deployment withou
1616
- only `true` grant bits are copied; a wildcard's `false` or unset bit adds nothing;
1717
- an object the step would add, but whose entry grants no verb on its own, is left out.
1818

19-
The step reads `name` and `access.default` off the `allSchemas` entries, so a direct caller of `buildEffectiveObjectPermissions` passes the registered schemas themselves there, as both in-repo callers do; an entry without `access` reads as public, exactly as the server reads it.
19+
The step reads `name` and `access.default` off the `allSchemas` entries, so the element type of `allSchemas` on `buildEffectiveObjectPermissions`' schema source gains an optional `access?: unknown` member (the package exports no new name for it). That is a type widening only: every call that compiled before still compiles, and a schema literal carrying `access` now does too. A direct caller passes the registered schemas themselves there, as both in-repo callers do; an entry without `access` reads as public, exactly as the server reads it.
2020

2121
**What a reader of `/auth/me/permissions` sees.** For a subject holding a plain wildcard, `objects` gains an entry for every registered public object the wildcard covers that had none, annotated with `apiOperations` by the same rule as every other entry. An entry that was already there may gain `true` bits. Nothing is removed. For a subject holding no plain wildcard — `admin_full_access`, a walled `organization_admin`, `member_default` alone — the response is byte-identical to before. The response shape, its keys and the route are unchanged.
2222

‎packages/core/src/security/effective-object-permissions.ts‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,18 @@ export interface ApiExposureSchemaLike {
184184
*/
185185
export interface ObjectAccessPostureLike {
186186
name?: string;
187-
access?: { default?: unknown } | null;
187+
/**
188+
* The registered schema's own `access` block, passed through as it is. Typed
189+
* `unknown` so that any schema a caller already hands over still type-checks;
190+
* the one read is `access.default === 'private'`.
191+
*/
192+
access?: unknown;
193+
}
194+
195+
/** `access.default === 'private'` off a registered schema — the evaluator's own test. */
196+
function isPrivatePosture(schema: ObjectAccessPostureLike | null | undefined): boolean {
197+
const access = schema?.access;
198+
return typeof access === 'object' && access !== null && (access as { default?: unknown }).default === 'private';
188199
}
189200

190201
/** The `allow*` bits {@link objectPermissionGrants} reads, i.e. every bit a `can()` verb resolves to. */
@@ -251,7 +262,7 @@ function materializePlainWildcardCoverage(
251262
for (const schema of allSchemas) {
252263
const name = schema?.name;
253264
if (!name || name === '*') continue;
254-
if (schema.access?.default === 'private') continue;
265+
if (isPrivatePosture(schema)) continue;
255266
const had = Object.prototype.hasOwnProperty.call(objects, name);
256267
const acc: Record<string, unknown> = had ? objects[name] : {};
257268
let touched = false;

0 commit comments

Comments
 (0)