VX/ACMS - Application Control and Management System
Overview
VX/ACMS is a native C++20 reimplementation of VSI ACMS (Application Control and Management System) for Linux. It provides source-compatible task execution for migrated OpenVMS applications: existing TDF / GDF / ADF sources compile through the ADU, existing COBOL and C step procedures run unchanged, and existing calls to ACMS$ services resolve against the same signatures and status codes as on VMS.
VX/ACMS targets the single-server deployment model used by the majority of production ACMS estates. Multi-node clustering and DDTM/XA are v2 scope items.
<!-- Webflow embed (min). Source: vx-acms-page-technical/acms-tech-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … -->
<div class="s7-tech-embed" data-vxacmsd-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="components">Component map</h2><p>
VX/ACMS preserves the ACMS component model verbatim. Each VMS process class has an exact
Linux counterpart with the same responsibilities; only the IPC substrate changes (POSIX
shared memory and UNIX-domain sockets instead of VMS global sections and DECnet links).
</p><div class="table-wrapper"><table><thead><tr><th>VMS process</th><th>Linux binary</th><th>Role</th><th>Status</th></tr></thead><tbody><tr><td>ACC</td><td><code>acmsd</code></td><td>Root daemon. Owns the SQLite control / audit / queue databases; spawns child processes; listens on the operator socket (<code>/run/acms/operator.sock</code>).</td><td><span class="ac-live">live</span></td></tr><tr><td>EXC</td><td><code>acms-exc</code></td><td>Per-application task interpreter. Drives IF / WHILE / REPEAT / GOTO / ATOMIC / EXCHANGE / PROCESSING steps; loads task <code>.so</code> files via <code>dlopen</code>; manages workspace pool.</td><td><span class="ac-live">live</span></td></tr><tr><td>SP</td><td><code>acms-sp</code></td><td>Procedure server pool. Loads server group <code>.so</code> files; dispatches PROCESSING CALL steps from EXC via UDS; maintains server process pool sizing.</td><td><span class="ac-live">live</span></td></tr><tr><td>CP</td><td><code>acms-cp</code></td><td>Per-submitter FORMS$ / menu process. Bridges terminal I/O through VSI DECforms <code>libvxdf*</code> on Linux. Handles EXCHANGE steps bound to forms.</td><td><span class="ac-v1">v1 soon</span></td></tr><tr><td>QTI</td><td><code>acms-qti</code></td><td>Queued task initiator. Drains the SQLite-backed task queue; submits deferred tasks to EXC; honours priority ordering.</td><td><span class="ac-v1">v1 soon</span></td></tr><tr><td>ATL</td><td><code>acms-atl</code></td><td>Audit trail logger. Receives structured log events from all other components via the SWL library; writes to the audit database.</td><td><span class="ac-live">live</span></td></tr><tr><td>SWL</td><td><code>liblogswl</code></td><td>In-process event log library. Wraps all structured logging; consumed by ACC / EXC / SP / QTI / ATL.</td><td><span class="ac-live">live</span></td></tr><tr><td>SI (agent)</td><td><code>libacms_si.so</code></td><td>Agent-callable surface. Provides <code>ACMS$CALL</code>, <code>ACMS$SIGN_IN</code>, and 10 other implemented services. Linked by submitter programs.</td><td><span class="ac-live">live (12 svc)</span></td></tr><tr><td>ADU</td><td><code>adu</code> / <code>adu_compile</code></td><td>Application Definition Utility. Parses TDF / GDF / ADF / MDF; resolves CDD$ records; emits C++; compiles to <code>.so</code>.</td><td><span class="ac-live">live (MDF v1)</span></td></tr></tbody></table></div><div class="callout"><strong>No RR server in v1.</strong> The Remote Request (RR) inter-node server is a v2 item.
All inter-process communication in v1 is on a single Linux host via UNIX-domain sockets and
shared memory.
</div><h2 id="workspaces">Workspace model</h2><p>
Workspaces are the shared-memory records that flow between a submitter, the EXC task interpreter,
and SP procedure servers. VX/ACMS maps the VMS global section model onto POSIX
<code>shm_open</code> / <code>mmap</code>; the layout of each workspace is determined by its
CDD record and is verified at install time by matching a <code>layout_hash</code>.
</p><h6>Workspace lifetimes</h6><div class="table-wrapper"><table><thead><tr><th>Lifetime</th><th>VMS equivalent</th><th>Scope</th></tr></thead><tbody><tr><td><code>TASK</code></td><td>Task workspace</td><td>Allocated at task call entry; released at <code>EXIT TASK</code> or exception unwind.</td></tr><tr><td><code>USER</code></td><td>Per-submitter workspace</td><td>Lives for the duration of the submitter's ACMS session (sign-in to sign-out).</td></tr><tr><td><code>GROUP</code></td><td>Group workspace</td><td>Shared across all tasks in the same application group; lifetime = application runtime.</td></tr><tr><td><code>SYSTEM</code></td><td>System workspace</td><td>Shared system-wide. Includes the three ACMS-maintained system workspaces
(<code>ACMS$SELECTION_STRING</code>, <code>ACMS$PROCESSING_STATUS</code>,
<code>ACMS$TASK_INFORMATION</code>).</td></tr></tbody></table></div><h6>Workspace handle</h6><p>Every workspace in VX/ACMS is identified by a handle passed through the CBOR IPC frames:</p>
<pre>{
shm_id: uint32 // POSIX shm name index
offset: uint64 // byte offset within the mapping
length: uint32 // record byte length
version: uint16 // layout_hash low word (checked at first access)
}</pre>
<h6>CDD integration</h6><p>
Record layouts are authoritative in the CDD database (<code>banking.cdd.db</code>) built by
<code>vxcdd --add</code>. Both the ADU compiler and the COBOL/C compilers consume CDD views —
no hand-maintained struct duplicates. The <code>layout_hash</code> is a deterministic hash of
the flattened CDD field list; a mismatch between the task <code>.so</code> and the server
<code>.so</code> causes a hard install-time error.
</p><div class="callout warn"><strong>TASK ARGUMENTS copy boundary.</strong> When a submitter passes workspaces to EXC
as <code>TASK ARGUMENTS</code>, the contents are copied across the submitter/EXC process split.
All other workspace access (EXC ↔ SP) is zero-copy shared memory.
</div><h6>System workspaces</h6><div class="table-wrapper"><table><thead><tr><th>Name</th><th>Content</th></tr></thead><tbody><tr><td><code>ACMS$SELECTION_STRING</code></td><td>Task selector passed by the submitter at <code>ACMS$CALL</code> time.</td></tr><tr><td><code>ACMS$PROCESSING_STATUS</code></td><td>Status returned from the most recent PROCESSING step.</td></tr><tr><td><code>ACMS$TASK_INFORMATION</code></td><td>Submitter identity, task name, call flags; populated by EXC at task entry.</td></tr></tbody></table></div><h2 id="tdl">TDL — Task Definition Language</h2><p>
TDL is the language you use to define ACMS tasks, task groups, and applications.
The ADU compiler processes four TDL file types: TDF (tasks), GDF (groups / server definitions),
ADF (applications), and MDF (menus). All four compile unchanged through VX/ACMS.
</p><h6>Task Definition File (TDF)</h6><p>A TDF defines one or more tasks. Each task declares its workspaces, task arguments, and a
BLOCK of steps. A minimal task:</p>
<pre><span class="kw">REPLACE TASK</span> FUNDS_TRANSFER
<span class="kw">WORKSPACES ARE</span>
ACCOUNT_WKSP,
TRANSFER_WKSP,
AUDIT_WKSP;
<span class="kw">TASK ARGUMENTS ARE</span>
ACCOUNT_WKSP <span class="kw">WITH ACCESS</span> MODIFY,
AUDIT_WKSP <span class="kw">WITH ACCESS</span> WRITE;
<span class="kw">BLOCK</span>
<span class="kw">EXCEPTION HANDLER</span>
<span class="kw">MOVE</span> <span class="str">"FAILED"</span> <span class="kw">TO</span> AUDIT_WKSP.MSG;
<span class="kw">EXIT TASK</span>;
<span class="kw">PROCESSING CALL</span> READ_ACCOUNT
<span class="kw">USING</span> ACCOUNT_WKSP;
<span class="kw">IF</span> ACCOUNT_WKSP.BALANCE < TRANSFER_WKSP.AMOUNT <span class="kw">THEN</span>
<span class="kw">MOVE</span> <span class="str">"NSF"</span> <span class="kw">TO</span> AUDIT_WKSP.MSG;
<span class="kw">EXIT TASK</span>;
<span class="kw">END IF</span>;
<span class="kw">PROCESSING CALL</span> DEBIT_ACCOUNT
<span class="kw">USING</span> ACCOUNT_WKSP, TRANSFER_WKSP;
<span class="kw">END BLOCK</span>;
<span class="kw">END DEFINITION</span>;</pre>
<h6>Step types</h6><div class="table-wrapper"><table><thead><tr><th>Step</th><th>Purpose</th></tr></thead><tbody><tr><td><code>PROCESSING CALL name USING wksp, …</code></td><td>Dispatch to a server procedure in an SP process. The named procedure must appear in the GDF.</td></tr><tr><td><code>EXCHANGE USING wksp WRITING form …</code></td><td>Submit a FORMS$ exchange step; data flows through a CP process to the terminal.</td></tr><tr><td><code>ATOMIC BEGIN … END ATOMIC</code></td><td>Begin a SQLite-backed ATOMIC bracket (DDTM shape preserved). All enclosed steps are within one transaction context.</td></tr><tr><td><code>GET ERROR MESSAGE</code></td><td>Copy the current ACMS exception text into a workspace field.</td></tr><tr><td><code>MOVE value TO wksp.field</code></td><td>Assign a literal or workspace field to another workspace field inside EXC (no server dispatch).</td></tr><tr><td><code>IF … THEN … ELSE … END IF</code></td><td>Conditional based on workspace field comparison.</td></tr><tr><td><code>WHILE condition DO … END WHILE</code></td><td>Loop while condition holds.</td></tr><tr><td><code>REPEAT … END REPEAT</code></td><td>Unconditional loop; terminated by <code>EXIT LOOP</code>.</td></tr><tr><td><code>GOTO label</code></td><td>Unconditional branch to a labelled step within the same BLOCK.</td></tr><tr><td><code>EXIT TASK</code></td><td>Terminate the task normally.</td></tr></tbody></table></div><h6>Group Definition File (GDF)</h6><p>A GDF defines a server group: which server image (.so) to load, which procedures it exports,
and lifecycle hooks:</p>
<pre><span class="kw">REPLACE SERVER GROUP</span> BANKING_SERVERS
<span class="kw">PROCEDURE SERVER IMAGE IS</span> <span class="str">"/opt/acms/servers/banking_servers.so"</span>;
<span class="kw">INITIALIZATION PROCEDURE IS</span> BANKING_INIT;
<span class="kw">TERMINATION PROCEDURE IS</span> BANKING_TERM;
<span class="kw">CANCEL PROCEDURE IS</span> BANKING_CANCEL;
<span class="kw">PROCEDURES ARE</span>
READ_ACCOUNT,
DEBIT_ACCOUNT,
LOG_AUDIT;
<span class="kw">END DEFINITION</span>;</pre>
<h6>Application Definition File (ADF)</h6><p>An ADF registers tasks and server groups under a named ACMS application. It maps task selectors
(used at <code>ACMS$CALL</code> time) to TDF-defined tasks and GDF-defined server groups:</p>
<pre><span class="kw">REPLACE APPLICATION</span> BANKING
<span class="kw">TASKS ARE</span>
FUNDS_TRANSFER <span class="kw">USING TASK GROUP</span> BANKING_TASKS;
<span class="kw">SERVER GROUPS ARE</span>
BANKING_SERVERS;
<span class="kw">END DEFINITION</span>;</pre>
<h6>TDL keyword reference</h6><div class="kw-grid"><div>ACCESS</div><div>ADF</div><div>AND</div><div>APPLICATION</div><div>ATOMIC</div><div>BACKUP</div><div>BEGIN</div><div>BLOCK</div><div>CALL</div><div>CANCEL</div><div>CLEAR</div><div>DEFINE</div><div>DELETE</div><div>DO</div><div>ELSE</div><div>END</div><div>EXCEPTION</div><div>EXCHANGE</div><div>EXIT</div><div>FOR</div><div>FROM</div><div>GDF</div><div>GET</div><div>GOTO</div><div>GROUP</div><div>HANDLER</div><div>IF</div><div>IMAGE</div><div>IN</div><div>INITIALIZATION</div><div>IS</div><div>LOOP</div><div>MDF</div><div>MESSAGE</div><div>MODIFY</div><div>MOVE</div><div>NOT</div><div>ON</div><div>OR</div><div>PROCEDURE</div><div>PROCESSING</div><div>READ</div><div>REPEAT</div><div>REPLACE</div><div>SERVER</div><div>TASK</div><div>TDF</div><div>TERMINATION</div><div>THEN</div><div>TO</div><div>USING</div><div>WHILE</div><div>WITH</div><div>WORKSPACES</div><div>WRITE</div></div><h2 id="services">ACMS$ services</h2><p>
Submitter programs link against <code>libacms_si.so</code> and call ACMS$ services to sign in,
submit tasks, and sign out. VX/ACMS implements the 12 services used across production estates;
the full VMS-compatible signature and status code set is preserved.
</p><div class="table-wrapper"><table><thead><tr><th>Service</th><th>Purpose</th><th>Status</th></tr></thead><tbody><tr><td><code>ACMS$SIGN_IN</code></td><td>Authenticate the submitter and open an ACMS session. Validates against the PAM stack (or the internal rights store). Returns a session handle.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$SIGN_OUT</code></td><td>Terminate the submitter's ACMS session and release all USER-lifetime workspaces.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$CALL</code></td><td>Submit a task by application name and task selector. Blocks (or returns immediately with <code>ACMS$_PENDING</code> if non-blocking) until the task completes.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$START_CALL</code></td><td>Asynchronous task submission. Pairs with <code>ACMS$WAIT_CALL</code> for the completion rendezvous.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$WAIT_CALL</code></td><td>Block until a previously <code>ACMS$START_CALL</code>-submitted task completes.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$CANCEL_CALL</code></td><td>Cancel an outstanding <code>ACMS$START_CALL</code> submission before it has been dispatched by EXC.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$RAISE_STEP_EXCEPTION</code></td><td>Raise an exception from inside a server step procedure. The EXC EXCEPTION HANDLER fires at the nearest enclosing BLOCK.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$SET_SELECTION_STRING</code></td><td>Pre-populate the <code>ACMS$SELECTION_STRING</code> system workspace before a <code>ACMS$CALL</code>.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$GET_TASK_INFO</code></td><td>Read the current task's name, submitter identity, and call flags from <code>ACMS$TASK_INFORMATION</code>.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>ACMS$EXCHANGE_IO</code></td><td>Perform a forms-exchange I/O step from a server procedure (advanced use). Requires CP process.</td><td><span class="ac-v1">v1 soon (CP)</span></td></tr><tr><td><code>ACMS$QUEUE_TASK</code></td><td>Enqueue a task for deferred execution by QTI. Returns immediately; task runs when QTI dispatches it.</td><td><span class="ac-v1">surface live, QTI v1</span></td></tr><tr><td><code>ACMS$GET_ERROR_MESSAGE</code></td><td>Retrieve the text of the most recent ACMS exception. Corresponds to the <code>GET ERROR MESSAGE</code> TDL step when called from a server procedure.</td><td><span class="ac-live">live</span></td></tr></tbody></table></div><div class="callout"><strong>Status codes.</strong> VX/ACMS uses the same 32-bit ACMS status codes as VMS —
<code>ACMS_NORMAL</code> (1), <code>ACMS$_NOSUCHAPP</code>, <code>ACMS$_STEP_ERR</code>,
etc. A full table of status codes is available in Appendix A of the Programmer's
Reference Manual.
</div><h6>Calling ACMS$CALL from C</h6>
<pre><span class="cmt">/* Minimal ACMS$CALL from C — funds transfer */</span>
#include <acms_si.h>
acms_session_t sess;
acms_status_t rc;
<span class="cmt">/* sign in */</span>
rc = ACMS$SIGN_IN(<span class="str">"operator"</span>, &sess);
if (rc != ACMS_NORMAL) exit(1);
<span class="cmt">/* populate TASK ARGUMENTS workspaces via shm handle */</span>
memcpy(ws_account.shm_ptr, &account_rec, sizeof(account_rec));
memcpy(ws_transfer.shm_ptr, &transfer_rec, sizeof(transfer_rec));
<span class="cmt">/* call task */</span>
rc = ACMS$CALL(&sess, <span class="str">"BANKING"</span>, <span class="str">"FUNDS_TRANSFER"</span>,
ws_array, 2, 0);
if (rc != ACMS_NORMAL) handle_error(rc);
ACMS$SIGN_OUT(&sess);</pre>
<h2 id="server-programming">Server programming</h2><p>
Step procedures are compiled into server group <code>.so</code> files and loaded by the SP
process via <code>dlopen</code>. VX/ACMS preserves the VMS server module ABI so that existing
COBOL and C procedure source compiles and runs without modification.
</p><h6>Server module ABI (C / C++)</h6><p>Each server group <code>.so</code> must export an <code>acms_server_module_v1</code> struct:</p>
<pre><span class="kw">typedef struct</span> {
uint32_t version; <span class="cmt">/* = ACMS_SERVER_MODULE_VERSION (1) */</span>
const char *group_name; <span class="cmt">/* must match GDF SERVER GROUP name */</span>
acms_proc_entry_t *procedures; <span class="cmt">/* null-terminated array */</span>
acms_vfn_t on_init; <span class="cmt">/* INITIALIZATION PROCEDURE — may be NULL */</span>
acms_vfn_t on_term; <span class="cmt">/* TERMINATION PROCEDURE — may be NULL */</span>
acms_vfn_t on_cancel; <span class="cmt">/* CANCEL PROCEDURE — may be NULL */</span>
} acms_server_module_v1;</pre>
<p>Each procedure entry maps a name string to a function pointer:</p>
<pre><span class="kw">typedef struct</span> {
const char *name; <span class="cmt">/* must match GDF PROCEDURES ARE name */</span>
acms_proc_fn_t fn; <span class="cmt">/* unsigned int fn(WKSP *a, WKSP *b, …) */</span>
} acms_proc_entry_t;
<span class="cmt">/* Procedure signature: one pointer per workspace declared USING */</span>
<span class="kw">unsigned int</span> READ_ACCOUNT(ACCOUNT_WKSP_t *acct) {
<span class="cmt">/* read account record — field access via struct layout */</span>
if (acct->balance < 0) <span class="kw">return</span> ACMS$_INSUFFICIENT_FUNDS;
<span class="kw">return</span> ACMS_NORMAL;
}</pre>
<h6>COBOL step procedures</h6><p>COBOL procedures use the VMS-shape signature: workspace records appear as <code>BY REFERENCE</code>
arguments in the <code>PROCEDURE DIVISION USING</code> clause, and the return status is conveyed
through <code>RETURN-CODE</code> (not a function return value):</p>
<pre><span class="kw">IDENTIFICATION DIVISION.</span>
PROGRAM-ID. READ-ACCOUNT.
<span class="kw">DATA DIVISION.</span>
WORKING-STORAGE SECTION.
<span class="kw">LINKAGE SECTION.</span>
01 ACCOUNT-WKSP.
05 ACCOUNT-ID PIC X(16).
05 BALANCE PIC S9(12)V99 COMP-3.
05 STATUS-MSG PIC X(40).
<span class="kw">PROCEDURE DIVISION USING</span> ACCOUNT-WKSP.
EXEC SQL
SELECT BALANCE INTO :ACCOUNT-WKSP.BALANCE
FROM ACCOUNTS WHERE ID = :ACCOUNT-WKSP.ACCOUNT-ID
END-EXEC.
IF SQLCODE <> 0
MOVE <span class="str">"NOT FOUND"</span> TO ACCOUNT-WKSP.STATUS-MSG
MOVE ACMS-STEP-ERROR TO RETURN-CODE
ELSE
MOVE ACMS-NORMAL TO RETURN-CODE
END-IF.
STOP RUN.</pre>
<div class="callout"><strong>layout_hash enforcement.</strong> The <code>layout_hash</code> in the workspace
handle must match the hash compiled into the server <code>.so</code>. A mismatch is caught
at install time by <code>acmsctl install</code> — not at runtime. Recompile both the task
and server <code>.so</code> after any CDD record change.
</div><h6>Exception handling from server code</h6><p>
Call <code>ACMS$RAISE_STEP_EXCEPTION</code> to propagate a step-level error back to the
EXC EXCEPTION HANDLER without using a C++ exception or a non-zero return value directly:
</p>
<pre><span class="kw">unsigned int</span> DEBIT_ACCOUNT(ACCOUNT_WKSP_t *acct,
TRANSFER_WKSP_t *xfr)
{
<span class="kw">if</span> (xfr->amount > acct->balance) {
ACMS$RAISE_STEP_EXCEPTION(ACMS$_INSUFFICIENT_FUNDS,
<span class="str">"Balance too low"</span>);
<span class="kw">return</span> ACMS_NORMAL; <span class="cmt">/* control never reaches here */</span>
}
acct->balance -= xfr->amount;
<span class="kw">return</span> ACMS_NORMAL;
}</pre>
<h2 id="build-pipeline">Build pipeline</h2><p>
The ADU (Application Definition Utility) converts TDL sources to native Linux shared objects
through a four-step pipeline. All steps are exposed through the <code>adu_compile</code>
command-line tool.
</p><h6>Pipeline steps</h6><div class="table-wrapper"><table><thead><tr><th>Step</th><th>Command</th><th>Input</th><th>Output</th></tr></thead><tbody><tr><td>1. CDD compile</td><td><code>vxcdd --add <name>.cdo</code></td><td>CDD object definition (<code>.cdo</code>)</td><td>CDD database (<code>*.cdd.db</code>)</td></tr><tr><td>2. GDF compile</td><td><code>adu_compile server.gdf --cdd *.cdd.db</code></td><td>Group definition file + CDD database</td><td><code>server_group.cpp</code> → <code>server_group.so</code></td></tr><tr><td>3. TDF compile</td><td><code>adu_compile task.tdf --gdf server.gdf --compile</code></td><td>Task definition file + GDF</td><td><code>task_module.cpp</code> → <code>task.so</code></td></tr><tr><td>4. Install</td><td><code>acmsctl install application <APP></code></td><td>ADF + compiled <code>.so</code> files</td><td>Application registered in <code>control.db</code>; EXC/SP notified.</td></tr></tbody></table></div><h6>adu_compile flags</h6><div class="table-wrapper"><table><thead><tr><th>Flag</th><th>Purpose</th></tr></thead><tbody><tr><td><code>--cdd <path></code></td><td>Path to the CDD database (required for workspace resolution).</td></tr><tr><td><code>--gdf <path></code></td><td>Path to the compiled GDF (required for TDF step resolution).</td></tr><tr><td><code>--compile</code></td><td>Invoke the system C++ compiler on the generated <code>.cpp</code> and link the <code>.so</code>.</td></tr><tr><td><code>--output-dir <dir></code></td><td>Write generated files to this directory (default: same as input).</td></tr><tr><td><code>--cxx <compiler></code></td><td>C++ compiler binary (default: <code>g++</code>).</td></tr><tr><td><code>--cxxflags <flags></code></td><td>Additional flags forwarded to the C++ compiler.</td></tr><tr><td><code>--verbose</code></td><td>Print resolver and emitter trace to stderr.</td></tr><tr><td><code>--dry-run</code></td><td>Parse and resolve; do not emit C++ or compile.</td></tr></tbody></table></div><h6>Full build example</h6>
<pre><span class="cmt"># 1 — compile the CDD record repository</span>
vxcdd --add banking.cdo
<span class="cmt"># 2 — compile the server group</span>
adu_compile banking_servers.gdf \
--cdd banking.cdd.db \
--compile \
--output-dir build/
<span class="cmt"># 3 — compile all task definitions</span>
adu_compile funds_transfer.tdf enquiry.tdf audit_report.tdf \
--gdf banking_servers.gdf \
--cdd banking.cdd.db \
--compile \
--output-dir build/
<span class="cmt"># 4 — install the application</span>
acmsctl install application BANKING \
--adf banking.adf \
--task-dir build/ \
--server-dir build/</pre>
<h6>Incremental rebuild</h6><p>
Re-run only the steps for the files you changed. If you modify a CDD record, you must recompile
both the task <code>.so</code> and the server <code>.so</code> that reference that workspace —
the <code>layout_hash</code> check at install time will catch any mismatch. Use
<code>acmsctl reload application BANKING</code> to hot-reload an updated application without
restarting <code>acmsd</code>.
</p><div class="callout"><strong>Generated C++ is first-class source.</strong> Check <code>build/*.cpp</code> into
version control alongside your TDF sources. A <code>git diff</code> of the generated files
tells you precisely what semantic changes an ADU version bump or TDF edit produced.
</div><h2 id="quickref">Quick reference</h2><h6>TDF step summary</h6><div class="table-wrapper"><table><thead><tr><th>Step</th><th>Syntax (abbreviated)</th><th>EXC action</th></tr></thead><tbody><tr><td>PROCESSING CALL</td><td><code>PROCESSING CALL proc USING wksp, …</code></td><td>Dispatch to SP; workspace handles passed by reference.</td></tr><tr><td>EXCHANGE</td><td><code>EXCHANGE USING wksp WRITING form TO wksp</code></td><td>Route through CP; FORMS$TRANSCEIVE called.</td></tr><tr><td>MOVE</td><td><code>MOVE "lit" TO wksp.field</code></td><td>Assign literal or field to workspace field in-process.</td></tr><tr><td>ATOMIC BEGIN</td><td><code>ATOMIC BEGIN … END ATOMIC</code></td><td>Open SQLite transaction bracket; all steps inside are atomic.</td></tr><tr><td>GET ERROR MESSAGE</td><td><code>GET ERROR MESSAGE INTO wksp.field</code></td><td>Copy current exception text to workspace.</td></tr><tr><td>IF / ELSE / END IF</td><td><code>IF wksp.field op value THEN … END IF</code></td><td>Conditional; evaluated by EXC interpreter.</td></tr><tr><td>WHILE / END WHILE</td><td><code>WHILE condition DO … END WHILE</code></td><td>Loop until condition false.</td></tr><tr><td>REPEAT / END REPEAT</td><td><code>REPEAT … EXIT LOOP … END REPEAT</code></td><td>Unconditional loop.</td></tr><tr><td>GOTO</td><td><code>GOTO label</code></td><td>Jump to labelled step within BLOCK.</td></tr><tr><td>EXIT TASK</td><td><code>EXIT TASK</code></td><td>Normal task termination.</td></tr><tr><td>EXCEPTION HANDLER</td><td><code>EXCEPTION HANDLER steps; END HANDLER</code></td><td>Catch any step exception in the enclosing BLOCK.</td></tr></tbody></table></div><h6>ACMS$ service quick reference</h6><div class="table-wrapper"><table><thead><tr><th>Service</th><th>One-liner</th><th>v1?</th></tr></thead><tbody><tr><td><code>ACMS$SIGN_IN</code></td><td>Open session; authenticate submitter.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$SIGN_OUT</code></td><td>Close session; release USER workspaces.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$CALL</code></td><td>Synchronous task submission.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$START_CALL</code></td><td>Asynchronous task submission.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$WAIT_CALL</code></td><td>Wait for async task completion.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$CANCEL_CALL</code></td><td>Cancel pending async submission.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$RAISE_STEP_EXCEPTION</code></td><td>Raise exception from server step.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$SET_SELECTION_STRING</code></td><td>Pre-populate selection string workspace.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$GET_TASK_INFO</code></td><td>Read task identity from system workspace.</td><td><span class="ac-live">✓</span></td></tr><tr><td><code>ACMS$EXCHANGE_IO</code></td><td>Forms exchange I/O from server code.</td><td><span class="ac-v1">v1 soon</span></td></tr><tr><td><code>ACMS$QUEUE_TASK</code></td><td>Enqueue task for deferred QTI dispatch.</td><td><span class="ac-v1">partial</span></td></tr><tr><td><code>ACMS$GET_ERROR_MESSAGE</code></td><td>Retrieve exception text string.</td><td><span class="ac-live">✓</span></td></tr></tbody></table></div><h6>acmsctl operator commands</h6><div class="table-wrapper"><table><thead><tr><th>Command</th><th>Purpose</th></tr></thead><tbody><tr><td><code>acmsctl install application APP --adf app.adf</code></td><td>Register application; verify layout hashes; notify EXC/SP.</td></tr><tr><td><code>acmsctl reload application APP</code></td><td>Hot-reload updated <code>.so</code> files without restarting acmsd.</td></tr><tr><td><code>acmsctl start application APP</code></td><td>Activate application for task submission.</td></tr><tr><td><code>acmsctl stop application APP</code></td><td>Quiesce application; drain in-flight tasks.</td></tr><tr><td><code>acmsctl call APP TASK</code></td><td>Test-call a task from the operator shell.</td></tr><tr><td><code>acmsctl show application APP</code></td><td>Print task list, server group status, active submitters.</td></tr><tr><td><code>acmsctl show statistics</code></td><td>Aggregate call counts, step latency percentiles, queue depth.</td></tr></tbody></table></div><h6>Key file paths (default layout)</h6><div class="table-wrapper"><table><thead><tr><th>Path</th><th>Contents</th></tr></thead><tbody><tr><td><code>/opt/acms/bin/</code></td><td><code>acmsd</code>, <code>acms-exc</code>, <code>acms-sp</code>, <code>acms-qti</code>, <code>acms-atl</code>, <code>acmsctl</code></td></tr><tr><td><code>/opt/acms/lib/</code></td><td><code>libacms_si.so</code>, <code>liblogswl.so</code></td></tr><tr><td><code>/opt/acms/applications/</code></td><td>Installed task and server <code>.so</code> files per application</td></tr><tr><td><code>/var/lib/acms/</code></td><td><code>control.db</code>, <code>audit.db</code>, <code>queue.db</code></td></tr><tr><td><code>/run/acms/</code></td><td>UNIX-domain sockets: <code>operator.sock</code>, per-EXC / per-SP sockets</td></tr><tr><td><code>/etc/acms/applications.conf</code></td><td>Boot-time application list; paths override <code>acmsctl install</code> defaults</td></tr></tbody></table></div></div>