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

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.d.ts CHANGED
@@ -220,7 +220,7 @@ declare const SysApprovalRequest: Omit<{
220
220
  pluralLabel?: string | undefined;
221
221
  description?: string | undefined;
222
222
  icon?: string | undefined;
223
- managedBy?: "platform" | "system" | "config" | "append-only" | "better-auth" | undefined;
223
+ managedBy?: "platform" | "system" | "config" | "engine-owned" | "append-only" | "better-auth" | undefined;
224
224
  ownership?: "none" | "org" | "user" | undefined;
225
225
  userActions?: {
226
226
  create?: boolean | undefined;
@@ -418,7 +418,7 @@ declare const SysApprovalRequest: Omit<{
418
418
  }[] | undefined;
419
419
  filter?: {
420
420
  field: string;
421
- operator: string;
421
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
422
422
  value?: string | number | boolean | (string | number)[] | null | undefined;
423
423
  }[] | undefined;
424
424
  description?: string | undefined;
@@ -577,7 +577,7 @@ declare const SysApprovalRequest: Omit<{
577
577
  view?: string | undefined;
578
578
  filter?: {
579
579
  field: string;
580
- operator: string;
580
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
581
581
  value?: string | number | boolean | (string | number)[] | null | undefined;
582
582
  }[] | undefined;
583
583
  order?: number | undefined;
@@ -833,7 +833,7 @@ declare const SysApprovalRequest: Omit<{
833
833
  readonly pluralLabel: "Approval Requests";
834
834
  readonly icon: "inbox";
835
835
  readonly isSystem: true;
836
- readonly managedBy: "system";
836
+ readonly managedBy: "engine-owned";
837
837
  readonly description: "Live approval instance tracked per submission";
838
838
  readonly displayNameField: "id";
839
839
  readonly nameField: "id";
@@ -3349,8 +3349,14 @@ declare const SysApprovalRequest: Omit<{
3349
3349
  readonly label: "Comment";
3350
3350
  readonly type: "textarea";
3351
3351
  readonly required: false;
3352
+ }, {
3353
+ readonly name: "attachments";
3354
+ readonly label: "Attachments";
3355
+ readonly type: "file";
3356
+ readonly multiple: true;
3357
+ readonly required: false;
3352
3358
  }];
3353
- readonly visible: "record.status == \"pending\"";
3359
+ readonly visible: "record.viewer.can_act";
3354
3360
  readonly locations: ["record_section", "list_item"];
3355
3361
  readonly successMessage: "Approved.";
3356
3362
  readonly refreshAfter: true;
@@ -3366,8 +3372,14 @@ declare const SysApprovalRequest: Omit<{
3366
3372
  readonly label: "Comment";
3367
3373
  readonly type: "textarea";
3368
3374
  readonly required: false;
3375
+ }, {
3376
+ readonly name: "attachments";
3377
+ readonly label: "Attachments";
3378
+ readonly type: "file";
3379
+ readonly multiple: true;
3380
+ readonly required: false;
3369
3381
  }];
3370
- readonly visible: "record.status == \"pending\"";
3382
+ readonly visible: "record.viewer.can_act";
3371
3383
  readonly confirmText: "Reject this request? A rejection is final for every approver.";
3372
3384
  readonly locations: ["record_section", "list_item"];
3373
3385
  readonly successMessage: "Rejected.";
@@ -3391,7 +3403,7 @@ declare const SysApprovalRequest: Omit<{
3391
3403
  readonly type: "textarea";
3392
3404
  readonly required: false;
3393
3405
  }];
3394
- readonly visible: "record.status == \"pending\"";
3406
+ readonly visible: "record.viewer.can_act";
3395
3407
  readonly locations: ["record_section"];
3396
3408
  readonly successMessage: "Reassigned.";
3397
3409
  readonly refreshAfter: true;
@@ -3408,7 +3420,7 @@ declare const SysApprovalRequest: Omit<{
3408
3420
  readonly type: "textarea";
3409
3421
  readonly required: false;
3410
3422
  }];
3411
- readonly visible: "record.status == \"pending\"";
3423
+ readonly visible: "record.viewer.can_act";
3412
3424
  readonly locations: ["record_section"];
3413
3425
  readonly successMessage: "Sent back for revision.";
3414
3426
  readonly refreshAfter: true;
@@ -3425,7 +3437,7 @@ declare const SysApprovalRequest: Omit<{
3425
3437
  readonly type: "textarea";
3426
3438
  readonly required: true;
3427
3439
  }];
3428
- readonly visible: "record.status == \"pending\"";
3440
+ readonly visible: "record.viewer.can_act";
3429
3441
  readonly locations: ["record_section"];
3430
3442
  readonly successMessage: "Information requested.";
3431
3443
  readonly refreshAfter: true;
@@ -3442,7 +3454,7 @@ declare const SysApprovalRequest: Omit<{
3442
3454
  readonly type: "textarea";
3443
3455
  readonly required: false;
3444
3456
  }];
3445
- readonly visible: "record.status == \"pending\" && record.submitter_id == ctx.user.id";
3457
+ readonly visible: "record.status == \"pending\" && record.viewer.is_submitter";
3446
3458
  readonly locations: ["record_section"];
3447
3459
  readonly successMessage: "Reminder sent.";
3448
3460
  readonly refreshAfter: true;
@@ -3459,7 +3471,7 @@ declare const SysApprovalRequest: Omit<{
3459
3471
  readonly type: "textarea";
3460
3472
  readonly required: false;
3461
3473
  }];
3462
- readonly visible: "(record.status == \"pending\" || record.status == \"returned\") && record.submitter_id == ctx.user.id";
3474
+ readonly visible: "(record.status == \"pending\" || record.status == \"returned\") && record.viewer.is_submitter";
3463
3475
  readonly confirmText: "Recall this request? Approvers can no longer act on it and the record is unlocked.";
3464
3476
  readonly locations: ["record_section"];
3465
3477
  readonly successMessage: "Recalled.";
@@ -3477,7 +3489,7 @@ declare const SysApprovalRequest: Omit<{
3477
3489
  readonly type: "textarea";
3478
3490
  readonly required: false;
3479
3491
  }];
3480
- readonly visible: "record.status == \"returned\" && record.submitter_id == ctx.user.id";
3492
+ readonly visible: "record.status == \"returned\" && record.viewer.is_submitter";
3481
3493
  readonly locations: ["record_section"];
3482
3494
  readonly successMessage: "Resubmitted.";
3483
3495
  readonly refreshAfter: true;
@@ -3694,7 +3706,7 @@ declare const SysApprovalAction: Omit<{
3694
3706
  pluralLabel?: string | undefined;
3695
3707
  description?: string | undefined;
3696
3708
  icon?: string | undefined;
3697
- managedBy?: "platform" | "system" | "config" | "append-only" | "better-auth" | undefined;
3709
+ managedBy?: "platform" | "system" | "config" | "engine-owned" | "append-only" | "better-auth" | undefined;
3698
3710
  ownership?: "none" | "org" | "user" | undefined;
3699
3711
  userActions?: {
3700
3712
  create?: boolean | undefined;
@@ -3892,7 +3904,7 @@ declare const SysApprovalAction: Omit<{
3892
3904
  }[] | undefined;
3893
3905
  filter?: {
3894
3906
  field: string;
3895
- operator: string;
3907
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
3896
3908
  value?: string | number | boolean | (string | number)[] | null | undefined;
3897
3909
  }[] | undefined;
3898
3910
  description?: string | undefined;
@@ -4051,7 +4063,7 @@ declare const SysApprovalAction: Omit<{
4051
4063
  view?: string | undefined;
4052
4064
  filter?: {
4053
4065
  field: string;
4054
- operator: string;
4066
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
4055
4067
  value?: string | number | boolean | (string | number)[] | null | undefined;
4056
4068
  }[] | undefined;
4057
4069
  order?: number | undefined;
@@ -5940,7 +5952,7 @@ declare const SysApprovalApprover: Omit<{
5940
5952
  pluralLabel?: string | undefined;
5941
5953
  description?: string | undefined;
5942
5954
  icon?: string | undefined;
5943
- managedBy?: "platform" | "system" | "config" | "append-only" | "better-auth" | undefined;
5955
+ managedBy?: "platform" | "system" | "config" | "engine-owned" | "append-only" | "better-auth" | undefined;
5944
5956
  ownership?: "none" | "org" | "user" | undefined;
5945
5957
  userActions?: {
5946
5958
  create?: boolean | undefined;
@@ -6138,7 +6150,7 @@ declare const SysApprovalApprover: Omit<{
6138
6150
  }[] | undefined;
6139
6151
  filter?: {
6140
6152
  field: string;
6141
- operator: string;
6153
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
6142
6154
  value?: string | number | boolean | (string | number)[] | null | undefined;
6143
6155
  }[] | undefined;
6144
6156
  description?: string | undefined;
@@ -6297,7 +6309,7 @@ declare const SysApprovalApprover: Omit<{
6297
6309
  view?: string | undefined;
6298
6310
  filter?: {
6299
6311
  field: string;
6300
- operator: string;
6312
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
6301
6313
  value?: string | number | boolean | (string | number)[] | null | undefined;
6302
6314
  }[] | undefined;
6303
6315
  order?: number | undefined;
@@ -6553,7 +6565,7 @@ declare const SysApprovalApprover: Omit<{
6553
6565
  readonly pluralLabel: "Approval Approvers";
6554
6566
  readonly icon: "users";
6555
6567
  readonly isSystem: true;
6556
- readonly managedBy: "system";
6568
+ readonly managedBy: "engine-owned";
6557
6569
  readonly description: "Normalized pending-approver rows for indexed inbox queries";
6558
6570
  readonly displayNameField: "id";
6559
6571
  readonly nameField: "id";
@@ -7457,7 +7469,7 @@ declare const SysApprovalDelegation: Omit<{
7457
7469
  pluralLabel?: string | undefined;
7458
7470
  description?: string | undefined;
7459
7471
  icon?: string | undefined;
7460
- managedBy?: "platform" | "system" | "config" | "append-only" | "better-auth" | undefined;
7472
+ managedBy?: "platform" | "system" | "config" | "engine-owned" | "append-only" | "better-auth" | undefined;
7461
7473
  ownership?: "none" | "org" | "user" | undefined;
7462
7474
  userActions?: {
7463
7475
  create?: boolean | undefined;
@@ -7655,7 +7667,7 @@ declare const SysApprovalDelegation: Omit<{
7655
7667
  }[] | undefined;
7656
7668
  filter?: {
7657
7669
  field: string;
7658
- operator: string;
7670
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
7659
7671
  value?: string | number | boolean | (string | number)[] | null | undefined;
7660
7672
  }[] | undefined;
7661
7673
  description?: string | undefined;
@@ -7814,7 +7826,7 @@ declare const SysApprovalDelegation: Omit<{
7814
7826
  view?: string | undefined;
7815
7827
  filter?: {
7816
7828
  field: string;
7817
- operator: string;
7829
+ operator: "in" | "contains" | "after" | "starts_with" | "ends_with" | "not_in" | "not_contains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "greater_than_or_equal" | "less_than_or_equal" | "before";
7818
7830
  value?: string | number | boolean | (string | number)[] | null | undefined;
7819
7831
  }[] | undefined;
7820
7832
  order?: number | undefined;
@@ -9780,6 +9792,16 @@ declare class ApprovalService implements IApprovalService {
9780
9792
  * Display-only and best-effort — errors leave the row untouched.
9781
9793
  */
9782
9794
  private attachDecisionProgress;
9795
+ /**
9796
+ * Attach the per-viewer capability block (#3310) from the caller's context.
9797
+ * `can_act` mirrors the exact authorization the decision methods enforce — the
9798
+ * caller's user id is in the resolved `pending_approvers` while the request is
9799
+ * still `pending` (position/team/manager approvers are already resolved to
9800
+ * concrete user ids at open time, so a plain membership test is faithful).
9801
+ * `is_submitter` is a straight owner check. System/tokenless contexts get a
9802
+ * both-false block. Cheap + synchronous — safe on list reads.
9803
+ */
9804
+ private attachViewers;
9783
9805
  /**
9784
9806
  * Derive approval-step progress from the owning flow's graph (single-read
9785
9807
  * enrichment only — list reads skip it). Walks from the start node
package/dist/index.js CHANGED
@@ -880,7 +880,7 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
880
880
  pluralLabel: "Approval Requests",
881
881
  icon: "inbox",
882
882
  isSystem: true,
883
- managedBy: "system",
883
+ managedBy: "engine-owned",
884
884
  description: "Live approval instance tracked per submission",
885
885
  displayNameField: "id",
886
886
  nameField: "id",
@@ -1068,8 +1068,12 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1068
1068
  // (and their params) ship as metadata, not as hand-written buttons. Each
1069
1069
  // targets the existing approvals REST route; `{id}` resolves from the row
1070
1070
  // and `actorId` defaults to the caller server-side. The service remains the
1071
- // authority on who may act (pending-approver check) — `visible` only trims
1072
- // the obvious non-pending case.
1071
+ // authority on who may act; `visible` gates on the server-computed
1072
+ // per-viewer block (#3310): approver actions on `record.viewer.can_act`
1073
+ // (the caller is a current pending approver — same check the service
1074
+ // authorizes a decision with, so position/team approvers resolve correctly),
1075
+ // submitter actions on `record.viewer.is_submitter`. `viewer` is attached by
1076
+ // getRequest/listRequests; where it is absent the predicate fails closed.
1073
1077
  actions: [
1074
1078
  {
1075
1079
  name: "approval_approve",
@@ -1079,9 +1083,13 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1079
1083
  method: "POST",
1080
1084
  target: "/api/v1/approvals/requests/{id}/approve",
1081
1085
  params: [
1082
- { name: "comment", label: "Comment", type: "textarea", required: false }
1086
+ { name: "comment", label: "Comment", type: "textarea", required: false },
1087
+ // Decision attachments (#3266). The console renders `type:'file'` params
1088
+ // through the shared upload widget and POSTs the resolved `attachments:
1089
+ // string[]`; the decision route persists them on `sys_approval_action`.
1090
+ { name: "attachments", label: "Attachments", type: "file", multiple: true, required: false }
1083
1091
  ],
1084
- visible: 'record.status == "pending"',
1092
+ visible: "record.viewer.can_act",
1085
1093
  locations: ["record_section", "list_item"],
1086
1094
  successMessage: "Approved.",
1087
1095
  refreshAfter: true
@@ -1094,9 +1102,10 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1094
1102
  method: "POST",
1095
1103
  target: "/api/v1/approvals/requests/{id}/reject",
1096
1104
  params: [
1097
- { name: "comment", label: "Comment", type: "textarea", required: false }
1105
+ { name: "comment", label: "Comment", type: "textarea", required: false },
1106
+ { name: "attachments", label: "Attachments", type: "file", multiple: true, required: false }
1098
1107
  ],
1099
- visible: 'record.status == "pending"',
1108
+ visible: "record.viewer.can_act",
1100
1109
  confirmText: "Reject this request? A rejection is final for every approver.",
1101
1110
  locations: ["record_section", "list_item"],
1102
1111
  successMessage: "Rejected.",
@@ -1118,15 +1127,15 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1118
1127
  { field: "submitter_id", name: "to", label: "New approver", required: true, helpText: "User to hand this step to" },
1119
1128
  { name: "comment", label: "Comment", type: "textarea", required: false }
1120
1129
  ],
1121
- visible: 'record.status == "pending"',
1130
+ visible: "record.viewer.can_act",
1122
1131
  locations: ["record_section"],
1123
1132
  successMessage: "Reassigned.",
1124
1133
  refreshAfter: true
1125
1134
  },
1126
1135
  // ── Approver secondary decisions ────────────────────────────────
1127
1136
  // Send back for revision / request more info (ADR-0044). Both are approver
1128
- // actions on a pending request; the service is the authority on who may act,
1129
- // so `visible` only trims the non-pending case (matching approve/reject).
1137
+ // actions, so `visible` gates on `record.viewer.can_act` (a current pending
1138
+ // approver) same as approve/reject. The service stays the authority.
1130
1139
  {
1131
1140
  name: "approval_send_back",
1132
1141
  label: "Send back",
@@ -1137,7 +1146,7 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1137
1146
  params: [
1138
1147
  { name: "comment", label: "Reason", type: "textarea", required: false }
1139
1148
  ],
1140
- visible: 'record.status == "pending"',
1149
+ visible: "record.viewer.can_act",
1141
1150
  locations: ["record_section"],
1142
1151
  successMessage: "Sent back for revision.",
1143
1152
  refreshAfter: true
@@ -1152,17 +1161,17 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1152
1161
  params: [
1153
1162
  { name: "comment", label: "What do you need?", type: "textarea", required: true }
1154
1163
  ],
1155
- visible: 'record.status == "pending"',
1164
+ visible: "record.viewer.can_act",
1156
1165
  locations: ["record_section"],
1157
1166
  successMessage: "Information requested.",
1158
1167
  refreshAfter: true
1159
1168
  },
1160
1169
  // ── Submitter continuity actions ────────────────────────────────
1161
1170
  // Remind / recall (pending) and resubmit / recall (returned). These are the
1162
- // submitter's own levers, so `visible` gates on `submitter_id == ctx.user.id`
1163
- // the current user is exposed via the console's predicate scope. The
1164
- // service re-checks ownership; the predicate keeps a non-submitter from ever
1165
- // seeing a button they cannot use.
1171
+ // submitter's own levers, so `visible` gates on `record.viewer.is_submitter`
1172
+ // (server-computed on the current viewer). The service re-checks ownership;
1173
+ // the predicate keeps a non-submitter from ever seeing a button they cannot
1174
+ // use.
1166
1175
  {
1167
1176
  name: "approval_remind",
1168
1177
  label: "Send reminder",
@@ -1173,7 +1182,7 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1173
1182
  params: [
1174
1183
  { name: "comment", label: "Note", type: "textarea", required: false }
1175
1184
  ],
1176
- visible: 'record.status == "pending" && record.submitter_id == ctx.user.id',
1185
+ visible: 'record.status == "pending" && record.viewer.is_submitter',
1177
1186
  locations: ["record_section"],
1178
1187
  successMessage: "Reminder sent.",
1179
1188
  refreshAfter: true
@@ -1190,7 +1199,7 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1190
1199
  ],
1191
1200
  // Recall applies while the request is live for the submitter — pending
1192
1201
  // (withdraw) or returned (abandon the revision instead of resubmitting).
1193
- visible: '(record.status == "pending" || record.status == "returned") && record.submitter_id == ctx.user.id',
1202
+ visible: '(record.status == "pending" || record.status == "returned") && record.viewer.is_submitter',
1194
1203
  confirmText: "Recall this request? Approvers can no longer act on it and the record is unlocked.",
1195
1204
  locations: ["record_section"],
1196
1205
  successMessage: "Recalled.",
@@ -1206,7 +1215,7 @@ var SysApprovalRequest = import_data.ObjectSchema.create({
1206
1215
  params: [
1207
1216
  { name: "comment", label: "What changed?", type: "textarea", required: false }
1208
1217
  ],
1209
- visible: 'record.status == "returned" && record.submitter_id == ctx.user.id',
1218
+ visible: 'record.status == "returned" && record.viewer.is_submitter',
1210
1219
  locations: ["record_section"],
1211
1220
  successMessage: "Resubmitted.",
1212
1221
  refreshAfter: true
@@ -1344,7 +1353,7 @@ var SysApprovalApprover = import_data3.ObjectSchema.create({
1344
1353
  pluralLabel: "Approval Approvers",
1345
1354
  icon: "users",
1346
1355
  isSystem: true,
1347
- managedBy: "system",
1356
+ managedBy: "engine-owned",
1348
1357
  description: "Normalized pending-approver rows for indexed inbox queries",
1349
1358
  displayNameField: "id",
1350
1359
  nameField: "id",
@@ -3350,6 +3359,7 @@ var _ApprovalService = class _ApprovalService {
3350
3359
  const rows = await this.engine.find("sys_approval_request", findOpts);
3351
3360
  const list = Array.isArray(rows) ? rows.map(rowFromRequest) : [];
3352
3361
  await this.enrichRows(list);
3362
+ this.attachViewers(list, context);
3353
3363
  return list;
3354
3364
  }
3355
3365
  async countRequests(filter, context) {
@@ -3391,6 +3401,7 @@ var _ApprovalService = class _ApprovalService {
3391
3401
  await this.enrichRows([row]);
3392
3402
  await this.attachFlowSteps(row);
3393
3403
  await this.attachDecisionProgress(row, rows[0]);
3404
+ this.attachViewers([row], context);
3394
3405
  return row;
3395
3406
  }
3396
3407
  /**
@@ -3438,6 +3449,25 @@ var _ApprovalService = class _ApprovalService {
3438
3449
  } catch {
3439
3450
  }
3440
3451
  }
3452
+ /**
3453
+ * Attach the per-viewer capability block (#3310) from the caller's context.
3454
+ * `can_act` mirrors the exact authorization the decision methods enforce — the
3455
+ * caller's user id is in the resolved `pending_approvers` while the request is
3456
+ * still `pending` (position/team/manager approvers are already resolved to
3457
+ * concrete user ids at open time, so a plain membership test is faithful).
3458
+ * `is_submitter` is a straight owner check. System/tokenless contexts get a
3459
+ * both-false block. Cheap + synchronous — safe on list reads.
3460
+ */
3461
+ attachViewers(rows, context) {
3462
+ const uid2 = context?.userId != null ? String(context.userId) : null;
3463
+ for (const row of rows) {
3464
+ const pending = row.pending_approvers ?? [];
3465
+ row.viewer = {
3466
+ can_act: row.status === "pending" && !!uid2 && pending.includes(uid2),
3467
+ is_submitter: !!uid2 && row.submitter_id != null && String(row.submitter_id) === uid2
3468
+ };
3469
+ }
3470
+ }
3441
3471
  /**
3442
3472
  * Derive approval-step progress from the owning flow's graph (single-read
3443
3473
  * enrichment only — list reads skip it). Walks from the start node
@@ -3506,7 +3536,7 @@ var SysApprovalToken = import_data5.ObjectSchema.create({
3506
3536
  pluralLabel: "Approval Action Tokens",
3507
3537
  icon: "key",
3508
3538
  isSystem: true,
3509
- managedBy: "system",
3539
+ managedBy: "engine-owned",
3510
3540
  description: "Single-use tokens behind actionable approval links",
3511
3541
  displayNameField: "id",
3512
3542
  nameField: "id",