Skip to content

Commit f56cc59

Browse files
os-zhuangclaude
andcommitted
fix(platform-objects): allow import/export on sys_business_unit_member (#3025 / #3391 P0)
Completes the #3025 point-fix. #3392 added import/export to sys_business_unit but left the sibling membership table on a CRUD-only whitelist, so the HRIS org-tree sync scenario (units + memberships imported together) still 405'd on the membership import/export path. #3391's P0 checklist pairs both tables; this is the half #3392 missed. - sys_business_unit_member enable.apiMethods gains 'import','export'. - Reconcile-safe: import/export are not in MANAGED_WRITE_VERB_AFFORDANCE, so reconcileManagedApiMethods (managedBy:'platform') never strips them — the declared whitelist reaches apiAccessDenialFromEnable intact. - Regression test locks import/export + CRUD, proven red-before-green. Transitional per #3391 P2 (derived import/export mapping reclaims both objects). Refs #3025, #3391. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b20201f commit f56cc59

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@objectstack/platform-objects": patch
3+
---
4+
5+
fix(platform-objects): allow import/export on sys_business_unit_member (#3025 / #3391 P0)
6+
7+
Completes the #3025 point-fix. #3392 unblocked `sys_business_unit`'s Import/Export
8+
buttons (405 `OBJECT_API_METHOD_NOT_ALLOWED`) by adding `'import'`/`'export'` to its
9+
`enable.apiMethods` whitelist, but the HRIS org-tree sync scenario imports **two
10+
tables together** — the units *and* their memberships — and the sibling
11+
`sys_business_unit_member` was left on the CRUD-only whitelist, so the membership
12+
Import/Export path still 405'd. #3391's P0 checklist pairs both tables; this is the
13+
half #3392 missed.
14+
15+
- `packages/platform-objects/src/identity/sys-business-unit-member.object.ts`:
16+
`apiMethods` gains `'import'`, `'export'`. Import reuses the object's
17+
already-granted create/update affordances; export is a bulk read.
18+
- Reconcile-safe: the object is `managedBy:'platform'`, but
19+
`reconcileManagedApiMethods` only strips generic write verbs
20+
(`create/update/upsert/delete/purge``MANAGED_WRITE_VERB_AFFORDANCE`). It never
21+
touches `import`/`export`, so the declared whitelist reaches the REST gate intact
22+
(no false-green: the static whitelist the regression test asserts IS what
23+
`apiAccessDenialFromEnable` enforces at runtime).
24+
- Regression test (`platform-objects.test.ts`) locks `import`/`export` presence and
25+
CRUD retention. Proven red-before-green: reverting the object edit fails with
26+
`expected [...] to include 'import'`.
27+
28+
Transitional: #3391 P2 replaces per-object `import`/`export` declarations with a
29+
single derived mapping (import ⊆ create/update, export ⊆ list) and reclaims the
30+
explicit entries on both business-unit objects together.
31+
32+
Refs #3025, #3391.

packages/platform-objects/src/identity/sys-business-unit-member.object.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,14 @@ export const SysBusinessUnitMember = ObjectSchema.create({
101101
trackHistory: true,
102102
searchable: true,
103103
apiEnabled: true,
104-
apiMethods: ['get', 'list', 'create', 'update', 'delete'],
104+
// import/export complete the HRIS org-tree sync scenario: the units
105+
// (sys_business_unit, #3025/#3392) and their memberships are imported
106+
// together as one bulk operation. Import reuses the already-granted
107+
// create/update affordances; export is a bulk read. Transitional — #3391
108+
// P2 derives these from create/update|list and reclaims the explicit
109+
// entries. Reconcile-safe: import/export are not generic write verbs, so
110+
// reconcileManagedApiMethods (managedBy:'platform') never strips them.
111+
apiMethods: ['get', 'list', 'create', 'update', 'delete', 'import', 'export'],
105112
trash: true,
106113
mru: false,
107114
},

packages/platform-objects/src/platform-objects.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { describe, expect, it } from 'vitest';
44
import {
55
SysAccount,
66
SysApiKey,
7+
SysBusinessUnitMember,
78
SysDeviceCode,
89
SysInvitation,
910
SysJwks,
@@ -128,6 +129,37 @@ describe('@objectstack/platform-objects', () => {
128129
});
129130
});
130131

132+
describe('data-portability api whitelist (#3025 / #3391 P0)', () => {
133+
// HRIS org-tree sync imports TWO tables together: the units
134+
// (sys_business_unit) AND the memberships (sys_business_unit_member).
135+
// #3025/#3392 unblocked the unit table; this locks the sibling membership
136+
// table, which #3391's P0 checklist pairs with it. Both carry a restrictive
137+
// apiMethods whitelist, so import/export must be listed explicitly or the
138+
// REST data face 405s (`OBJECT_API_METHOD_NOT_ALLOWED`, via
139+
// apiAccessDenialFromEnable in rest-server.ts).
140+
//
141+
// Transitional: #3391 P2 replaces per-object import/export declarations with
142+
// a single derived mapping (import ⊆ create/update, export ⊆ list). When
143+
// that lands, the explicit entries — and this guard — are reclaimed together.
144+
//
145+
// Reconcile-safe: sys_business_unit_member is managedBy:'platform', but
146+
// reconcileManagedApiMethods only strips generic WRITE verbs
147+
// (create/update/upsert/delete/purge — MANAGED_WRITE_VERB_AFFORDANCE) whose
148+
// affordance is absent. It never touches import/export, so the declared
149+
// whitelist reaches the runtime gate intact (no false-green: the static
150+
// whitelist here IS what the gate enforces).
151+
it('sys_business_unit_member allows import/export and keeps CRUD', () => {
152+
const methods = SysBusinessUnitMember.enable?.apiMethods ?? [];
153+
expect(methods).toContain('import');
154+
expect(methods).toContain('export');
155+
// The data-portability additions must not drop the CRUD affordances the
156+
// membership grid + HRIS upsert depend on.
157+
for (const verb of ['get', 'list', 'create', 'update', 'delete']) {
158+
expect(methods).toContain(verb);
159+
}
160+
});
161+
});
162+
131163
describe('sysadmin row actions', () => {
132164
// Setup-App admins must be able to drive the access-control lifecycle
133165
// without dropping to SQL. These assertions lock in the high-traffic

0 commit comments

Comments
 (0)