@nzelajs/node-approval
v0.1.0
Published
Org-chart approval node handler for the Nzela engine: task creation and aggregation (SINGLE/ALL/ANY/QUORUM), visa, delegation, desk queues, N-1 return routing, SLA, chain preview, anti-zombie reassignment. Vendor-free.
Maintainers
Readme
@nzelajs/node-approval
English below · Version francaise plus bas.
The org-chart approval node handler of the Nzela engine, the differentiator of the product: human approval tasks resolved on the org chart, aggregation modes, visa (counter-sign), one-off delegation, desk queues with auto-assignment, N-1 return routing, SLA timers, chain preview and anti-zombie reassignment. Opt-in: a generic app never loads it. Vendor-free.
English
Install
npm add @nzelajs/node-approval @nzelajs/coreWhat it provides
approvalNodeHandler({ outboxSigner, sensitiveFieldsOf? }): anApprovalCapableHandler.- On entry: runs the node's effects, resolves the approvers via the engine's
resolverFactory(fail-closed when absent) and creates one task per approver. The aggregation group is keyedcase:node:vVersion, so every re-submission cycle gets a fresh group and a RETURNED task of a previous cycle can never pollute the new aggregation. Desk queue tasks (queueUnitId, unassigned) are auto-assigned per the unit'sDeskRule(deterministic active/active split). Zero approvers above the requester: the node auto-approves (the chain adapts to the requester's rank). Schedules SLA timers (remind/escalate/expire) and the optionalwait.timeoutauto-close. - On decision (
decideTask, delegated byNzelaEngine): idempotent replay, claim-first on shared queues, assignee check, visa discipline (avisanode accepts ONLYvisa, a normal node refuses it), authorization via the optionalAuthorizer(the handler hands it an agnostic task attempt carrying the action and the node; the adapter maps it to a permission) with two safe exemptions (assigned desk queue task,selfConfirm), bounded form capture (node.capture.fields, anti-injection; sensitive fields stripped from the history snapshot and forwarded toupdateDataas the encryption classification),signal-incompletetargeted return (bypasses the N-1 resolution), aggregation (SINGLE/ALL/ANY/QUORUM, reject/return win; DELEGATED is ignored), sibling cancellation on ANY/QUORUM success, and the resulting case transition with the N-1 return-routing rule (tenantreturnRoutingoverride > previous approval echelon > blueprint fallback).
- On entry: runs the node's effects, resolves the approvers via the engine's
previewChain(deps, { tenantId, caseId }): history-aware validation chain. States derive from the recorded TRANSITIONS (never from votes, so the preview cannot contradict the status): approved / returned / rejected / current / upcoming, with the resolved approver per echelon, desk queue unit + agent in charge, and optional dispatch phases. Missing optional stores (directory, org, approvalTasks) only remove the matching enrichment, never crash.reassignOpenTasksForUser(deps, { tenantId, userId }): anti-zombie deprovisioning. Ends the user's assignments, re-resolves each open task on the org chart and reassigns it; a fully vacant chain parks the task in the fallback shared queue ("ADM", overridable), never orphaned. Best-effort per task, idempotent.- Building blocks:
aggregate,aggregateVisa,resolveReturnTarget,returnRoutingSteps(derives the N-1 return targets from the approve chain),pickDeskAssignee,isTaskTerminal,taskPermissionForAction, and theAPPROVAL_ACTIONSvocabulary constants.
Example
import { InMemoryNodeHandlerRegistry, NzelaEngine } from "@nzelajs/core";
import { approvalNodeHandler } from "@nzelajs/node-approval";
const handlers = new InMemoryNodeHandlerRegistry();
handlers.register(approvalNodeHandler({ outboxSigner }));
const engine = new NzelaEngine({
uow,
blueprints,
guards,
effects,
clock,
outboxSigner,
handlers,
resolverFactory: (repos) => myOrgChartResolver(repos), // org-driven approver resolution
});Security note: everything fails closed. No approver resolver or no approvalTasks store: the
operation throws. Unclaimed shared queue tasks cannot be decided. Form capture only accepts the
whitelisted capture.fields keys.
Francais
Installation
npm add @nzelajs/node-approval @nzelajs/coreCe que ca fournit
approvalNodeHandler({ outboxSigner, sensitiveFieldsOf? }): unApprovalCapableHandler.- A l'entree : execute les effets du noeud, resout les approbateurs via la
resolverFactorydu moteur (fail-closed si absente) et cree une tache par approbateur. Le groupe d'agregation est clecase:node:vVersion: chaque cycle de re-soumission obtient un groupe neuf, une tache RETURNED d'un cycle precedent ne pollue jamais la nouvelle agregation. Les taches de file de bureau (queueUnitId, non assignees) sont auto-affectees selon laDeskRulede l'unite (repartition active/active deterministe). Zero approbateur au-dessus du demandeur : le noeud s'auto-approuve (la chaine s'adapte au rang du demandeur). Planifie les timers SLA (remind/escalate/expire) et l'auto-fermeture optionnellewait.timeout. - A la decision (
decideTask, delegue parNzelaEngine) : rejeu idempotent, claim obligatoire sur file partagee, controle d'assigne, discipline du visa (un noeudvisan'accepte QUEvisa, un noeud normal le refuse), autorisation via l'Authorizeroptionnel (le handler lui passe une tentative de tache agnostique portant l'action et le noeud ; l'adapter la mappe vers une permission) avec deux dispenses sures (tache de file affectee,selfConfirm), capture de formulaire bornee (node.capture.fields, anti-injection ; champs sensibles retires de l'instantane d'historique et transmis aupdateDatacomme classification de chiffrement), renvoi ciblesignal-incomplete(contourne la resolution N-1), agregation (SINGLE/ALL/ANY/QUORUM, rejet/renvoi gagnent ; DELEGATED ignore), annulation des soeurs sur succes ANY/QUORUM, et la transition du dossier avec la regle de renvoi N-1 (override tenantreturnRouting> echelon d'approbation precedent > fallback du blueprint).
- A l'entree : execute les effets du noeud, resout les approbateurs via la
previewChain(deps, { tenantId, caseId }): parcours de validation conscient de l'historique. Les etats derivent des TRANSITIONS enregistrees (jamais des votes : l'apercu ne peut pas contredire le statut) : approved / returned / rejected / current / upcoming, avec l'approbateur resolu par echelon, bureau + agent en charge pour les files, et phases de dispatch optionnelles. Un store optionnel absent (directory, org, approvalTasks) retire seulement l'enrichissement correspondant, sans jamais planter.reassignOpenTasksForUser(deps, { tenantId, userId }): deprovisionnement anti-zombie. Clot les affectations de l'utilisateur, re-resout chaque tache ouverte sur l'organigramme et la reassigne ; une chaine entierement vacante gare la tache dans la file partagee de secours ("ADM", surchargeable), jamais orpheline. Best-effort par tache, idempotent.- Briques :
aggregate,aggregateVisa,resolveReturnTarget,returnRoutingSteps(calcule les cibles de renvoi N-1 depuis la chaine d'approbation),pickDeskAssignee,isTaskTerminal,taskPermissionForAction, et les constantes de vocabulaireAPPROVAL_ACTIONS.
Note de securite : tout echoue ferme. Pas de resolveur d'approbateurs ou pas de store
approvalTasks : l'operation leve. Une tache de file partagee non reclamee ne peut pas etre
decidee. La capture n'accepte que les cles whitelist de capture.fields.
License
Apache-2.0.
