Skip to content
Last updated: Sep 25, 2026

Run a Close Task as a Module Automation ​

OwnerClassificationVersionEffectiveNext reviewStatus
Platform EngineeringInternal0.22026-09-052026-12-05Draft

Purpose. Operating guide for how a CloseHub task step executes a real module engine instead of a hand-written result — the automation registry, the template binding that selects an engine, the parameter resolution that fills its input, and the dispatch to the owning module's worker. The companion Connected Close — GL Stitching covers how the same task resolves to the correct GL account across modules.

The chain (task step → automation → module engine) ​

A close task step does not compute its answer. It names an automation, the automation names a module workflow, and the orchestrator dispatches that workflow to the module that owns it.

closehub.tasktemplate                     ← the reusable close step (rollover source)
   │
closehub.tasktemplateactivity  (tta)      ← one row per step activity
   │  automationtaskid  ─────────────►  finbase.automationtask   ← the REGISTRY
   │  serviceid (legacy, Script Based)        │  application  (owning module)
   │                                          │  tasktype / endpoint  (the workflow)
   ▼                                          ▼
closehub.task  (instantiated per period)   orchestrator dispatch → MODULE worker
   │  glaccountcode · segment1..10 · perioddate · businessentity
   ▼
closehub.taskactivityparameter  {{tokens}} ← the step input, resolved from the task row

resolveStepForActivity reads tasktemplateactivity.automationtaskid. Because the binding lives on the template, every period's task inherits it — the change is rollover-safe and needs no per-period rework.

The registry — finbase.automationtask ​

Each row is one automation primitive, tagged with the owning module (application) and the workflow it runs (tasktype / endpoint).

ApplicationRepresentative endpoints
reconciliationrunaccountreconciliation, runfullreconciliation, autocertifyaccounts, setupreconciliation, transitionopenitems
cashcashapplication, runcollectionprocess, companyledgerageing
invoicecalculateinvoiceageing, generatesupplierageingreport
system (connectors/format)fetchFileFromDrive, fetchFileFromSFTP, uploadFileToSFTP, csvToJson, pdfToXlsx
closehubimportrollovertasks, rundailytaskautomations, generateexhaustivereport

Two dispatch classes ​

tasktypeMeaningUse
A named workflow endpoint (e.g. runaccountreconciliation)A job-tracked run dispatched through the orchestrator to the owning module's worker.The correct pattern — the module engine produces an auditable result.
Script Based (in-process SVC-* services)An in-process step that writes its result directly.Two cases. Result-asserting SVCs (asserted a status/difference, bypassing the engine) are deprecated and repointed onto workflow endpoints — e.g. SVC-MATCH-BANK → runaccountreconciliation. Lightweight import SVCs are retained: SVC-IMPORT-GL (82), SVC-IMPORT-SUBLEDGER (220), SVC-IMPORT-BANK (81) load a balance from a module or fixture without needing a tracked job.

How a step's input is filled ​

Step parameters are authored once on the template with placeholders and resolved at run time from the running task's own row — no other table is read.

SourceMechanism
Scalar tokenstask-parameter.resolver substitutes , , , , etc. from the closehub.task row. A single-placeholder value passes its typed value through (a number stays a number); embedded placeholders interpolate as strings.
The reconciled accountattachReconPeriodSummaryId stamps periodSummaryId onto the step input by resolving glaccountcode (+ segment1) → reconciliation.account → periodsummary. See Connected Close — GL Stitching.

An unknown token (author typo) throws at build time rather than reaching the engine as a literal — the downstream shell reports "completed" even on a failed flow, so silent failure is prevented by failing early. A known-but-null column resolves to "" and is reported back to the caller.

Declaring inputs for the config UI — finbase.automationparameter ​

The registry says which workflow runs; automationparameter declares that workflow's inputs, so the step-configuration dialog (FinAddStepDialog) renders a mapping form. One row per input, keyed to automationtaskid. The value column decides whether the input is developer-fixed or admin-facing.

valueMeaning in the config form
A Developer-fixed. Resolved from the task row at run time; not shown to the configurer.
NULLAdmin-fillable. Rendered as a blank field for a genuine business choice.

Example — Post Recon Adjustment (id 56): periodSummaryId and businessentity are fixed tokens; offsetAccount and offsetAccountName are NULL, so the configurer selects which account absorbs the reconciliation variance.

Where the automation layer is authored ​

Task structure and the automation layer come from different sources and are applied in sequence during onboarding. The Excel import owns structure; the automation layer is applied on top, exactly as fix-tasktemplate-phases.sql owns the phase layer.

LayerSourceOwns
Task structureExcel template import (importtasktemplate)tasks, activities, phases, dependencies
Automation binding + paramsBlueprint SQL (wire-connected-close-automations.sql)tta.automationtaskid / serviceid / actionurl + tasktemplateactivityparameter tokens
Config-UI declarationsdeclare-automation-parameters.sqlfinbase.automationparameter (the input schema)

The blueprint is idempotent and title-keyed (scoped to the tasktemplate title + activity), COALESCE-guarded so it never clobbers an existing binding. It writes to the template, never the instantiated task, and is re-run after every Excel re-import.

Two authoring paths — template vs user-defined ​

Automation reaches a step two ways, and the run-time one wins:

PathWhere authoredFieldWhen
TemplateBlueprint SQL / Excel (design time)tasktemplateactivity.automationtaskidInstantiated onto every period's task
User-definedThe UI, on a running task (FinAddStepDialog)taskactivity.userdefinedautomationid (+ isuserdefinedtask=true)Ad-hoc, one task

The FinAddStepDialog surface lets a preparer add a step to a live task, toggle Automated, pick a Trigger Task from the finbase.automationtask registry, and supply the inputs that automation declares (finbase.automationparameter) — persisting userdefinedautomationid, automatedtaskname, and the parameter values.

Dispatch resolves the effective automation per step (taskrunner.automationIdForActivity):

userdefinedautomationid                     -- the step's OWN automation (user-added), wins
  ?? tasktemplateactivity.automationtaskid   -- else the template's binding
  (?? serviceid)                             -- else the legacy SVC path

So a user-added step names its own automation and has no template behind it; a template-instantiated step falls back to its template's binding. Note this is the automation picker (a real recon-account/segment picker for glaccountcode does not exist yet — that binding is still free text; see Account Model & Chart of Accounts and GL Stitching).

Parameter rollover — template → instance ​

Both the binding and its parameters live on the template, so both must be copied onto each period's instance. Two code paths create instance activities, and each must copy parameters:

PathCopies parameters?
First-run / lazy (ensureActivities)Yes — reads tta.parameters, inserts taskactivityparameter.
Bulk rollover (taskrollover_createactivities.razor)PASS 2 copies activities; PASS 3 copies tasktemplateactivityparameter → taskactivityparameter (idempotent NOT EXISTS).

Before PASS 3 existed, rolled-over automated steps inherited the engine binding but no parameters, so the resolver had nothing to fill. The instance column taskactivityparameter.tasktemplateactivityid references taskactivity(id) — the instance, not the template — despite the name.

Dispatch and tracking ​

  1. The step's resolved input is handed to the orchestrator.
  2. The orchestrator reads the automation's endpoint and starts the workflow on the owning module's worker (recon endpoints run on Recon's worker, cash on Cash's, connectors on the system worker).
  3. The run is job-tracked: the trigger returns a job handle, not a result; the task polls coresystem/v1/jobqueues/{id} to a terminal Completed / Failed state and records the outcome against the task activity.

The compose-a-chain pattern ​

Primitives sequence across a task's activities to automate an end-to-end control. A fully automated bank reconciliation is four registry endpoints in order:

Fetch File From SFTP  →  Csv To Json  →  bankbalanceimport  →  Run Account Reconciliation
   pull the statement      normalise        load recon.bankbalance     account clears (diff → 0)

Worked example — Match Bank to GL ​

The "Bank Reconciliation - Operating Account" template's Match Bank to GL activity is bound to runaccountreconciliation (registry id 33, application reconciliation):

sql
UPDATE closehub.tasktemplateactivity tta
   SET automationtaskid = (SELECT id FROM finbase.automationtask
                            WHERE endpoint = 'runaccountreconciliation' LIMIT 1),
       serviceid = NULL                      -- off the SVC-MATCH-BANK write-back
  FROM closehub.tasktemplate tt
 WHERE tt.id = tta.tasktemplateid
   AND tt.title = 'Bank Reconciliation - Operating Account'
   AND tta.activity = 'Match Bank to GL';

At run time the step resolves periodSummaryId for the operating bank account and dispatches a real recon run on Recon's worker; the engine computes the account's unidentifieddifference, replacing the previous hand-written status.

Operational constraints ​

  • The wfw worker and Temporal must be running — every endpoint dispatch is an async job.
  • Script Based bindings are legacy; new automation binds a workflow endpoint.
  • The binding is template-level — change it on the tasktemplate / tasktemplateactivity, not the instantiated task, so rollover preserves it.
  • The automation only runs against the correct account when the GL-account resolution succeeds — see the companion guide.

Revision history ​

VersionDateAuthorChange
0.12026-09-01Platform EngineeringInitial draft — automation registry, template binding, parameter resolution, dispatch, chain pattern.
0.22026-09-05Platform EngineeringAdded the config-UI input declaration layer (automationparameter, fixed vs admin-fillable), where the automation layer is authored (blueprint SQL vs Excel structure), and parameter rollover (template→instance, razor PASS 3). Refined the Script Based framing to separate deprecated result-asserting SVCs from retained lightweight-import SVCs.
0.32026-09-17Platform EngineeringAdded the two authoring paths — template vs user-defined (taskactivity.userdefinedautomationid via FinAddStepDialog, wins over the template binding); documented the dispatch precedence and noted the automation picker exists while the account/segment picker does not.

Finaisse Internal — Confidential. Access-restricted; not for external distribution.