Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .changeset/business-unit-member-import-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
"@objectstack/platform-objects": patch
---

fix(platform-objects): allow import/export on sys_business_unit_member (#3025 / #3391 P0)

Completes the #3025 point-fix. #3392 unblocked `sys_business_unit`'s Import/Export
buttons (405 `OBJECT_API_METHOD_NOT_ALLOWED`) by adding `'import'`/`'export'` to its
`enable.apiMethods` whitelist, but the HRIS org-tree sync scenario imports **two
tables together** — the units *and* their memberships — and the sibling
`sys_business_unit_member` was left on the CRUD-only whitelist, so the membership
Import/Export path still 405'd. #3391's P0 checklist pairs both tables; this is the
half #3392 missed.

- `packages/platform-objects/src/identity/sys-business-unit-member.object.ts`:
`apiMethods` gains `'import'`, `'export'`. Import reuses the object's
already-granted create/update affordances; export is a bulk read.
- Reconcile-safe: the object is `managedBy:'platform'`, but
`reconcileManagedApiMethods` only strips generic write verbs
(`create/update/upsert/delete/purge` — `MANAGED_WRITE_VERB_AFFORDANCE`). It never
touches `import`/`export`, so the declared whitelist reaches the REST gate intact
(no false-green: the static whitelist the regression test asserts IS what
`apiAccessDenialFromEnable` enforces at runtime).
- Regression test (`platform-objects.test.ts`) locks `import`/`export` presence and
CRUD retention. Proven red-before-green: reverting the object edit fails with
`expected [...] to include 'import'`.

Transitional: #3391 P2 replaces per-object `import`/`export` declarations with a
single derived mapping (import ⊆ create/update, export ⊆ list) and reclaims the
explicit entries on both business-unit objects together.

Refs #3025, #3391.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,14 @@ export const SysBusinessUnitMember = ObjectSchema.create({
trackHistory: true,
searchable: true,
apiEnabled: true,
apiMethods: ['get', 'list', 'create', 'update', 'delete'],
// import/export complete the HRIS org-tree sync scenario: the units
// (sys_business_unit, #3025/#3392) and their memberships are imported
// together as one bulk operation. Import reuses the already-granted
// create/update affordances; export is a bulk read. Transitional — #3391
// P2 derives these from create/update|list and reclaims the explicit
// entries. Reconcile-safe: import/export are not generic write verbs, so
// reconcileManagedApiMethods (managedBy:'platform') never strips them.
apiMethods: ['get', 'list', 'create', 'update', 'delete', 'import', 'export'],
trash: true,
mru: false,
},
Expand Down
22 changes: 22 additions & 0 deletions packages/platform-objects/src/platform-objects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SysAccount,
SysApiKey,
SysBusinessUnit,
SysBusinessUnitMember,
SysDeviceCode,
SysInvitation,
SysJwks,
Expand Down Expand Up @@ -246,6 +247,27 @@ describe('@objectstack/platform-objects', () => {
expect(SysBusinessUnit.enable?.apiMethods).toContain(verb);
}
});

it('sys_business_unit_member allows import/export and keeps CRUD (#3391 P0)', () => {
// HRIS org-tree sync imports TWO tables together — the units (above) AND
// their memberships. The sibling membership table carries the same kind
// of restrictive whitelist, so it needs import/export too or the
// membership import path 405s (OBJECT_API_METHOD_NOT_ALLOWED). #3391's P0
// checklist pairs both tables; this is the half #3392 did not cover.
const methods = SysBusinessUnitMember.enable?.apiMethods ?? [];
expect(methods).toContain('import');
expect(methods).toContain('export');
// CRUD must remain — import writes reuse create/update; the membership
// grid depends on the rest.
for (const verb of ['get', 'list', 'create', 'update', 'delete'] as const) {
expect(methods).toContain(verb);
}
// Transitional: #3391 P2 derives import/export (import ⊆ create/update,
// export ⊆ list) and reclaims both objects' explicit entries together.
// Reconcile-safe: import/export are not generic write verbs, so
// reconcileManagedApiMethods (managedBy:'platform') never strips them —
// this static whitelist IS what apiAccessDenialFromEnable enforces.
});
});

describe('SETUP_APP (ADR-0029 D7 shell)', () => {
Expand Down