@objectstack/plugin-approvals@16.0.0
Minor Changes
-
e412fb6: feat(approvals): declare file attachments on approve/reject decisions
The declared
approval_approve/approval_rejectactions on
sys_approval_requestgain an optional multi-fileattachmentsparam
(type: 'file',multiple). The console renderstype:'file'action params
through the shared upload widget (objectui ADR-0059) and POSTs the resolved
attachments: string[], so a reviewer can attach supporting files to a
decision through the generic declared-action dialog — letting the approvals
inbox retire its hand-wired attachment composer (objectui#2698).Purely additive metadata: the decision route already forwards
body.attachmentstoApprovalService.decide, and the
sys_approval_action.attachmentscolumn (file, multiple) already persists them
(#3266/#3274). No service or route change. -
8efa395: feat(approvals): server-computed
viewercapability for precise decision-action gatinggetRequest/listRequestsnow attach a per-viewer block —
viewer: { can_act, is_submitter }— computed from the caller's context
(ApprovalRequestRow.viewer):can_act— the caller is a current pending approver (their user id is in the
request's resolvedpending_approverswhile it is stillpending). This is
the same check the decision methods authorize with, so it already reflects
position/team/manager resolution — strictly more accurate than a client-side
identity guess.is_submitter— the caller submitted the request.
The declared decision actions on
sys_approval_requestnow gate on it: approver
actions (approve/reject/reassign/send-back/request-info) use
record.viewer.can_act; submitter levers (remind/recall/resubmit) use
record.viewer.is_submitter. Previously approver actions only trimmed the
non-pending case, so a submitter viewing their own pending request saw buttons
they couldn't use (the backend 403'd); a position-addressed approver could be
wrongly hidden by the old client heuristic. Wherevieweris absent (a row
surfaced outside a service read with a user context), the predicate fails closed. -
3a18b60: feat(approvals): rename the
roleapprover type toorg_membership_level(#3133)ApproverType.rolewas the last platform surface projecting the reserved word
"role" (ADR-0090 D3). It is not covered by D3's better-auth exception: that
exception protects better-auth's ownsys_member.rolecolumn, which we do
not own —ApproverTypeis our own enum, an authoring surface, and D3 mandates
that the projection of that concept is spelledorg_membership_leveland
labelled "organization membership", never "role".The sentence licensing the leak was also false: ADR-0090 D3 claims
sys_member.roleis "already relabelledorg_membership_levelin the platform
projection", butorg_membership_levelexisted nowhere in the codebase and
ADR-0057 D7 lists that relabel under "Deferred (evidence-gated, P4)". The
projection never landed, so the word reached authors.The name manufactured a real, silent failure — "hotcrm class": every other
surface renamed toposition(sys_role,ShareRecipientType.role,
ctx.roles[]), so{ type: 'role', value: 'sales_manager' }reads as the
legacy spelling of a position. It resolves against the membership tier, finds
no member row, falls back to an inertrole:sales_managerliteral, and the
request waits forever on an approver that cannot exist.- spec:
ApproverTypegainsorg_membership_level;rolestays as a
deprecated alias for one window (a published 15.x flow keeps loading) with
DEPRECATED_APPROVER_TYPES+canonicalApproverType()as the single source
for the mapping. Removed in the next major. - plugin-approvals: resolves on the canonical type and warns on the
deprecated spelling. Thetype:valuefallback literal keeps the authored
spelling — storedsys_approval_approverrows andpending_approversslots
from 15.x carryrole:<v>, and rewriting it would orphan them. - lint:
approval-role-not-membership-tier→approval-approver-not-membership-tier
(the rule id carried the reserved word too), plus a new
approval-approver-type-deprecated. The two are mutually exclusive: a bad
value wins, because prescribingorg_membership_levelfor a position name
would be wrong advice — the fix there isposition.
Authoring
type: 'role'keeps working and now says so out loud. Rewrite it as
org_membership_level; if the value is an org position, the fix isposition. - spec:
Patch Changes
-
22013aa: Split the overloaded
managedBy: 'system'bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). Thesystembucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row asbetter-auth/append-onlybut, unlikebetter-auth, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap).Rather than add a new
managedByenum value (which would fall through to fully-editableplatformdefaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances= bucket default +userActions), and engine-owned is defined as asystem/append-onlyobject that grants no write:- Writable set declares
userActions— the RBAC link tables (sys_user_position,sys_user_permission_set,sys_position_permission_set),sys_user_preference,sys_approval_delegation, and the messaging config grids (sys_notification_preference/…_subscription/…_template) now declareuserActions: { create, edit, delete: true }. The affordance is a declaration only — theDelegatedAdminGate/ RLS / permission sets remain the authz. - Engine-owned objects locked to reads —
apiMethods: ['get','list']added where absent (jobs, notifications, approval request/approver/token/action,sys_record_share,sys_automation_run, mail/settings/secret audit, the messaging delivery pipeline).sys_secretis explicitly read-locked (an emptyapiMethodsarray fails open). sys_import_jobstays engine-owned: the REST import route now writes its job rowsisSystem-elevated (attribution preserved via the explicitcreated_bystamp) and the object is locked to['get','list'].- New engine write guard (
assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-ownedsystem/append-onlyobjects, keyed off the resolved affordance;isSystemand context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. reconcileManagedApiMethods(objectql registry) now runs for every managed bucket, not justbetter-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049)./me/permissionsclamp (plugin-hono-server) now clampssystem/append-onlyas well asbetter-auth, so the client hint reflectspermission ∩ guard.
Potentially breaking: a downstream/third-party
systemobject that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. DeclareuserActionsopening the verbs the object legitimately takes from a user context.better-authkeeps plugin-auth's identity write guard unchanged; the row-levelmanaged_byprovenance vocabulary (ADR-0066) is a different axis and is untouched. - Writable set declares
-
62a2117: Split the overloaded
managedBy: 'system'bucket with an explicitengine-ownedvalue (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a newmanagedByvalue would fall through to the fully-editableplatformdefault on deployed Console clients. Both reasons against it are now retired — the server-side write guard /apiMethodsreconciliation //me/permissionsclamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, additively.- New enum value
engine-ownedwith the same all-locked default affordance row assystem(create/import/edit/delete: false,exportCsv: true). It joinsENGINE_OWNED_BUCKETS(the engine write guard) andGUARDED_WRITE_BUCKETS(the/me/permissionsclamp); the guard,reconcileManagedApiMethods, and the clamp mechanisms are unchanged —engine-ownedis an explicit member of the set they already covered by resolved affordance. - 20 objects relabelled
system → engine-owned— the ones the engine owns end to end and that declared no write-openinguserActions(the metadata store, jobs, approval runtime rows, sharing rows,sys_automation_run, the messaging delivery/receipt pipeline,sys_secret, settings). One-line, behaviour-identical per object. - 8 admin/user-writable objects keep
managedBy: 'system'(the RBAC link tables,sys_user_preference,sys_approval_delegation, the messaging config grids) —systemnow reads as "engine-managed schema, writable viauserActions".
Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405
apiMethodsreconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedByis schema metadata) and no code branches on the'system'literal. Retiring the overloadedsystementirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. - New enum value
-
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [e057f42]
-
Updated dependencies [a3823b2]
-
Updated dependencies [bc65105]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [6b51346]
-
Updated dependencies [80273c8]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [dd9f223]
-
Updated dependencies [46e876c]
-
Updated dependencies [7125007]
-
Updated dependencies [5f05de2]
-
Updated dependencies [021ba4c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [06cb319]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [290e2f0]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [ea32ec7]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/platform-objects@16.0.0
- @objectstack/core@16.0.0
- @objectstack/formula@16.0.0
- @objectstack/metadata-core@16.0.0