ngx-t-workflow-typings 3.1.0 → 3.2.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.
@@ -65,6 +65,18 @@ export interface ElementEditorInnerSectionStepElementInterface {
65
65
  label: string;
66
66
  value: string;
67
67
  }>;
68
+ /**
69
+ * Renders the chip list as a MULTI-select, so the bound value is a `string[]`
70
+ * rather than one value.
71
+ *
72
+ * Already honoured at runtime — `t-dynamic-data-edit.component.html:183` binds
73
+ * `[multiple]="!!vm.editorConfigValue?.multipleSelection"` for every chip
74
+ * editor, and the workflow-level config (`ElementEditorConfig`) has always
75
+ * declared it. It was simply missing from the STEP-level declaration, so a
76
+ * step element that needed a multi-select (the document/print field lists)
77
+ * could not say so without a cast.
78
+ */
79
+ multipleSelection?: boolean;
68
80
  computedErrors?: ValidationError[];
69
81
  min?: number;
70
82
  max?: number;
@@ -90,6 +90,56 @@ export interface leanWorkFlowDocumentInterface {
90
90
  /** UI-only: in-flight indicator. */
91
91
  busy?: boolean;
92
92
  SYSTEM_TAGS: ITransactionTag[];
93
+ /**
94
+ * Present when the document is a FINAL RECORD — see {@link DocumentSeal}.
95
+ *
96
+ * This is THE seal indicator every consumer reads. It travels with the document
97
+ * through every existing endpoint (list, detail, reports), so no surface needs a
98
+ * separate "is it sealed" call: presence is the answer, and the fields inside it
99
+ * carry who, when, and until when it can still be reversed.
100
+ */
101
+ seal?: DocumentSeal;
102
+ }
103
+ /**
104
+ * The stamp that makes a workflow document a final record: no further editing,
105
+ * check-out, archiving, restoring or deletion.
106
+ *
107
+ * Lives on the document (not only in the sealing ledger) because that is what makes
108
+ * enforcement atomic — the check-out guard tests `seal: { $exists: false }` in the
109
+ * same `$match` it already runs — and what lets every read path report the state
110
+ * without a join.
111
+ */
112
+ export interface DocumentSeal {
113
+ /** When the record was sealed. */
114
+ at: Date;
115
+ /**
116
+ * Who sealed it. Name and email are SNAPSHOT at seal time, never looked up later:
117
+ * a record has to stay readable after the person leaves or is renamed.
118
+ */
119
+ by: {
120
+ userId: string;
121
+ name?: string;
122
+ email?: string;
123
+ };
124
+ /** Why, when the sealer gave a reason. */
125
+ reason?: string;
126
+ /** Digest algorithm, recorded so a later change to it stays verifiable. */
127
+ algo: 'sha256';
128
+ /** Hex digest of the record's canonical content at the moment of sealing. */
129
+ hash: string;
130
+ /**
131
+ * Which canonical-payload recipe produced {@link hash}. Verification compares this
132
+ * BEFORE comparing hashes, so a seal written by an older recipe reports "cannot be
133
+ * checked" rather than falsely reporting the record as altered.
134
+ */
135
+ payloadVersion: number;
136
+ /**
137
+ * The instant the seal becomes permanent. Before it, the sealer or an administrator
138
+ * may reverse the seal; after it, no code path does. Stored as an absolute instant
139
+ * rather than derived from a configured window, so changing that policy cannot move
140
+ * the terms a record was sealed under.
141
+ */
142
+ hardenAt: Date;
93
143
  }
94
144
  /** A hydrated workflow document: the stored fields plus the ones Mongoose adds. */
95
145
  export interface WorkFlowDocumentInterface extends leanWorkFlowDocumentInterface {
@@ -1,4 +1,4 @@
1
- export type { RecentlyViewedParams, SimilarityResult, leanWorkFlowDocumentInterface, WorkFlowDocumentInterface, IRecentlyViewed, IRecentlyViewedCookie, } from './WorkFlowDocument.interface.js';
1
+ export type { RecentlyViewedParams, SimilarityResult, leanWorkFlowDocumentInterface, WorkFlowDocumentInterface, IRecentlyViewed, IRecentlyViewedCookie, DocumentSeal, } from './WorkFlowDocument.interface.js';
2
2
  export { RecentlyViewedStatus, WorkFlowDocumentStatus } from './WorkFlowDocument.interface.js';
3
3
  /** @deprecated Use the {@link RecentlyViewedStatus} enum. */
4
4
  export type { RecentlyViewedStatusType } from './WorkFlowDocument.interface.js';
@@ -121,6 +121,32 @@ export interface DocumentWorkflowStep extends ProcessStep {
121
121
  * @see LegacyDocumentWorkflowStep if a consumer still needs the opaque shape.
122
122
  */
123
123
  validations: StepValidationOverride[];
124
+ /**
125
+ * Escalation records stamped onto the step when it is written.
126
+ *
127
+ * @deprecated **DUE REVIEW — pending removal.** The workflow builder's escalation
128
+ * setup has been decommissioned, so nothing configures escalations any more and
129
+ * every write is an empty array. It is declared only because writers still emit the
130
+ * key (`osproc-be/src/services/form.ts:671, 1033, 1407`; also on that service's own
131
+ * step interface, `src/interfaces/IFormVal.ts:6`) and a typed step schema would
132
+ * otherwise drop it silently.
133
+ *
134
+ * Remove this field — and the writes — once it is confirmed no stored document
135
+ * carries a non-empty `escalations` array. Deliberately `unknown[]`, NOT the
136
+ * definition-side escalation-level shape, which is already withdrawn (see
137
+ * `WorkflowSubSchemas.schema.ts`).
138
+ */
139
+ escalations?: unknown[];
140
+ /**
141
+ * The working copy a check-in came from, stamped on the `checkin` step record.
142
+ *
143
+ * Evidence: written at
144
+ * `cartalist-client-server/src/api/repositories/TransactionRepository.ts:518`. The
145
+ * matching `versions[].tempDocId` is already declared on the FormVal schema; this is
146
+ * the same link on the step record, which is what ties a filed version to the edit
147
+ * that produced it.
148
+ */
149
+ tempDocId?: string;
124
150
  }
125
151
  export interface DocumentActivityLog extends DocumentWorkflowStep {
126
152
  /**
@@ -200,6 +200,62 @@ export interface ProcessStep extends IProcessNodeBase {
200
200
  adjudicationMicroFlowStepWithAssignPoints?: string;
201
201
  microFlow: string;
202
202
  formId?: string;
203
+ /**
204
+ * Hides this step's section from the transaction's SYSTEM DOC — the assembled
205
+ * document a transaction's whole team can read.
206
+ *
207
+ * A workflow accumulates form values step by step and the System Doc renders
208
+ * one section per step that captured any, so an internal or procedural step
209
+ * (an inbox triage, a routing capture, a step whose fields only exist to drive
210
+ * a decision gate) publishes its fields to everyone with access to the
211
+ * document. This flag is the admin's opt-out for exactly those steps.
212
+ *
213
+ * PRESENTATION ONLY, and only on that one surface:
214
+ *
215
+ * - The step still RUNS. Its form is still filled in, its values still land in
216
+ * `document.form`, and every downstream reader — decision gates, bulk mail,
217
+ * scheduling, reports — still resolves them. This is not access control and
218
+ * must not be used as such; a hidden step's values remain readable through
219
+ * the document payload.
220
+ * - The ACTIVITY LOG is unaffected. That surface is the audit trail: it must
221
+ * keep showing every visit, by whom and when, whatever the document chooses
222
+ * to publish.
223
+ *
224
+ * Owned by the workflow DEFINITION, never by a document's step record — an
225
+ * admin toggling it must change what an already-filed document renders, so
226
+ * consumers read it off the definition rather than a runtime snapshot.
227
+ *
228
+ * Absent/`false` means visible, so every workflow authored before this field
229
+ * existed keeps rendering exactly as it did.
230
+ */
231
+ hideFromDocument?: boolean;
232
+ /**
233
+ * `formControlName`s of this step's own form that are suppressed EVERYWHERE the
234
+ * document is presented — details and print alike.
235
+ *
236
+ * Use for a field the step needs but the record should not carry: an internal
237
+ * note, a routing selector, a scratch value a decision gate reads. The value is
238
+ * still captured and still reaches `document.form`; it simply is not published.
239
+ *
240
+ * @see hideFromDocument, which is the same decision one level up. A hidden STEP
241
+ * publishes nothing at all, so these lists are moot on one — precedence is
242
+ * step-hidden > field-hidden > printable, and consumers apply it in that order.
243
+ */
244
+ hiddenFields?: string[];
245
+ /**
246
+ * `formControlName`s of this step's own form that are shown on screen but left
247
+ * OUT OF PRINT.
248
+ *
249
+ * The third state of the publishing rule, and the only one that distinguishes
250
+ * the two surfaces: a field the team should be able to read in the document
251
+ * without it landing in the printed/PDF copy — working notes, a long internal
252
+ * justification, an attachment thumbnail that prints as a wall of nothing.
253
+ *
254
+ * Ignored for a field already in {@link hiddenFields} (that field is gone from
255
+ * both surfaces) and for a step with {@link hideFromDocument}. Absent means
256
+ * printable, so every field authored before this existed still prints.
257
+ */
258
+ nonPrintableFields?: string[];
203
259
  members?: string[];
204
260
  followUpOperations: BackEndFollowUpOperationInterface[];
205
261
  active?: boolean;
@@ -10,9 +10,19 @@ export declare enum WorkflowStepTypeEnum {
10
10
  PublicPortal = "publicPortal",
11
11
  Archive = "archive",
12
12
  Checkout = "checkout",
13
- Checkin = "checkin"
13
+ Checkin = "checkin",
14
+ /**
15
+ * Record sealing. Like `Checkout`/`Checkin` these are custody events rather than
16
+ * steps a document moves through — they carry no `stepId` and no definition in any
17
+ * workflow — but they ARE persisted into `processTree`, which is why they belong in
18
+ * this enum: `iserve-shared-types/schemas/Workflow/WorkflowProcessTree.schema.ts`
19
+ * derives that column's `enum` constraint from these values, so a step type absent
20
+ * here cannot legally be written.
21
+ */
22
+ Seal = "seal",
23
+ Unseal = "unseal"
14
24
  }
15
- export type WorkflowStepTypes = WorkflowStepTypeEnum.Initiate | WorkflowStepTypeEnum.Review | WorkflowStepTypeEnum.Append | WorkflowStepTypeEnum.Process | WorkflowStepTypeEnum.Decision | WorkflowStepTypeEnum.End | WorkflowStepTypeEnum.BulkMail | WorkflowStepTypeEnum.Adjudication | WorkflowStepTypeEnum.PublicPortal | WorkflowStepTypeEnum.Archive | WorkflowStepTypeEnum.Checkout | WorkflowStepTypeEnum.Checkin;
25
+ export type WorkflowStepTypes = WorkflowStepTypeEnum.Initiate | WorkflowStepTypeEnum.Review | WorkflowStepTypeEnum.Append | WorkflowStepTypeEnum.Process | WorkflowStepTypeEnum.Decision | WorkflowStepTypeEnum.End | WorkflowStepTypeEnum.BulkMail | WorkflowStepTypeEnum.Adjudication | WorkflowStepTypeEnum.PublicPortal | WorkflowStepTypeEnum.Archive | WorkflowStepTypeEnum.Checkout | WorkflowStepTypeEnum.Checkin | WorkflowStepTypeEnum.Seal | WorkflowStepTypeEnum.Unseal;
16
26
  /**
17
27
  * Placeholder step kinds the CLIENT fabricates for display. These are NEVER
18
28
  * persisted and are deliberately kept OUT of {@link WorkflowStepTypeEnum}.
@@ -12,6 +12,16 @@ export var WorkflowStepTypeEnum;
12
12
  WorkflowStepTypeEnum["Archive"] = "archive";
13
13
  WorkflowStepTypeEnum["Checkout"] = "checkout";
14
14
  WorkflowStepTypeEnum["Checkin"] = "checkin";
15
+ /**
16
+ * Record sealing. Like `Checkout`/`Checkin` these are custody events rather than
17
+ * steps a document moves through — they carry no `stepId` and no definition in any
18
+ * workflow — but they ARE persisted into `processTree`, which is why they belong in
19
+ * this enum: `iserve-shared-types/schemas/Workflow/WorkflowProcessTree.schema.ts`
20
+ * derives that column's `enum` constraint from these values, so a step type absent
21
+ * here cannot legally be written.
22
+ */
23
+ WorkflowStepTypeEnum["Seal"] = "seal";
24
+ WorkflowStepTypeEnum["Unseal"] = "unseal";
15
25
  })(WorkflowStepTypeEnum || (WorkflowStepTypeEnum = {}));
16
26
  /**
17
27
  * Placeholder step kinds the CLIENT fabricates for display. These are NEVER
@@ -747,6 +747,8 @@ export const STEP_REFERENCE_RULES = {
747
747
  [WorkflowStepTypeEnum.Archive]: [],
748
748
  [WorkflowStepTypeEnum.Checkout]: [],
749
749
  [WorkflowStepTypeEnum.Checkin]: [],
750
+ [WorkflowStepTypeEnum.Seal]: [],
751
+ [WorkflowStepTypeEnum.Unseal]: [],
750
752
  };
751
753
  /* -------------------------------------------------------------------------- */
752
754
  /* Workflow-level rules */
@@ -1174,6 +1174,38 @@ export const STEP_TYPE_REQUIREMENTS = {
1174
1174
  'Synthesised by the server on check-in (`S/repositories/TransactionRepository.ts:512-520`) and treated identically to `checkout` by the interpreter (`workflow.service.ts:57-63`) and the status resolver (`workflow-document-utils.ts:347-350`).',
1175
1175
  ],
1176
1176
  },
1177
+ /* ====================================================================== */
1178
+ /* SEAL / UNSEAL — record custody, never authored */
1179
+ /* ====================================================================== */
1180
+ [WorkflowStepTypeEnum.Seal]: {
1181
+ stepType: WorkflowStepTypeEnum.Seal,
1182
+ label: 'Record Sealed',
1183
+ origin: StepTypeOrigin.Runtime,
1184
+ systemStep: null,
1185
+ required: [],
1186
+ conditional: [],
1187
+ optional: [],
1188
+ sockets: NO_SOCKET_EXPECTATION,
1189
+ integrityNotes: [
1190
+ 'Not authorable: no palette entry, no step editor. A workflow can never contain a seal step — it is a custody event the server records when a completed document is made a final record.',
1191
+ 'Carries no `stepId`, so every "where is this document" reading must look past it; see `documentLifecycle.LIFECYCLE_EVENT_STEP_TYPES` (server) and `shared/utils/workflow-lifecycle.ts` (client).',
1192
+ 'Declared here because `processTree.stepType` derives its database `enum` constraint from this enum — a persisted step type absent from it cannot legally be written.',
1193
+ ],
1194
+ },
1195
+ [WorkflowStepTypeEnum.Unseal]: {
1196
+ stepType: WorkflowStepTypeEnum.Unseal,
1197
+ label: 'Seal Reversed',
1198
+ origin: StepTypeOrigin.Runtime,
1199
+ systemStep: null,
1200
+ required: [],
1201
+ conditional: [],
1202
+ optional: [],
1203
+ sockets: NO_SOCKET_EXPECTATION,
1204
+ integrityNotes: [
1205
+ 'Recorded only when a seal is reversed inside its grace window; past that window no code path produces this event.',
1206
+ 'Same custody-event rules as `seal` — never authored, no `stepId`, skipped by every step resolver.',
1207
+ ],
1208
+ },
1177
1209
  };
1178
1210
  /**
1179
1211
  * The 12 `WorkflowStepTypeEnum` members in a stable order, derived from the map
@@ -1,5 +1,152 @@
1
1
  import type { DocumentSectionConfigurationsInterface } from '../DocumentSection/DocumentSectionConfigurations.interface.js';
2
2
  import type { ProcessStep } from '../ProcessStep/ProcessStep.interface.js';
3
+ /**
4
+ * WHEN a transaction of a workflow may be archived, expressed as a window over
5
+ * the transaction's position in its own process tree.
6
+ *
7
+ * - `inflight-abandon` — anywhere EXCEPT a completed end step. Archiving is an
8
+ * abandon: a request that should not proceed is taken off the active list
9
+ * while it is still running. This is the behaviour every workflow had before
10
+ * the policy existed, and remains the default.
11
+ * - `on-completion` — ONLY at a completed end step. The inverse reading:
12
+ * archiving is records disposal, so finished work can be filed away and
13
+ * nothing half-done can disappear.
14
+ * - `at-step` — only while the transaction sits on one of the `stepId`s listed
15
+ * in {@link ArchivePolicy.steps}. Archiving becomes a particular step's own
16
+ * affordance ("the people on Verification can bin it there, nobody else").
17
+ * - `always` — any position.
18
+ * - `never` — archiving is not offered at all. For registers that must retain
19
+ * every transaction, this is the workflow-wide equivalent of what sealing does
20
+ * to a single record.
21
+ *
22
+ * The `auto-*` presets sketched during design (auto-archive on completion, after
23
+ * a retention window, or on stale in-flight transactions) are deliberately NOT
24
+ * here: they need a scheduled runner and a system actor for the `processTree`
25
+ * marker. Adding them later only widens this union, so it is a non-breaking
26
+ * change for stored configs.
27
+ */
28
+ export type ArchivePolicyPreset = 'inflight-abandon' | 'on-completion' | 'at-step' | 'always' | 'never';
29
+ /**
30
+ * WHO may archive, once {@link ArchivePolicyPreset} says the transaction is in
31
+ * an archivable position.
32
+ *
33
+ * - `initiator` — a member of the workflow's initiate step. The only rule that
34
+ * existed before this field, and the default.
35
+ * - `stepMembers` — the members of the step the transaction currently sits on.
36
+ * The natural pairing with the `at-step` preset; also the answer to the
37
+ * standing complaint that a step member who spots a bad transaction has to go
38
+ * find its initiator to have it removed.
39
+ * - `admin` — department or organisation administrators (inclusive upwards),
40
+ * the same right that already governs sealing.
41
+ * - `workflowMembers` — anyone named on ANY step of the workflow. The widest
42
+ * setting available, and still bounded: it is the workflow's own people, not
43
+ * "whoever can see the row". An earlier draft offered `any` for that; it was
44
+ * removed because "can see it" is a read permission and archiving is a write,
45
+ * so it granted removal to an audience nobody had chosen for the purpose.
46
+ *
47
+ * `workflowMembers` here means the same set as {@link SealActors} `workflowMembers`
48
+ * — the union of every step's members — and is NOT `stepMembers`, which is only
49
+ * the step a transaction is currently sitting on.
50
+ */
51
+ export type ArchiveActors = 'initiator' | 'stepMembers' | 'admin' | 'workflowMembers';
52
+ /**
53
+ * WHO may seal a completed transaction — and, being the same people, who may
54
+ * reverse a seal while it is still inside its grace window.
55
+ *
56
+ * - `authorOrAdmin` — the person who created the transaction, or an
57
+ * administrator of its department/organisation. The rule that predates this
58
+ * policy, and the default.
59
+ * - `author` — only the creator.
60
+ * - `admin` — administrators only. This is the separation-of-duty setting: an
61
+ * author who does not administer the department cannot finalise their own
62
+ * record. It is expressed through the actor list rather than a separate flag
63
+ * because "who may seal" is the only question being asked.
64
+ * - `initiator` — anyone on the workflow's INITIATE step.
65
+ * - `workflowMembers` — anyone named on ANY step of the workflow.
66
+ *
67
+ * AUTHOR vs INITIATOR are different scopes and must not be conflated. The author
68
+ * is `FormVal.userId` — whoever created THIS transaction. An initiator is a
69
+ * member of the workflow's initiate step: a standing permission to START
70
+ * transactions, held regardless of whether they ever touched this one. The
71
+ * author is normally also an initiator, but need not be — step membership
72
+ * changes, and the author of an old record may have left it.
73
+ *
74
+ * `workflowMembers` is also NOT the archive policy's `stepMembers`. That one
75
+ * means the members of the step a transaction is CURRENTLY sitting on; this one
76
+ * is the union of every step's members. Same-sounding, deliberately different
77
+ * sets — a sealed transaction has finished, so it sits on no actionable step.
78
+ */
79
+ export type SealActors = 'authorOrAdmin' | 'author' | 'admin' | 'initiator' | 'workflowMembers';
80
+ /**
81
+ * Per-workflow rule for sealing its transactions into final records. See
82
+ * {@link WorkflowModel.sealPolicy}.
83
+ */
84
+ export interface SealPolicy {
85
+ /**
86
+ * Whether this workflow offers sealing at all.
87
+ *
88
+ * Sealing is OPT-IN: absent or `false` means the action is not available on
89
+ * this workflow and the server refuses it. Nothing else in this object is read
90
+ * while it is off.
91
+ *
92
+ * Defaulting to `false` is also what makes the builder's Toggle safe — see the
93
+ * note on {@link ArchivePolicy.oneWay} for why a true-default boolean cannot be
94
+ * edited with one.
95
+ */
96
+ enabled: boolean;
97
+ /** Defaults to `'authorOrAdmin'` when absent. */
98
+ actors?: SealActors;
99
+ /**
100
+ * How many days a seal stays reversible.
101
+ *
102
+ * Absent means the server's `SEAL_GRACE_DAYS` environment value. `0` makes a
103
+ * seal permanent the instant it is applied.
104
+ *
105
+ * Read ONCE at seal time and stored on the seal as `hardenAt`, an absolute
106
+ * instant. Changing this never moves the terms a record was already sealed
107
+ * under — and once `hardenAt` passes, nothing and nobody can reverse that seal
108
+ * through any interface. That refusal is what the seal is worth.
109
+ */
110
+ graceDays?: number;
111
+ }
112
+ /**
113
+ * Per-workflow rule for archiving its transactions. See
114
+ * {@link WorkflowModel.archivePolicy}.
115
+ */
116
+ export interface ArchivePolicy {
117
+ preset: ArchivePolicyPreset;
118
+ /**
119
+ * The `stepId`s at which archiving is offered.
120
+ *
121
+ * Required — and only read — when `preset` is `'at-step'`; ignored under every
122
+ * other preset. Matched against the transaction's `currentStepID`, which is
123
+ * the same `ProcessStep.stepId` value the engine writes when it advances a
124
+ * document.
125
+ *
126
+ * An id that no longer appears in `processTree` (its step was deleted in the
127
+ * editor) simply never matches. The engine IGNORES unknown ids rather than
128
+ * erroring, so deleting a step degrades to "not archivable there" instead of
129
+ * breaking every transaction on the workflow.
130
+ */
131
+ steps?: string[];
132
+ /** Defaults to `'initiator'` when absent. */
133
+ actors?: ArchiveActors;
134
+ /**
135
+ * Makes archiving final: the engine refuses the restore half of the toggle.
136
+ *
137
+ * Defaults to `false` — archive and restore are a single toggle endpoint
138
+ * (`POST api/form/archiveDocument` flips `archive` whichever way it currently
139
+ * points), which is the only behaviour that exists today.
140
+ *
141
+ * Phrased as "one-way" rather than the more readable `allowRestore` because
142
+ * the workflow builder edits it with a Toggle, and every Toggle in that panel
143
+ * binds a boolean whose safe default is `false`. An `allowRestore` field would
144
+ * default to `true`, so a Toggle emitting `false` for a value the workflow has
145
+ * never carried would silently make archiving irreversible on every workflow
146
+ * whose config panel was merely opened.
147
+ */
148
+ oneWay?: boolean;
149
+ }
3
150
  /**
4
151
  * A workflow's STORED fields — everything the application writes, and nothing
5
152
  * Mongoose adds for itself (`_id`, `createdAt`, `updatedAt`, `__v`).
@@ -78,8 +225,93 @@ export interface WorkflowModel {
78
225
  *
79
226
  * Optional, so existing workflow object literals that never set it keep
80
227
  * compiling and `StrictSchemaDefinition<IDbWorkflow>` is unaffected.
228
+ *
229
+ * See {@link WorkflowModel.uniqueKeysScope} for whether ARCHIVED transactions
230
+ * still hold their tuple.
81
231
  */
82
232
  uniqueKeys?: string[];
233
+ /**
234
+ * Whether archived transactions still occupy their {@link
235
+ * WorkflowModel.uniqueKeys} tuple.
236
+ *
237
+ * - `'active'` — the duplicate check ignores archived transactions, so
238
+ * archiving RELEASES the tuple and a new transaction may re-use it. This is
239
+ * the behaviour that predates the field (the engine's duplicate query has
240
+ * `archive: false` hard-coded), and so remains the default when absent.
241
+ * - `'all'` — archived transactions still block. The tuple is claimed for the
242
+ * life of the record whatever its storage state; releasing it means
243
+ * restoring the transaction and editing the offending value, or deleting it
244
+ * outright. Note there is no "archive it out of the way" escape hatch under
245
+ * this setting — that is the point of it, but it does mean a mistaken value
246
+ * on an archived transaction needs a deliberate correction.
247
+ *
248
+ * Only consulted when `uniqueKeys` is non-empty. Enforced by
249
+ * `FormService.generateFormValues` (osproc-be) at transaction-create time,
250
+ * which includes or omits `archive: false` in the duplicate query accordingly.
251
+ *
252
+ * READ THIS TOGETHER WITH {@link WorkflowModel.archivePolicy}. Archiving is the
253
+ * only way a transaction leaves the uniqueness pool, so the two fields settle
254
+ * one question between them: whether archiving is a route around the
255
+ * constraint. A workflow that both enforces `uniqueKeys` and permits archiving
256
+ * should set this deliberately rather than inherit the default.
257
+ */
258
+ uniqueKeysScope?: 'active' | 'all';
259
+ /**
260
+ * When a transaction of this workflow may be archived, and by whom.
261
+ *
262
+ * Archiving is a soft delete: `FormVal.archive` flips to `true`, an
263
+ * `{ isArchived, date, userId }` marker is pushed onto the document's own
264
+ * `processTree`, and the row moves from the active list to the archived one
265
+ * (`getArchivedFormValByWorkflowID`). It is reversible unless
266
+ * {@link ArchivePolicy.oneWay} says otherwise.
267
+ *
268
+ * Absent => `{ preset: 'inflight-abandon', actors: 'initiator' }`, which is
269
+ * exactly what every workflow did before this field existed: the initiator may
270
+ * abandon a transaction that is still running, and the action is withdrawn
271
+ * once it reaches a completed end step. Adding the field therefore changes no
272
+ * existing workflow's behaviour.
273
+ *
274
+ * Two guards sit BELOW the policy and are not configurable by it:
275
+ * - a sealed record can never be archived or restored under any preset
276
+ * (`SealedRecordError`) — that is half of what sealing means;
277
+ * - the server re-checks the whole policy in `FormService.archiveDocument`.
278
+ * The client's `canArchive` is a UI affordance only; before this field the
279
+ * step and initiator rules lived exclusively in the Angular list projection
280
+ * and the endpoint enforced nothing but the seal.
281
+ *
282
+ * READ THIS TOGETHER WITH {@link WorkflowModel.uniqueKeysScope} — see the note
283
+ * there.
284
+ */
285
+ archivePolicy?: ArchivePolicy;
286
+ /**
287
+ * Whether this workflow's transactions can be sealed into final records, and
288
+ * on whose terms.
289
+ *
290
+ * Sealing makes a completed transaction permanent: it can never again be
291
+ * edited, checked out, archived, restored or deleted. It is reversible only
292
+ * inside a grace window recorded on the seal itself as `hardenAt`; past that
293
+ * instant NO interface and NO role can reverse it, which is the entire value of
294
+ * the seal.
295
+ *
296
+ * OPT-IN. Absent — or `enabled: false` — means this workflow does not offer
297
+ * sealing at all, and the server refuses the action. This is a deliberate
298
+ * change from the behaviour that predates the field, where every workflow could
299
+ * be sealed: finalising a record is a decision about a class of document, not a
300
+ * capability every workflow should carry by default. Records already sealed
301
+ * stay sealed, verifiable and reversible-in-window whatever this says — turning
302
+ * sealing off withdraws the ability to seal ANEW, it does not unseal anything.
303
+ *
304
+ * Eligibility is unchanged and NOT configurable: only a transaction that is
305
+ * complete, unlocked, not a working copy, not archived and not already sealed
306
+ * can be sealed. {@link SealPolicy.enabled} is a gate in front of those rules,
307
+ * never a replacement for them.
308
+ *
309
+ * READ THIS TOGETHER WITH {@link WorkflowModel.archivePolicy}. Both decide what
310
+ * may happen to a transaction once it finishes, and they interact: a sealed
311
+ * transaction can no longer be archived under ANY archive policy, because the
312
+ * seal guard refuses the write.
313
+ */
314
+ sealPolicy?: SealPolicy;
83
315
  }
84
316
  /**
85
317
  * A persisted workflow as it comes back from Mongo: the stored fields plus the
@@ -1,2 +1,2 @@
1
- export type { WorkflowModel, DbWorkflowModel } from './WorkflowModel.interface.js';
1
+ export type { WorkflowModel, DbWorkflowModel, ArchivePolicy, ArchivePolicyPreset, ArchiveActors, SealPolicy, SealActors, } from './WorkflowModel.interface.js';
2
2
  export type { WorkflowPointingRuleInterface } from './WorkflowPointingRule.interface.js';
@@ -23,6 +23,48 @@ const documentSectionConfigSchema = Joi.object({
23
23
  labelConfiguration: Joi.array().required(),
24
24
  }).optional(),
25
25
  });
26
+ /**
27
+ * Archive rule for a workflow's transactions — see the `archivePolicy` note on
28
+ * `WorkflowModel` for what each preset means.
29
+ *
30
+ * `steps` is only meaningful under the `at-step` preset, and is required there:
31
+ * an `at-step` policy with no steps would offer archiving nowhere, which is
32
+ * `never` written the confusing way. Under every other preset the key is
33
+ * forbidden rather than merely ignored, so a stale `steps` list cannot sit in a
34
+ * stored config implying a rule that is not in force.
35
+ */
36
+ const archivePolicySchema = Joi.object({
37
+ preset: Joi.string()
38
+ .valid('inflight-abandon', 'on-completion', 'at-step', 'always', 'never')
39
+ .required(),
40
+ steps: Joi.array()
41
+ .items(Joi.string())
42
+ .when('preset', {
43
+ is: 'at-step',
44
+ then: Joi.array().items(Joi.string()).min(1).required(),
45
+ otherwise: Joi.forbidden(),
46
+ }),
47
+ actors: Joi.string()
48
+ .valid('initiator', 'stepMembers', 'admin', 'workflowMembers')
49
+ .optional(),
50
+ oneWay: Joi.boolean().optional(),
51
+ });
52
+ /**
53
+ * Sealing rule for a workflow's transactions — see the `sealPolicy` note on
54
+ * `WorkflowModel`.
55
+ *
56
+ * `enabled` is required because the object exists to answer that question;
57
+ * `actors` and `graceDays` are only meaningful once it is on. `graceDays` is a
58
+ * whole number of days from zero (0 = permanent immediately) and is capped to
59
+ * keep a typo from writing a grace window measured in centuries.
60
+ */
61
+ const sealPolicySchema = Joi.object({
62
+ enabled: Joi.boolean().required(),
63
+ actors: Joi.string()
64
+ .valid('authorOrAdmin', 'author', 'admin', 'initiator', 'workflowMembers')
65
+ .optional(),
66
+ graceDays: Joi.number().integer().min(0).max(3650).optional(),
67
+ });
26
68
  /**
27
69
  * Validates a PERSISTED workflow, i.e. `DbWorkflowModel` — `_id` and the
28
70
  * timestamps are required here because this schema is only ever run against a
@@ -53,6 +95,12 @@ export const workflowModelSchema = Joi.object({
53
95
  // by the engine at transaction-create time, not by Mongo — see the
54
96
  // `uniqueKeys` note on `WorkflowModel`.
55
97
  uniqueKeys: Joi.array().items(Joi.string()).optional(),
98
+ // Whether archiving releases a `uniqueKeys` tuple (`'active'`, the pre-existing
99
+ // behaviour) or the tuple stays claimed for the life of the record (`'all'`).
100
+ // Read alongside `archivePolicy` — see both notes on `WorkflowModel`.
101
+ uniqueKeysScope: Joi.string().valid('active', 'all').optional(),
102
+ archivePolicy: archivePolicySchema.optional(),
103
+ sealPolicy: sealPolicySchema.optional(),
56
104
  });
57
105
  export function validateWorkflowModel(data) {
58
106
  const { error } = workflowModelSchema.validate(data, { abortEarly: false });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-t-workflow-typings",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Typings and interfaces for the ngx-t-workflows library.",
5
5
  "keywords": [
6
6
  "typings",
@@ -42,14 +42,14 @@
42
42
  "test": "echo \"Error: no test specified\" && exit 1"
43
43
  },
44
44
  "peerDependencies": {
45
- "ngx-t-forms-types": ">=0.0.16",
45
+ "ngx-t-forms-types": ">=0.0.27",
46
46
  "rxjs": ">=7.8.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "joi": "^18.0.2"
50
50
  },
51
51
  "devDependencies": {
52
- "ngx-t-forms-types": "^0.0.16",
52
+ "ngx-t-forms-types": "^0.0.27",
53
53
  "rimraf": "^6.1.3",
54
54
  "typescript": "^5.9.3"
55
55
  }