-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathsys-approval-action.object.ts
More file actions
149 lines (133 loc) · 5.02 KB
/
Copy pathsys-approval-action.object.ts
File metadata and controls
149 lines (133 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { ObjectSchema, Field } from '@objectstack/spec/data';
import { APPROVAL_ACTION_KINDS } from '@objectstack/spec/contracts';
/**
* sys_approval_action — Audit trail row per approval action.
*
* Append-only: every `submit`, `approve`, `reject`, `recall`, or
* `escalate` event lands here. The engine reads back per-step approval
* rows to evaluate `behavior: 'unanimous'` (all approvers must approve
* before advancing) versus `first_response` (any single approval
* advances the step).
*
* @namespace sys
*/
export const SysApprovalAction = ObjectSchema.create({
name: 'sys_approval_action',
label: 'Approval Action',
pluralLabel: 'Approval Actions',
icon: 'check-circle',
isSystem: true,
managedBy: 'append-only',
description: 'Append-only audit trail for approval actions',
displayNameField: 'id',
nameField: 'id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{action} · {step_name}',
highlightFields: ['request_id', 'step_name', 'action', 'actor_id', 'created_at'],
// ADR-0104 D3 wave 2. `attachments` is a media field, so the files it holds
// are OWNED by this row — and the storage service would otherwise authorize
// their download by testing whether the caller can READ this row. It cannot:
// this table is deliberately closed to ordinary approver positions, so that
// test denies the very approver the attachment was filed for. The approvals
// service already owns the rule for seeing a decision (visibility of the
// parent request, exactly as `listActions` applies it), so it answers.
fileAccessDelegate: 'approvals',
listViews: {
recent: {
type: 'grid',
name: 'recent',
label: 'Recent',
data: { provider: 'object', object: 'sys_approval_action' },
columns: ['created_at', 'request_id', 'step_name', 'action', 'actor_id', 'comment'],
sort: [{ field: 'created_at', order: 'desc' }],
pagination: { pageSize: 50 },
emptyState: { title: 'No approval actions yet', message: 'Actions are logged automatically when approvals progress.' },
},
by_actor: {
type: 'grid',
name: 'by_actor',
label: 'By Actor',
data: { provider: 'object', object: 'sys_approval_action' },
columns: ['actor_id', 'created_at', 'request_id', 'step_name', 'action'],
sort: [{ field: 'actor_id', order: 'asc' }, { field: 'created_at', order: 'desc' }],
grouping: { fields: [{ field: 'actor_id', order: 'asc', collapsed: false }] },
pagination: { pageSize: 100 },
},
all_actions: {
type: 'grid',
name: 'all_actions',
label: 'All',
data: { provider: 'object', object: 'sys_approval_action' },
columns: ['created_at', 'request_id', 'step_name', 'action', 'actor_id', 'comment'],
sort: [{ field: 'created_at', order: 'desc' }],
pagination: { pageSize: 100 },
},
},
fields: {
id: Field.text({ label: 'Action ID', required: true, readonly: true, group: 'System' }),
organization_id: Field.lookup('sys_organization', {
label: 'Organization',
required: false,
group: 'System',
description: 'Tenant that owns this action (mirrors the parent request)',
}),
request_id: Field.lookup('sys_approval_request', {
label: 'Request',
required: true,
group: 'Target',
}),
step_name: Field.text({
label: 'Step',
required: false,
maxLength: 100,
description: 'Machine name of the step at the time of the action',
group: 'Target',
}),
step_index: Field.number({
label: 'Step Index',
required: false,
group: 'Target',
}),
action: Field.select(
// Spread from the contract, not re-typed (#3786). `APPROVAL_ACTION_KINDS`
// is where the list and the per-kind notes live (which kinds move the flow
// and which are thread-only); `ApprovalActionKind` is derived from it, so
// this column and the contract cannot disagree.
[...APPROVAL_ACTION_KINDS],
{
label: 'Action',
required: true,
group: 'Action',
},
),
actor_id: Field.lookup('sys_user', {
label: 'Actor',
required: false,
group: 'Action',
}),
comment: Field.textarea({ label: 'Comment', required: false, group: 'Action' }),
attachments: Field.file({
label: 'Attachments',
required: false,
multiple: true,
group: 'Action',
description: 'Files supporting this action — e.g. a signed contract or evidence (#3266).',
}),
created_at: Field.datetime({
label: 'Created At',
required: true,
defaultValue: 'NOW()',
readonly: true,
group: 'System',
}),
},
indexes: [
{ fields: ['request_id', 'created_at'] },
{ fields: ['request_id', 'step_index', 'action'] },
],
enable: {
// [ADR-0103] Engine-owned append-only decision log: appended by the approval
// engine (SYSTEM_CTX). Reads stay open.
apiMethods: ['get', 'list'],
},
});