@objectstack/plugin-approvals 16.0.0-rc.0 → 16.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -855,7 +855,7 @@ var SysApprovalRequest = ObjectSchema.create({
855
855
  pluralLabel: "Approval Requests",
856
856
  icon: "inbox",
857
857
  isSystem: true,
858
- managedBy: "system",
858
+ managedBy: "engine-owned",
859
859
  description: "Live approval instance tracked per submission",
860
860
  displayNameField: "id",
861
861
  nameField: "id",
@@ -1043,8 +1043,12 @@ var SysApprovalRequest = ObjectSchema.create({
1043
1043
  // (and their params) ship as metadata, not as hand-written buttons. Each
1044
1044
  // targets the existing approvals REST route; `{id}` resolves from the row
1045
1045
  // and `actorId` defaults to the caller server-side. The service remains the
1046
- // authority on who may act (pending-approver check) — `visible` only trims
1047
- // the obvious non-pending case.
1046
+ // authority on who may act; `visible` gates on the server-computed
1047
+ // per-viewer block (#3310): approver actions on `record.viewer.can_act`
1048
+ // (the caller is a current pending approver — same check the service
1049
+ // authorizes a decision with, so position/team approvers resolve correctly),
1050
+ // submitter actions on `record.viewer.is_submitter`. `viewer` is attached by
1051
+ // getRequest/listRequests; where it is absent the predicate fails closed.
1048
1052
  actions: [
1049
1053
  {
1050
1054
  name: "approval_approve",
@@ -1054,9 +1058,13 @@ var SysApprovalRequest = ObjectSchema.create({
1054
1058
  method: "POST",
1055
1059
  target: "/api/v1/approvals/requests/{id}/approve",
1056
1060
  params: [
1057
- { name: "comment", label: "Comment", type: "textarea", required: false }
1061
+ { name: "comment", label: "Comment", type: "textarea", required: false },
1062
+ // Decision attachments (#3266). The console renders `type:'file'` params
1063
+ // through the shared upload widget and POSTs the resolved `attachments:
1064
+ // string[]`; the decision route persists them on `sys_approval_action`.
1065
+ { name: "attachments", label: "Attachments", type: "file", multiple: true, required: false }
1058
1066
  ],
1059
- visible: 'record.status == "pending"',
1067
+ visible: "record.viewer.can_act",
1060
1068
  locations: ["record_section", "list_item"],
1061
1069
  successMessage: "Approved.",
1062
1070
  refreshAfter: true
@@ -1069,9 +1077,10 @@ var SysApprovalRequest = ObjectSchema.create({
1069
1077
  method: "POST",
1070
1078
  target: "/api/v1/approvals/requests/{id}/reject",
1071
1079
  params: [
1072
- { name: "comment", label: "Comment", type: "textarea", required: false }
1080
+ { name: "comment", label: "Comment", type: "textarea", required: false },
1081
+ { name: "attachments", label: "Attachments", type: "file", multiple: true, required: false }
1073
1082
  ],
1074
- visible: 'record.status == "pending"',
1083
+ visible: "record.viewer.can_act",
1075
1084
  confirmText: "Reject this request? A rejection is final for every approver.",
1076
1085
  locations: ["record_section", "list_item"],
1077
1086
  successMessage: "Rejected.",
@@ -1093,15 +1102,15 @@ var SysApprovalRequest = ObjectSchema.create({
1093
1102
  { field: "submitter_id", name: "to", label: "New approver", required: true, helpText: "User to hand this step to" },
1094
1103
  { name: "comment", label: "Comment", type: "textarea", required: false }
1095
1104
  ],
1096
- visible: 'record.status == "pending"',
1105
+ visible: "record.viewer.can_act",
1097
1106
  locations: ["record_section"],
1098
1107
  successMessage: "Reassigned.",
1099
1108
  refreshAfter: true
1100
1109
  },
1101
1110
  // ── Approver secondary decisions ────────────────────────────────
1102
1111
  // Send back for revision / request more info (ADR-0044). Both are approver
1103
- // actions on a pending request; the service is the authority on who may act,
1104
- // so `visible` only trims the non-pending case (matching approve/reject).
1112
+ // actions, so `visible` gates on `record.viewer.can_act` (a current pending
1113
+ // approver) same as approve/reject. The service stays the authority.
1105
1114
  {
1106
1115
  name: "approval_send_back",
1107
1116
  label: "Send back",
@@ -1112,7 +1121,7 @@ var SysApprovalRequest = ObjectSchema.create({
1112
1121
  params: [
1113
1122
  { name: "comment", label: "Reason", type: "textarea", required: false }
1114
1123
  ],
1115
- visible: 'record.status == "pending"',
1124
+ visible: "record.viewer.can_act",
1116
1125
  locations: ["record_section"],
1117
1126
  successMessage: "Sent back for revision.",
1118
1127
  refreshAfter: true
@@ -1127,17 +1136,17 @@ var SysApprovalRequest = ObjectSchema.create({
1127
1136
  params: [
1128
1137
  { name: "comment", label: "What do you need?", type: "textarea", required: true }
1129
1138
  ],
1130
- visible: 'record.status == "pending"',
1139
+ visible: "record.viewer.can_act",
1131
1140
  locations: ["record_section"],
1132
1141
  successMessage: "Information requested.",
1133
1142
  refreshAfter: true
1134
1143
  },
1135
1144
  // ── Submitter continuity actions ────────────────────────────────
1136
1145
  // Remind / recall (pending) and resubmit / recall (returned). These are the
1137
- // submitter's own levers, so `visible` gates on `submitter_id == ctx.user.id`
1138
- // the current user is exposed via the console's predicate scope. The
1139
- // service re-checks ownership; the predicate keeps a non-submitter from ever
1140
- // seeing a button they cannot use.
1146
+ // submitter's own levers, so `visible` gates on `record.viewer.is_submitter`
1147
+ // (server-computed on the current viewer). The service re-checks ownership;
1148
+ // the predicate keeps a non-submitter from ever seeing a button they cannot
1149
+ // use.
1141
1150
  {
1142
1151
  name: "approval_remind",
1143
1152
  label: "Send reminder",
@@ -1148,7 +1157,7 @@ var SysApprovalRequest = ObjectSchema.create({
1148
1157
  params: [
1149
1158
  { name: "comment", label: "Note", type: "textarea", required: false }
1150
1159
  ],
1151
- visible: 'record.status == "pending" && record.submitter_id == ctx.user.id',
1160
+ visible: 'record.status == "pending" && record.viewer.is_submitter',
1152
1161
  locations: ["record_section"],
1153
1162
  successMessage: "Reminder sent.",
1154
1163
  refreshAfter: true
@@ -1165,7 +1174,7 @@ var SysApprovalRequest = ObjectSchema.create({
1165
1174
  ],
1166
1175
  // Recall applies while the request is live for the submitter — pending
1167
1176
  // (withdraw) or returned (abandon the revision instead of resubmitting).
1168
- visible: '(record.status == "pending" || record.status == "returned") && record.submitter_id == ctx.user.id',
1177
+ visible: '(record.status == "pending" || record.status == "returned") && record.viewer.is_submitter',
1169
1178
  confirmText: "Recall this request? Approvers can no longer act on it and the record is unlocked.",
1170
1179
  locations: ["record_section"],
1171
1180
  successMessage: "Recalled.",
@@ -1181,7 +1190,7 @@ var SysApprovalRequest = ObjectSchema.create({
1181
1190
  params: [
1182
1191
  { name: "comment", label: "What changed?", type: "textarea", required: false }
1183
1192
  ],
1184
- visible: 'record.status == "returned" && record.submitter_id == ctx.user.id',
1193
+ visible: 'record.status == "returned" && record.viewer.is_submitter',
1185
1194
  locations: ["record_section"],
1186
1195
  successMessage: "Resubmitted.",
1187
1196
  refreshAfter: true
@@ -1319,7 +1328,7 @@ var SysApprovalApprover = ObjectSchema3.create({
1319
1328
  pluralLabel: "Approval Approvers",
1320
1329
  icon: "users",
1321
1330
  isSystem: true,
1322
- managedBy: "system",
1331
+ managedBy: "engine-owned",
1323
1332
  description: "Normalized pending-approver rows for indexed inbox queries",
1324
1333
  displayNameField: "id",
1325
1334
  nameField: "id",
@@ -3328,6 +3337,7 @@ var _ApprovalService = class _ApprovalService {
3328
3337
  const rows = await this.engine.find("sys_approval_request", findOpts);
3329
3338
  const list = Array.isArray(rows) ? rows.map(rowFromRequest) : [];
3330
3339
  await this.enrichRows(list);
3340
+ this.attachViewers(list, context);
3331
3341
  return list;
3332
3342
  }
3333
3343
  async countRequests(filter, context) {
@@ -3369,6 +3379,7 @@ var _ApprovalService = class _ApprovalService {
3369
3379
  await this.enrichRows([row]);
3370
3380
  await this.attachFlowSteps(row);
3371
3381
  await this.attachDecisionProgress(row, rows[0]);
3382
+ this.attachViewers([row], context);
3372
3383
  return row;
3373
3384
  }
3374
3385
  /**
@@ -3416,6 +3427,25 @@ var _ApprovalService = class _ApprovalService {
3416
3427
  } catch {
3417
3428
  }
3418
3429
  }
3430
+ /**
3431
+ * Attach the per-viewer capability block (#3310) from the caller's context.
3432
+ * `can_act` mirrors the exact authorization the decision methods enforce — the
3433
+ * caller's user id is in the resolved `pending_approvers` while the request is
3434
+ * still `pending` (position/team/manager approvers are already resolved to
3435
+ * concrete user ids at open time, so a plain membership test is faithful).
3436
+ * `is_submitter` is a straight owner check. System/tokenless contexts get a
3437
+ * both-false block. Cheap + synchronous — safe on list reads.
3438
+ */
3439
+ attachViewers(rows, context) {
3440
+ const uid2 = context?.userId != null ? String(context.userId) : null;
3441
+ for (const row of rows) {
3442
+ const pending = row.pending_approvers ?? [];
3443
+ row.viewer = {
3444
+ can_act: row.status === "pending" && !!uid2 && pending.includes(uid2),
3445
+ is_submitter: !!uid2 && row.submitter_id != null && String(row.submitter_id) === uid2
3446
+ };
3447
+ }
3448
+ }
3419
3449
  /**
3420
3450
  * Derive approval-step progress from the owning flow's graph (single-read
3421
3451
  * enrichment only — list reads skip it). Walks from the start node
@@ -3484,7 +3514,7 @@ var SysApprovalToken = ObjectSchema5.create({
3484
3514
  pluralLabel: "Approval Action Tokens",
3485
3515
  icon: "key",
3486
3516
  isSystem: true,
3487
- managedBy: "system",
3517
+ managedBy: "engine-owned",
3488
3518
  description: "Single-use tokens behind actionable approval links",
3489
3519
  displayNameField: "id",
3490
3520
  nameField: "id",