diff --git a/base/mp_init.sas b/base/mp_init.sas
index ee2231b..68b4e19 100644
--- a/base/mp_init.sas
+++ b/base/mp_init.sas
@@ -41,8 +41,9 @@
&prefix._INIT_NUM /* initialisation time as numeric */
&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
&prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */
+ &prefix.PROCESSMODE
+ &prefix._STPSRV_HEADER_LOC
;
-
%let sasjs_prefix=&prefix;
data _null_;
diff --git a/tests/viyaonly/mv_castabsave.test.sas b/tests/viyaonly/mv_castabsave.test.sas
new file mode 100644
index 0000000..20bb0f6
--- /dev/null
+++ b/tests/viyaonly/mv_castabsave.test.sas
@@ -0,0 +1,158 @@
+/**
+ @file
+ @brief Testing mv_castabsave macro
+
+
SAS Macros
+ @li mf_uid.sas
+ @li mp_assert.sas
+ @li mp_assertscope.sas
+ @li mv_castabsave.sas
+
+**/
+
+options mprint;
+
+/* -------------------------------------------------------------------- */
+/* Setup: start a CAS session and load a table that has a tracked */
+/* source file so mv_castabsave can discover it via the REST API */
+/* -------------------------------------------------------------------- */
+cas mysess;
+caslib _all_ assign;
+
+%let testcaslib=Public;
+
+proc cas;
+ table.caslibInfo result=r / ;
+ found=0;
+ do row over r.CASLibInfo;
+ if upcase(row.Name)=upcase("&testcaslib") then found=1;
+ end;
+ if found=0 then do;
+ print "ERROR: caslib &testcaslib not available";
+ exit;
+ end;
+quit;
+%put NOTE: Using testcaslib=&testcaslib;
+
+%let tab1=T%mf_uid();
+
+/* Load sashelp.class into CAS, save as sashdat, reload from that file
+ so the table has a tracked source path (needed for REST discovery) */
+proc casutil;
+ load data=sashelp.class
+ outcaslib="&testcaslib" casout="&tab1" replace;
+ save casdata="&tab1" incaslib="&testcaslib"
+ casout="&tab1..sashdat" outcaslib="&testcaslib" replace;
+ /* Drop any existing global-scope version before promoting */
+ /* runs twice (with quiet) as first would drop local scope if exists */
+ droptable casdata="&tab1" incaslib="&testcaslib" quiet;
+ droptable casdata="&tab1" incaslib="&testcaslib" quiet;
+
+ load casdata="&tab1..sashdat" incaslib="&testcaslib"
+ casout="&tab1" outcaslib="&testcaslib" promote;
+quit;
+
+libname mylib cas caslib="&testcaslib";
+
+
+/* -------------------------------------------------------------------- */
+%put TEST 1 - save in-memory table back to disk + no scope leakage;
+/* -------------------------------------------------------------------- */
+
+/* Source file is removed so that the reload proves mv_castabsave
+ created the file from scratch, not that a prior version existed */
+proc casutil;
+ deletesource casdata="&tab1..sashdat"
+ incaslib="&testcaslib" quiet;
+quit;
+
+/* Insert a sentinel row - it must survive the full save/drop/reload */
+data work.appendme;
+ set mylib.&tab1;
+ name='TESTROW';
+ output;
+ stop;
+proc casutil;
+ load data=work.appendme casout="&tab1" outcaslib="&testcaslib" append;
+quit;
+
+%mp_assertscope(SNAPSHOT)
+
+%mv_castabsave(lib=mylib, table=&tab1, mdebug=1)
+
+%mp_assertscope(COMPARE,
+ desc=Check mv_castabsave does not leak macro variables into GLOBAL scope,
+ ignorelist=MC0_JADP1LEN MC0_JADP2LEN MC0_JADP3LEN MC0_JADPNUM MC0_JADVLEN
+)
+
+proc casutil;
+ droptable casdata="&tab1" incaslib="&testcaslib" quiet;
+ droptable casdata="&tab1" incaslib="&testcaslib" quiet;
+ load casdata="&tab1..sashdat" incaslib="&testcaslib"
+ casout="&tab1" outcaslib="&testcaslib" promote;
+quit;
+
+%let _rowcount=0;
+proc sql noprint;
+ select count(*) into :_rowcount
+ from mylib.&tab1
+ where name='TESTROW';
+quit;
+
+%mp_assert(
+ iftrue=(&_rowcount=1),
+ desc=Check inserted row survives mv_castabsave round-trip to disk
+)
+
+
+/* -------------------------------------------------------------------- */
+%put TEST 2 - save overwrites an existing source file;
+/* -------------------------------------------------------------------- */
+
+/* Source file already exists from the TEST 1 save - append a new row */
+data work.appendme;
+ set mylib.&tab1;
+ name='TESTROW2';
+ output;
+ stop;
+proc casutil;
+ load data=work.appendme casout="&tab1"
+ outcaslib="&testcaslib" append;
+quit;
+
+%mv_castabsave(lib=mylib, table=&tab1, mdebug=1)
+
+proc casutil;
+ droptable casdata="&tab1" incaslib="&testcaslib" quiet;
+ droptable casdata="&tab1" incaslib="&testcaslib" quiet;
+ load casdata="&tab1..sashdat" incaslib="&testcaslib"
+ casout="&tab1" outcaslib="&testcaslib" promote;
+quit;
+
+%let _rowcount=0;
+proc sql noprint;
+ select count(*) into :_rowcount
+ from mylib.&tab1
+ where name='TESTROW2';
+quit;
+
+%mp_assert(
+ iftrue=(&_rowcount=1),
+ desc=Check inserted row survives save over an existing source file
+)
+
+
+/* -------------------------------------------------------------------- */
+/* Teardown */
+/* -------------------------------------------------------------------- */
+libname mylib clear;
+
+proc casutil;
+ droptable casdata="&tab1" incaslib="&testcaslib" quiet;
+ deletesource casdata="&tab1..sashdat"
+ incaslib="&testcaslib" quiet;
+quit;
+
+cas mysess terminate;
+
+%let syscc=0;
diff --git a/viya/mv_castabsave.sas b/viya/mv_castabsave.sas
new file mode 100644
index 0000000..87d6aa1
--- /dev/null
+++ b/viya/mv_castabsave.sas
@@ -0,0 +1,192 @@
+/**
+ @file mv_castabsave.sas
+ @brief Saves an in-memory CAS table back to persistent storage
+ @details Runs in SPRE against an active CAS session. Accepts a
+ SAS libref, derives the CAS caslib and session UUID from
+ sashelp.vlibnam, locates the owning CAS server via the
+ casManagement REST API, then queries the table endpoint to
+ discover the original source file and saves back to that path.
+ CASUTIL infers the file type from the output file extension.
+
+ A CAS session must already be established by the caller, eg:
+
+ cas mysess;
+ libname mylib cas caslib=Public;
+ %mv_castabsave(lib=mylib, table=BASEBALL)
+
+ @param [in] lib= SAS libref for the CAS caslib
+ @param [in] table= Name of the in-memory CAS table to save
+ @param [in] mdebug= (0) Set to 1 to enable verbose logging:
+ - echoes resolved parameters
+ - prints HTTP response body
+ - enables mprint/notes during PROC calls
+
+ SAS Macros
+ @li mf_getplatform.sas
+ @li mf_getuniquefileref.sas
+ @li mf_getuniquelibref.sas
+ @li mp_abort.sas
+
+**/
+
+%macro mv_castabsave(
+ lib=
+ ,table=
+ ,mdebug=0
+);
+
+%local _sysopts base_uri caslib uuid server
+ srcfile srccaslib fname1 libref1 ftmp i _svcount;
+%let _sysopts=%sysfunc(getoption(mprint)) %sysfunc(getoption(notes));
+
+/* ---- input validation -------------------------------------------------- */
+%mp_abort(
+ iftrue=("&lib"="" or "&table"=""),
+ msg=%str(lib= and table= are required)
+)
+
+%if &mdebug=1 %then %do;
+ %put &=lib;
+ %put &=table;
+ options mprint notes;
+%end;
+
+/* ---- derive caslib and session UUID from sashelp.vlibnam --------------- */
+data _null_;
+ set sashelp.vlibnam(
+ where=(libname="%upcase(&lib)"
+ and sysname in ("Caslib","Session UUID"))
+ );
+ if sysname="Caslib" then call symputx('caslib',sysvalue,'L');
+ else call symputx('uuid',sysvalue,'L');
+run;
+
+%mp_abort(
+ iftrue=("&caslib"=""),
+ msg=%str(&lib is not an assigned CAS libref)
+)
+
+%mp_abort(
+ iftrue=("&uuid"=""),
+ msg=%str(No session UUID found for libref &lib)
+)
+
+%if &mdebug=1 %then %do;
+ %put &=caslib;
+ %put &=uuid;
+%end;
+
+%let base_uri=%mf_getplatform(VIYARESTAPI);
+
+/* ---- get list of CAS servers ------------------------------------------- */
+%let fname1=%mf_getuniquefileref();
+%let libref1=%mf_getuniquelibref();
+
+proc http method='GET' out=&fname1 oauth_bearer=sas_services
+ url="&base_uri/casManagement/servers";
+run;
+
+%mp_abort(
+ iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200),
+ msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+
+libname &libref1 JSON fileref=&fname1;
+
+data _null_;
+ set &libref1..items;
+ call symputx(cats('_sv_', _n_), name, 'L');
+ call symputx('_svcount', _n_, 'L');
+run;
+
+libname &libref1 clear;
+filename &fname1 clear;
+
+/* ---- find which server owns this session ------------------------------- */
+%do i=1 %to &_svcount;
+ %if "&server"="" %then %do;
+ %if &mdebug=1 %then %put checking server: &&_sv_&i;
+ %let ftmp=%mf_getuniquefileref();
+ proc http method='GET' out=&ftmp oauth_bearer=sas_services
+ url="&base_uri/casManagement/servers/&&_sv_&i/sessions/&uuid";
+ run;
+ %if &SYS_PROCHTTP_STATUS_CODE=200
+ %then %let server=&&_sv_&i;
+ filename &ftmp clear;
+ %end;
+%end;
+
+%mp_abort(
+ iftrue=("&server"=""),
+ msg=%str(Could not find owning server for CAS session &uuid)
+)
+
+%if &mdebug=1 %then %put &=server;
+
+/* ---- discover srcfile from REST endpoint ------------------------------- */
+%let fname1=%mf_getuniquefileref();
+%let libref1=%mf_getuniquelibref();
+
+proc http method='GET' out=&fname1 oauth_bearer=sas_services
+ url="&base_uri/casManagement/servers/&server/caslibs/&caslib/tables/&table";
+run;
+
+%if &mdebug=1 %then %do;
+ %put &=SYS_PROCHTTP_STATUS_CODE &=SYS_PROCHTTP_STATUS_PHRASE;
+ data _null_;
+ infile &fname1;
+ input;
+ putlog _infile_;
+ run;
+%end;
+
+%mp_abort(
+ iftrue=(&SYS_PROCHTTP_STATUS_CODE=404),
+ msg=%str(&caslib..&table not found - is it loaded in memory?)
+)
+%mp_abort(
+ iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200),
+ msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+
+libname &libref1 JSON fileref=&fname1;
+
+data _null_;
+ set &libref1..tablereference;
+ call symputx('srcfile', sourceTableName, 'L');
+ call symputx('srccaslib', sourceCaslibName, 'L');
+ stop;
+run;
+
+libname &libref1 clear;
+filename &fname1 clear;
+
+%mp_abort(
+ iftrue=("&srcfile"="" or "&srccaslib"=""),
+ msg=%str(No sourceTableName/sourceCaslibName for &caslib..&table)
+)
+
+%if &mdebug=1 %then %put &=srcfile;
+
+/* ---- save to disk ------------------------------------------------------- */
+proc casutil;
+ save casdata="&table"
+ incaslib="&caslib"
+ casout="&srcfile"
+ outcaslib="&srccaslib"
+ replace;
+quit;
+
+%mp_abort(
+ iftrue=(&syscc ne 0),
+ msg=%str(Save failed for &caslib..&table)
+)
+
+%put NOTE: Table &caslib..&table saved to &srcfile;
+
+/* ---- restore options --------------------------------------------------- */
+%if &mdebug=1 %then %do;
+ options &_sysopts;
+%end;
+
+%mend mv_castabsave;