Skip to content

Commit 116a3ed

Browse files
committed
refactor(metadata-core): drop sys_view_definition's all-six apiMethods whitelist (#3026)
#3745 completed this object's boilerplate CRUD-five whitelist to all six primitives so its batch routes stopped 405-ing. A whitelist naming all six is equivalent to no whitelist — except it stops tracking primitives the enum grows later — so the #3543 audit rule ("delete the equivalent-to-open boilerplate") applies and the declaration goes. No behaviour change: `undefined` resolves to `unrestricted`, whose effective operation set is identical to `restricted` holding all six primitives. Removing it is safe HERE specifically because the object has no `managedBy`: `reconcileManagedApiMethods` (ADR-0103 D3) early-returns on a non-array `apiMethods`, so for a managed object an absent whitelist would take the managed-write backstop with it. That is why the RBAC objects reclaimed by #3745 keep their explicit arrays and this one does not — the two treatments are not inconsistent, they follow the presence of a backstop. The object's test is rewritten to guard the new shape in both directions (whitelist must stay absent; the resolver must report `unrestricted`) and to pin the derived verbs the contract grants it. Verified end-to-end against a real running server (showcase, seeded admin), not only by unit test: 14/14 checks over the live REST surface, including this object's deleteMany reaching the engine rather than the API gate, derived import/export on a whitelist-free object, a tightened object still answering 405, and apiEnabled:false still answering 404 ahead of the method gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CkdX2VCuKfcsFBbvtATe7V
1 parent 307e0fe commit 116a3ed

3 files changed

Lines changed: 53 additions & 25 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@objectstack/metadata-core": patch
3+
---
4+
5+
refactor(metadata-core): drop `sys_view_definition`'s all-six `apiMethods` whitelist (#3026)
6+
7+
#3745 completed this object's boilerplate CRUD-five whitelist to all six
8+
primitives so its batch routes stopped 405-ing. A whitelist naming all six is
9+
equivalent to no whitelist — except it stops tracking primitives the enum grows
10+
later — so the #3543 audit rule applies and the declaration is removed.
11+
12+
No behaviour change: `undefined` resolves to `unrestricted`, whose effective
13+
operation set is identical to `restricted` holding all six.
14+
15+
Removing it is safe HERE specifically because the object has no `managedBy`:
16+
`reconcileManagedApiMethods` (ADR-0103 D3) early-returns on a non-array
17+
`apiMethods`, so for a managed object an absent whitelist would take the
18+
managed-write backstop with it. That is why the RBAC objects reclaimed by #3745
19+
keep their explicit arrays and this one does not.
Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,48 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* #3026 follow-up — `sys_view_definition` must expose the BATCH shape of the
5-
* write verbs it already grants.
4+
* #3026 follow-up — `sys_view_definition` is default-open, and the derivation
5+
* grants it every operation including the batch routes.
66
*
7-
* Since the #3391 P1 contract made the bulk gate `bulk ∧ derived(child)`, a
8-
* boilerplate CRUD-five whitelist (`get,list,create,update,delete`) denies
9-
* `/batch`, `createMany`, `updateMany` and `deleteMany` while leaving the same
10-
* verbs open one record at a time. The companion fix that added the `bulk`
11-
* primitive to explicitly-whitelisted objects covered `platform-objects` only,
12-
* so this one — the sole object carrying a whitelist in `metadata-core` — kept
13-
* the gap.
7+
* The #3391 P1 contract made the bulk gate `bulk ∧ derived(child)`, which turned
8+
* this object's boilerplate CRUD-five whitelist into a silent denial of
9+
* `/batch`, `createMany`, `updateMany` and `deleteMany` while the same verbs
10+
* stayed open one record at a time. #3745 completed the whitelist to all six
11+
* primitives; #3543's audit rule then applies — a whitelist naming all six is
12+
* equivalent to no whitelist while NOT tracking future primitives — so the
13+
* declaration is gone.
1414
*
15-
* Unlike the RBAC objects reclaimed alongside it, this object has no
16-
* `managedBy`, so the ADR-0103 D3 reconciliation never applies and deleting the
17-
* whitelist would be behaviourally equivalent. It stays explicit on purpose: a
18-
* metadata-plane object that is ALSO reachable through the generic data API is
19-
* worth naming its exposed surface rather than inheriting whatever the
20-
* primitive set grows into.
15+
* Deleting it is safe HERE specifically because the object has no `managedBy`:
16+
* `reconcileManagedApiMethods` (ADR-0103 D3) early-returns on a non-array
17+
* `apiMethods`, so for a managed object an absent whitelist would take the
18+
* managed-write backstop with it. That is why the RBAC objects reclaimed by
19+
* #3745 keep their explicit arrays and this one does not.
2120
*/
2221

2322
import { describe, expect, it } from 'vitest';
2423
import { resolveEffectiveApiMethods, isApiOperationAllowed } from '@objectstack/spec/data';
2524
import { SysViewDefinitionObject } from './sys-view-definition.object.js';
2625

27-
describe('sys_view_definition — batch exposure (#3026 / #3391 P1 companion)', () => {
28-
it('grants the bulk primitive alongside its single-record write verbs', () => {
29-
expect(SysViewDefinitionObject.enable?.apiMethods).toContain('bulk');
30-
for (const verb of ['get', 'list', 'create', 'update', 'delete'] as const) {
31-
expect(SysViewDefinitionObject.enable?.apiMethods, `must keep ${verb}`).toContain(verb);
32-
}
26+
describe('sys_view_definition — API exposure (#3026 / #3543 audit)', () => {
27+
it('carries no whitelist, so the resolver reports unrestricted', () => {
28+
// Regression guard both ways: re-adding a whitelist naming all six
29+
// primitives is a no-op that stops tracking future ones, and any narrower
30+
// whitelist silently closes routes that are open today.
31+
expect(SysViewDefinitionObject.enable?.apiMethods).toBeUndefined();
32+
expect(resolveEffectiveApiMethods(SysViewDefinitionObject.enable).mode).toBe('unrestricted');
3333
});
3434

3535
it('admits createMany / updateMany / deleteMany and /batch', () => {
3636
const eff = resolveEffectiveApiMethods(SysViewDefinitionObject.enable);
37-
expect(eff.mode).toBe('restricted');
3837
for (const child of ['create', 'update', 'delete'] as const) {
3938
expect(isApiOperationAllowed(eff, 'bulk', { bulkChild: child }), `batch ${child}`).toBe(true);
4039
}
4140
});
41+
42+
it('derives the data-portability verbs from the primitives', () => {
43+
const eff = resolveEffectiveApiMethods(SysViewDefinitionObject.enable);
44+
for (const op of ['import', 'export', 'upsert', 'aggregate'] as const) {
45+
expect(isApiOperationAllowed(eff, op), op).toBe(true);
46+
}
47+
});
4248
});

packages/metadata-core/src/objects/sys-view-definition.object.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,11 @@ export const SysViewDefinitionObject = ObjectSchema.create({
138138
trackHistory: true,
139139
searchable: false,
140140
apiEnabled: true,
141-
// `bulk` = the batch shape of the verbs above; the gate is `bulk ∧ child`
142-
// (#3391 P1), so omitting it 405s /batch and the *Many routes (#3026).
143-
apiMethods: ['get', 'list', 'create', 'update', 'delete', 'bulk'],
141+
// No `apiMethods` — default-open (#3543 audit). #3745 completed this
142+
// object's whitelist to all six primitives, which is equivalent to no
143+
// whitelist while NOT tracking future primitives. Unlike the RBAC objects
144+
// reclaimed alongside it, this one has no `managedBy`, so there is no
145+
// ADR-0103 D3 managed-write backstop that an explicit array keeps alive —
146+
// nothing argues for keeping the declaration, so it goes.
144147
},
145148
});

0 commit comments

Comments
 (0)