Commit Graph
1769 Commits
Author SHA1 Message Date
allan be3e31f998 Merge pull request 'fix(mocks): bring the mock services onto the SAS contract' (#466) from fix/mock-makedata-actions into release/7.22
Sites / deploy (push) Successful in 4m45s
Build / Build-and-ng-test (pull_request) Successful in 5m9s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m4s
Build / Build-and-test-development (pull_request) Successful in 27m18s
Reviewed-on: #466
2026-11-04 15:48:55 +00:00
dc ca602082ff fix(mocks): abort like mp_abort, lock like mp_lockanytable, derive saslibs
Build / Build-and-ng-test (pull_request) Successful in 5m43s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m58s
Build / Build-and-test-development (pull_request) Successful in 29m42s
Three more places where the mock answered differently from the SAS service.

mp_abort (dcMockUtils): every mock refusal is now a sasjsAbort table
(MSG + MAC) written where the macro writes it, so the client shows the
abort modal and rejects the promise. A thrown error had become an HTTP
500 "Job execution failed" - the browser host discards the webout when a
program throws - which is unreadable to the user and hides the message in
a stack. mpAbort also stops the program, so nothing is written after a
refusal; each service that can abort catches that stop at its top level.
stagedata, loadfile, getauditfile and makedata now go through it, and the
hand-rolled sasjsAbort blocks are gone.

mp_lockanytable (dcMockUtils): a lock is a row in MPE_LOCKANYTABLE marked
LOCKED carrying the holder's job id, user and reference, exactly as the
macro records it, and a lock held by another session refuses the write
with the macro's message. postdata and rejection now lock MPE_REVIEW and
MPE_SUBMIT around each write, which is where postdata.sas and
rejection.sas take them. The macro's retry loop is collapsed to a single
attempt: the runtime is single threaded, so nothing can release a lock
while the request waits.

startupservice: saslibs is the distinct LIBREF of the permitted datasets,
which is what "create table saslibs as select distinct libref from
&syslast" produces - it was a four-column row describing the control
library, with fields the client does not read.

Tests: abort.test.js and locks.test.js join the harness suites; 25 pass,
and 9 of the 13 relevant cases fail against the base services.
2026-11-04 15:37:31 +00:00
dc 87fd46c825 fix(mocks): derive startupservice's maps and timezone instead of hardcoding
Build / Build-and-ng-test (pull_request) Successful in 6m46s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m8s
Build / Build-and-test-development (pull_request) Canceled after 7m30s
Every screen reads public/startupservice, so a divergence there is a
divergence in the whole app.  Three of its answers were fixed values:

  xlmaps       a hardcoded list of three maps, so editing MPE_XLMAP_RULES
               or MPE_XLMAP_INFO changed nothing on the site
  sasdatasets  included the map target dataset, which the SAS service
               deletes - the demo offered MPE_XLMAP_DATA as an editable
               table while SAS refuses it
  timezone     TIMEZONE and SYSTIMEZONEIDENT pinned to Europe/London and
               the offset to +0100, so a GMT session reported an hour of
               DST it did not have

They now come from the same sources the SAS service reads: the current
MPE_XLMAP_RULES rows joined to MPE_XLMAP_INFO (coalescec to
<mpelib>.MPE_XLMAP_DATA), the target datasets removed from sasdatasets,
and the session zone and current offset taken from the runtime, with
TIMEZONE left blank as an unset option is.

The shared harness (sas/mocks/test/harness.js) makes the service tests
one-liners: it builds a temporary estate with every service on its drive
and runs a service file the way the runtime does, webout() included.
sas/mocks/test/startupservice.test.js covers the joins, the target
removal, closed rules and the offset.
2026-11-04 15:23:14 +00:00
dc e1c3c6e872 fix(mocks): answer the deploy screen's DIRLIST and WRITECHECK actions
Lighthouse Checks / lighthouse (pull_request) Successful in 9m45s
Build / Build-and-ng-test (pull_request) Failing after 15m6s
Build / Build-and-test-development (pull_request) Skipped
Staging now opens on the deploy screen, and its folder picker asks
makedata for a listing.  The mock ignored ACTION entirely: every request
seeded the control library, deleted makedata from the drive and replied
with the HTML success page.  So the first BROWSE deployed the site and
closed the wizard the visitor was standing on, behind an "invalid Json
string" error from the adapter, and VALIDATE and SUBMIT then answered
500 "Program not found in drive".

The mock now dispatches on ACTION the way the SAS service does:

  DIRLIST     the folders below PARENT, one level at a time, with the
              dirinfo/dirlist shape mpe_dirlist emits
  WRITECHECK  the checkwrite row mpe_writecheck emits, probed by creating
              and removing a file
  MAKEDATA    the existing seeding path, unchanged

ACTION is required and refused when absent or unknown, and the two browse
actions are refused once the settings file exists, mirroring browse_gate.
Both input routes are accepted - the screen's fromjs table and a CLI
seed's SASControlTable - because %webout(FETCH) does not care which one
arrived; deploy/makedata.json now carries ACTION=MAKEDATA as the real
service requires.

sas/mocks/test/makedata-actions.test.js runs the service file itself
against a temporary estate and is wired into the build workflow.
2026-11-04 14:59:19 +00:00
allan 03cb3b9d37 Merge pull request 'ci(sites): publish staging unconfigured so it opens on the deploy screen' (#464) from ci/staging-unconfigured into release/7.22
Sites / deploy (push) Successful in 3m46s
Build / Build-and-ng-test (pull_request) Successful in 5m41s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m57s
Build / Build-and-test-development (pull_request) Successful in 29m41s
Reviewed-on: #464
2026-11-04 13:49:17 +00:00
allan 15575b4228 Merge pull request 'feat(mocks): emit the captured-literal services through webout()' (#461) from feat/mocks-metanav-webout into release/7.22
Sites / deploy (push) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #461
2026-11-04 13:48:48 +00:00
dc 9c3d067e0e ci(sites): publish staging unconfigured so it opens on the deploy screen
Build / Build-and-ng-test (pull_request) Successful in 5m10s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m6s
Staging is the preview of the next release, and what it should preview first is
the deployment itself. DC routes to /deploy while makedata is still on the drive
(at <appLoc>/services/admin) and stops doing so once the wizard has run it and it
has removed itself - so a drive that carries the services and the licence but no
control library opens on the deploy screen, and the visitor deploys the control
library from the wizard into their own IndexedDB drive.

The seeding step is now gated on a tag: only the demo (which a visitor should be
able to use straight away) runs makedata before the drive is captured. The
staging path gets a guard of its own, because a seed that leaked into it would
only be visible as a site that opens on the home screen: makedata must be on the
drive and no mpe_tables.json may be there.

CONTRIBUTING updated: the two sites now ship different drives from the same
build.
2026-11-04 13:41:50 +00:00
dc 7d9d466c3e fix(mocks): keep the OMS and path separators the captures lost
Build / Build-and-ng-test (pull_request) Successful in 6m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m1s
Build / Build-and-test-development (pull_request) Canceled after 7m2s
The captures wrote values like OMSOBJ:SASLibrary\A59LNVZG.B500000K inside a
template literal, where \A is an escape, so the separator never reached the
client: it served OMSOBJ:SASLibraryA59LNVZG.B500000K. The same happened to
D:\MyDemo\Sankey and BUILTIN\Administrators. Converting the service to webout()
faithfully preserved the corrupted value - and the metadetails ASSOCURI, the one
field fixed by hand at conversion time, was left as the only member that kept its
separator, which is the inconsistency the review named.

Seventeen values corrected across five services:

  lineage/fetchtablelineage  info[0].LIBURI          1
  lineage/getmetacols        metacols[].COLURI      11
  lineage/getmetatables      PATH_SCHEMA, TABLEURI   2
  public/refreshlibinfo      libinfo[0].OWNERS      1
  usernav/usermembersbyrole  sasgroups[].URIMEM,
                             sasmembers[].URIMEM     2

Verified by deploying both trees on one server and comparing the parsed
responses: those seventeen fields are the only differences, and every other
member of all thirteen converted services is unchanged (metadetails' two
ASSOCURI, already carrying its separator, still differ from the capture for the
same reason).

Anything that splits a URI or a Windows path on the separator, or displays
OWNERS or PATH_SCHEMA, now sees the value the SAS metadata server returns.
2026-11-04 13:23:13 +00:00
allan 2dbaee7811 Merge pull request 'ci: run the mock estate on 3.14.0' (#462) from ci/server-3.14 into release/7.22
Sites / deploy (push) Successful in 3m50s
Build / Build-and-ng-test (pull_request) Successful in 6m39s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m1s
Build / Build-and-test-development (pull_request) Canceled after 13m2s
Reviewed-on: #462
2026-11-04 13:22:19 +00:00
dc 246cc39ca0 ci: run the mock estate on 3.14.0
Build / Build-and-ng-test (pull_request) Successful in 5m42s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m8s
Build / Build-and-test-development (pull_request) Successful in 29m36s
3.14.0 ships @sasjs/lint 5.0.0, so the studio reports OS commands, and it is the
release the estate runs. It still declares @sasjs/utils ^4.7.0, so the envelope
formats from utils 4.8 (SYSCC as text, DATE7, TIME5, E8601DT26.6) are not yet
guaranteed by the floor - a fresh install resolves 4.8.0, a cached tree need not.
The comment says so, and the pin moves again when the floor lands.

Verified on a local 3.14.0 install (clean, resolving utils 4.8.0): the mock
estate's startupservice returns SYSDATE 04NOV26, SYSTIME 12:44, SYSCC "0" (text),
END_DTTM 2026-11-04T12:44:14.954000 (no Z). With that estate seeded, and against
a fresh app location, licensing.cy.ts passes 10/10 and metanav.cy.ts 1/1 on
Chromium.
2026-11-04 13:06:55 +00:00
dc 1f54fad3fe feat(mocks): emit the lineage, usernav and libinfo responses through webout()
Build / Build-and-ng-test (pull_request) Successful in 6m40s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m2s
Build / Build-and-test-development (pull_request) Successful in 33m38s
The other nine services that answer with a captured response also assigned it to
_webout, envelope and all. They now emit their members through the runtime's
webout():

  lineage/fetchtablelineage  info, finalfinal, flatdata, idlookup
  lineage/getmetacols        metacols   11 rows
  lineage/getmetatables      metatables  1 row
  usernav/usermembers        users       2 rows
  usernav/usermembersbygroup sasmembers  2 rows
  usernav/usergroupsbymember groups      1 row
  usernav/usermembersbyrole  sasgroups   1 row, sasmembers 1 row
  usernav/userroles          roles      46 rows
  public/refreshlibinfo      libinfo     1 row

Each payload was taken from the response the old service serves, then both
trees were deployed on one server (/Public/app/dc and /Public/app/dcnew) and
compared as parsed JSON with the envelope keys excluded: all nine are equal row
for row, including the escaped values (the OMS URIs keep their backslash, and
refreshlibinfo keeps the quoted paths).

The envelope is the runtime's now, so SYSCC is numeric 0 rather than the string
"0", SYSDATE/SYSTIME are today's, SYSSITE is reported (these captures carried a
lower-cased syssite, which the client never read), and the SAS-only
_METAUSER/_METAPERSON are gone.

lineage/fetchlineage is deliberately left alone: its captured body is not valid
JSON today (bare quotes in the graphviz labels, raw newlines inside the STRING
values, and a lost backslash), so there is no payload to preserve and the
intended text needs a decision rather than a rewrite. Tracked separately.
2026-11-04 11:46:19 +00:00
dc e1d877f21d feat(mocks): emit the metanav responses through webout()
Build / Build-and-ng-test (pull_request) Canceled after 56s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 55s
The four metanav services assigned a captured 2022 response to _webout, envelope
and all, instead of using the runtime's webout() like every other service. The
members are unchanged:

  metarepos    outrepos     2 rows
  metaobjects  objects      1 row
  metatypes    types       71 rows
  metadetails  attributes  12 rows
               associations 2 rows

Both trees were deployed on one server, at /Public/app/dc and /Public/app/dcnew,
and the responses compared as parsed JSON with the envelope keys excluded:
outrepos, objects and types are equal row for row.

metadetails changes in one place. The capture wrote the OMS URI as
OMSOBJ:Property\A59..., but \A is an escape in a JavaScript template literal,
so the backslash was consumed and the client received OMSOBJ:PropertyA59... -
a URI that cannot resolve. The member now carries the backslash the SAS
metadata server returns.

The envelope comes from the runtime now, which also drops three artefacts of
the recording: SYSCC "4" on metarepos (an error code from the captured day,
which the client reads as a SAS error), SYSDATE/SYSTIME frozen at 26SEP22, and
the SAS-only _METAUSER/_METAPERSON.
2026-11-04 11:45:17 +00:00
allan 2b85341845 Merge pull request 'test(embed): read the stored filter, by this run's hash, instead of wiping the tables' (#454) from fix/449-va-ddc-host-order-free into release/7.22
Sites / deploy (push) Successful in 3m48s
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m4s
Build / Build-and-test-development (pull_request) Successful in 27m28s
Reviewed-on: #454
2026-11-04 11:19:46 +00:00
allan e0cf7c247b Merge pull request 'fix(build): build mock sites with a CLI whose runtime provides webout()' (#460) from fix/cli-mock-webout into release/7.22
Sites / deploy (push) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #460
2026-11-04 11:19:07 +00:00
dc b1eab98200 fix(build): build the mock sites with a CLI whose runtime provides webout()
Build / Build-and-ng-test (pull_request) Canceled after 3m16s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m9s
The mock services call webout(), as the server's runtime does. The browser mock
runtime that @sasjs/cli ships is what provides it there, and the 6.5.0 this repo
was pinned to does not: its runtime-core.js has no reference to the webout
prelude, and the build writes no webout-prelude.json for it to load. Every
service that calls webout() therefore throws "ReferenceError: webout is not
defined" in a published mock site while the build itself reports success.

6.9.0 is the first release that ships the prelude; this pins 6.10.0, the current
release. The transitive @sasjs/adapter and @sasjs/utils entries move with it.
2026-11-04 11:03:47 +00:00
allan 3d94c499d9 Merge branch 'release/7.22' into fix/449-va-ddc-host-order-free
Build / Build-and-ng-test (pull_request) Successful in 5m15s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m11s
Build / Build-and-test-development (pull_request) Successful in 29m56s
2026-11-04 10:40:15 +00:00
allan 8c9b0ff5f9 Merge pull request 'ci(workflows): hand values to the shell as data, not as script text' (#458) from ci/no-shell-interpolation into release/7.22
Sites / deploy (push) Successful in 3m47s
Build / Build-and-ng-test (pull_request) Successful in 5m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m55s
Build / Build-and-test-development (pull_request) Canceled after 25m35s
Reviewed-on: #458
2026-11-04 10:40:07 +00:00
allan 1a0d371fb0 Merge branch 'release/7.22' into fix/449-va-ddc-host-order-free
Build / Build-and-ng-test (pull_request) Canceled after 6s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 5s
2026-11-04 10:39:54 +00:00
allan e69af76771 Merge pull request 'fix(mocks): read the metadata member from the response, not the function's arity' (#459) from fix/mock-showmeta-by-outcome into release/7.22
Sites / deploy (push) Canceled after 9s
Build / Build-and-ng-test (pull_request) Canceled after 1s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #459
2026-11-04 10:39:44 +00:00
dc 09e9f192c5 fix(mocks): read the metadata member from the response, not the function's arity
Lighthouse Checks / lighthouse (pull_request) Successful in 8s
Build / Build-and-ng-test (pull_request) Successful in 6m37s
Build / Build-and-test-development (pull_request) Successful in 27m32s
The guard that keeps a runtime without webout()'s options argument from serving a
thinner response tested webout.length >= 4. Function.length stops counting at the
first default or rest parameter, so a runtime that does carry the option but
declares it `options = {}` or gathers the tail with `...args` reports three, and
every service that calls the guard refuses to start on a working runtime.

It now asserts the outcome instead: the member the option produces is looked for
in the response body the program is writing. `$sasdata`, `$viewdata`, `$vals` are
the members the client reads for column formats and labels, and their presence is
the thing that actually has to hold.

Where it runs matters too, and the first attempt got that wrong: called after
webout('CLOSE') the throw leaves a complete body behind, and the server serves it
- the caller sees a normal response with SYSCC 0 and no metadata, which is exactly
the silent failure this exists to prevent. It is called before the envelope is
closed, so an unhonoured option fails the run.
2026-11-04 10:34:15 +00:00
dc aa295b384a Merge remote-tracking branch 'origin/release/7.22' into fix/449-va-ddc-host-order-free
Build / Build-and-ng-test (pull_request) Successful in 5m41s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 4m49s
2026-11-04 10:20:17 +00:00
dc ba32d1fa46 ci: run the mock estate on the server that reports SYSSITE
Sites / deploy (push) Successful in 4m8s
Build / Build-and-ng-test (pull_request) Successful in 5m7s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m8s
Build / Build-and-test-development (pull_request) Canceled after 19m25s
@sasjs/utils 4.7 makes webout() emit SYSSITE from a configured site id,
defaulting to 123, where %webout takes &syssite. Until 3.13.0 the prelude
wrote an empty string, and dc's client routes an expired licence key and a
wrong-organisation key only when startup_site_id is truthy - so on the mock
estate both pages were unreachable and licensing.cy.ts failed three cases.
2026-11-04 10:15:04 +00:00
dc f0e3117540 ci(workflows): hand values to the shell as data, not as script text
Build / Build-and-ng-test (pull_request) Successful in 6m40s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m8s
Build / Build-and-test-development (pull_request) Successful in 33m40s
Three values reached `run:` blocks through `${{ }}`, so the runner pasted them
into the script before the shell ever saw it - which makes the value part of the
code rather than an argument to it.

- `BASE="${{ github.base_ref }}"` (build.yaml, lighthouse.yaml) - a ref name in
  the script.
- `echo ${{ secrets.SHEET_PWD }} | gpg --passphrase-fd 0` (all four workflows) -
  a passphrase in the script. A quote in it ends the quoting, and a command
  substitution in it runs.
- two `replace-in-files --replacement=...${{ secrets.CYPRESS_* }}...` lines
  (build.yaml, release.yaml) - the same, inside a replacement string.

Each now arrives as a step `env:` value and is expanded as `"$VAR"`, so the shell
only ever sees data. Checked the replacement lines side by side with the old form:
identical for an ordinary value, and the new form keeps a value containing a space
intact where the old one word-splits it, and survives a value containing a single
quote where the old one is a shell syntax error.

The passphrase pipe also moves from `echo` to `printf '%s\n'`, so the byte the
decryption sees does not depend on how the shell's `echo` treats backslashes.
2026-11-04 09:55:22 +00:00
dc 1225eb122a test(embed): fail on a missing FILTER_HASH before reading the store back
Build / Build-and-ng-test (pull_request) Successful in 6m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m14s
Build / Build-and-test-development (pull_request) Failing after 37m14s
Review finding: when every body is refused by carriesClause, or the response
carries no FILTER_HASH, capture.hash stays empty - and storedClauseFor('')
then spends its 60 retries before reporting "no clause for <var> under",
which names neither the refused body nor the missing hash.

The hash is now asserted non-empty before the read, with the case's own
message, so a broken delivery fails where it happened.

The call count stays where it was - after the data load - because moving it
ahead of the read would check it before the app has issued the second POST
that the count exists to catch.
2026-11-04 09:08:55 +00:00
dc 4a87f8f6ce chore: merge release/7.22 into fix/449-va-ddc-host-order-free
Build / Build-and-ng-test (pull_request) Successful in 6m37s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m12s
Build / Build-and-test-development (pull_request) Failing after 37m22s
2026-11-04 01:20:13 +00:00
dc 4b42d1aaf1 test(embed): count deliveries on the app's own signal, not on a clock
The duplicate check waited a fixed 250ms after reading the stored row, which is
wrong in both directions: on a slow runner the window can elapse before the first
response callback has incremented the counter, and a second POST landing after it
passes with two deliveries.

The app's sequence for an apply is validatefilter, then the data load for the
filtered route, so the load is the completion signal: the count is asserted after
it, and nothing depends on how fast the machine is. A delivery issued later than
that is a second apply, not a duplicate of this one.

Also corrects the ordering paragraph in cypress-specs.sh: it still said specs are
appended, in a file that now places va-ddc-host ahead of metanav. va-ddc-host does
not depend on the estate being unlicensed either - it passed 3/3 immediately after
metanav planted a key in the full CI run - so the bullet says what is true and
metanav stays the only spec with a fixed place.
2026-11-04 01:20:05 +00:00
allan 514e326be8 Merge pull request 'ci(build): record why the mock estate's server pin is 3.12.3' (#457) from ci/build-comment-server-floor into release/7.22
Build / Build-and-ng-test (pull_request) Successful in 6m16s
Sites / deploy (push) Successful in 4m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m4s
Build / Build-and-test-development (pull_request) Failing after 33m14s
Reviewed-on: #457
2026-11-04 00:49:46 +00:00
dc 1c4d2f65cc test(embed): match the clause's field wherever the body wraps it
Build / Build-and-ng-test (pull_request) Successful in 9m10s
Build / Build-and-test-development (pull_request) Canceled after 4m21s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Review finding: the matcher only accepted the variable name at the start of the
body or after a comma or newline, so a delivery wrapped any other way was not
counted - and the case then failed at the call-count assertion, reading as
"the app never called validatefilter". Reproduced against the previous
implementation: a urlencoded form value, a form value that starts with the
name, a JSON object keyed on the column and a JSON string value holding the
row all matched nothing.

The body is now expanded into the candidate strings it can carry - the raw
text, the percent-decoded text, each form field's value, and every string leaf
of a JSON body - and the name has to be a whole CSV field of one of them.
Field equality rather than delimiters means a quoted literal, a longer name,
an SQL fragment or another variable's name is still refused, and there is no
regular expression over user text to get wrong.

Also restores the ordering note in cypress-specs.sh: va-ddc-host asserts
nothing about licensing, so it runs before metanav, the one spec that plants a
licence key.
2026-11-04 00:40:47 +00:00
dc 75d32d9838 ci(build): record why the mock estate's server pin is 3.12.3
Build / Build-and-ng-test (pull_request) Successful in 5m10s
Lighthouse Checks / lighthouse (pull_request) Successful in 7s
Build / Build-and-test-development (pull_request) Successful in 10s
The pin moved to 3.12.3 with the webout() change but the comment above it still
named 3.12.2 as the current release - true when it was written, wrong under the
pin it now sits on. The comment also only carried the /ddc-host reason, which
does not explain this bump.

3.12.3 is the first server release declaring @sasjs/utils ^4.6.0, and that is
what gives webout() the options argument getdata, viewdata and getcolvals pass as
{ showmeta }. Say so where the constant is, so the next person to move it knows
the metadata members depend on it.
2026-11-04 00:38:40 +00:00
dc ec92eb4eee ci: run the mock estate on the server that carries the utils floor
Sites / deploy (push) Successful in 3m46s
Build / Build-and-ng-test (pull_request) Canceled after 1m4s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
The services in this branch pass { showmeta } to webout(), which is
@sasjs/utils 4.6 and up. sasjs/server 3.12.3 is the first release whose
package.json asks for ^4.6.0 (a clean install resolves 4.6.1), so the pin
moves from 3.12.2 to 3.12.3 in every workflow that starts the mock estate.

Verified against the published package rather than the release notes:
`npm view @sasjs/server@3.12.3 dependencies.@sasjs/utils` answers ^4.6.0, and
`npm install @sasjs/server@3.12.3` in an empty directory installs
@sasjs/utils 4.6.1, whose webout prelude carries showmeta.
2026-11-04 00:27:34 +00:00
allan 1a31bcf244 Merge pull request 'feat(va-host): emulate a VA report that embeds DC, as a deployable AppStream app' (#452) from feat/va-report-emulation into release/7.22
Sites / deploy (push) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #452
2026-11-04 00:18:21 +00:00
allan 3dbce00dea Merge pull request 'feat(mocks): build every JS stored program's response with webout()' (#456) from feat/js-stp-webout-all into release/7.22
Sites / deploy (push) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #456
2026-11-04 00:16:24 +00:00
dc 36a2bff086 ci(workflows): install the tooling at the versions this repo chose
Sites / deploy (push) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Review of the release diff: `npm install -g @sasjs/cli` and
`replace-in-files-cli` were unpinned, so CI ran whatever release was current
and a bad one would reach the pipeline unreviewed - next to a SASjs server
that is pinned to an exact version.

The CLI now comes from the version sas/package.json pins, which is the shape
docs.yaml and release-assets.yaml already use, so there is one source for it.
replace-in-files-cli, pm2 and wait-on are pinned to the versions in use
(4.0.0, 7.1.2, 9.5.1).

Both node expressions are checked from the directories they run in:
`require('./sas/package.json')` at the root and `require('../../package.json')`
in sas/mocks/sasjs both answer 6.5.0.
2026-11-04 00:12:46 +00:00
dc 482435f318 fix(mocks): refuse a runtime whose webout() cannot carry column metadata
Build / Build-and-ng-test (pull_request) Successful in 6m51s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m57s
Build / Build-and-test-development (pull_request) Failing after 30m50s
The three services that pass { showmeta } - getdata, viewdata, getcolvals - rely
on webout()'s fourth argument, which arrived in @sasjs/utils 4.6.0. @sasjs/server
3.12 declares ^4.4.0, so an install can resolve 4.5.x, and there the argument is
dropped silently: the response still looks complete but $sasdata, $viewdata and
$vals are absent, so the client's format and label paths stop being exercised
while the mock estate reports success.

requireWeboutOptions() checks that the generated webout() takes an options
argument (arity, because the option names live outside the callable function) and
throws naming the member, the cause and both fixes. A 4.6 runtime is unchanged.
2026-11-04 00:10:53 +00:00
dc 9c53e5b90d test(embed): match the clause's field, and wait for the response as long as the page takes
Build / Build-and-ng-test (pull_request) Successful in 5m49s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m58s
Build / Build-and-test-development (pull_request) Canceled after 0s
The matcher tested the variable as a substring of the whole request body, so a
RAW_VALUE, an SQL fragment or another variable whose name contains it was counted
as another delivery of this clause and failed the count. It now requires the name
between its CSV delimiters, which is the filterquery VARIABLE_NM field; a quoted
literal cannot match, because its delimiters are quotes.

cy.wait('@validatefilter') used the 5s default, where the polling loop it replaced
tolerated 15s, and the message only arrives once the framed page has rendered.
It now waits for longerCommandTimeout, as the rest of this spec does.

The capture assertion also carries the tail of the host page's log in its message:
the failure reads "expected -1 to be above -1" otherwise, and the log is what says
whether the app never exposed its buffer or the frame never loaded.
2026-11-03 23:54:42 +00:00
dc 90784b342f fix(va-host): deliver the load-mount message once, and document report.json from the file
Build / Build-and-ng-test (pull_request) Successful in 5m9s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m59s
Build / Build-and-test-development (pull_request) Successful in 33m54s
Two review findings:

- The load handler re-posted pendingPayload on every load event of the frame,
  so an in-app navigation or a self-reload posted a filter that belonged to
  the mount which had already delivered it, and logged it as that mount's.
  The handler now takes the payload (clearing pendingPayload and
  pendingTiming) before posting, so a later load has nothing to send.

- The README illustrated report.json with one parameter while the committed
  file carries three, and the prose below reads as though one parameter were
  the shape. The example block is gone: the file is the example, and the keys
  are documented as a table with the parameter rules kept underneath.
2026-11-03 23:45:40 +00:00
dc ce6bb5d4cd feat(mocks): build every JS stored program's response with webout()
Build / Build-and-ng-test (pull_request) Successful in 5m44s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m9s
Build / Build-and-test-development (pull_request) Canceled after 0s
The last eleven services still built the response envelope through
dcMockUtils' webOutOpen/webOutObj/webOutClose. They now use the webout() the
SASjs Server emits into every JS program, so dcMockUtils no longer emulates
%webout at all - the helper existed only for these callers and is deleted
with them.

That needed the options object added to webout() in @sasjs/utils 4.6
(showmeta, maxobs), which is what a clean install of @sasjs/server@3.12.2
resolves to (^4.4.0 -> 4.6.1, checked). A server whose install still holds
4.4/4.5 refuses the option by name rather than ignoring it, so the failure is
loud rather than a response that is quietly missing its column metadata.

getdata, viewdata and getcolvals send their metadata as
`webout('OBJ', name, rows, { showmeta })`, and every other call keeps its
member name and rows unchanged.

One call was positional all along: startupservice's xlmaps is an array of
arrays, which the old helper serialised as-is and webout() refuses for OBJ.
It is now webout('ARR', 'xlmaps', xlmaps) - caught by the comparison run
below, not by the rewrite.
2026-11-03 23:16:48 +00:00
dc 6508ac7f5c chore: merge release/7.22 into fix/449-va-ddc-host-order-free
Build / Build-and-ng-test (pull_request) Successful in 5m47s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m23s
Build / Build-and-test-development (pull_request) Canceled after 0s
2026-11-03 23:16:37 +00:00
dc 93af2dabe6 test(embed): match a parsed request body, and count duplicates after the read
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Two gaps in the guard that decides which validatefilter response belongs to the
case.

A body Cypress parses into an object stringified to "[object Object]", so no
call matched and the case failed on the count with nothing naming the body shape.
The body is now serialised the way the response already was - a string is used as
it is, anything else is JSON.stringify'd.

The count was asserted immediately after the first response, which is before a
second POST from the same apply could reach its own callback, so a duplicate read
as one call. The count is now asserted after the stored row has been read back,
with a settle that covers a second POST still in flight when the read returned.
2026-11-03 23:15:50 +00:00
dc 9cec292681 fix(va-host): post to the frame's own origin, and key the replay to the document
Lighthouse Checks / lighthouse (pull_request) Successful in 14s
Build / Build-and-ng-test (pull_request) Successful in 6m35s
Build / Build-and-test-development (pull_request) Canceled after 0s
Two review findings on host.js:

- postToFrame() used '*' as targetOrigin, so the filter clause went to
  whatever document occupied the frame. The frame is the app on report.server,
  so that origin is the only one that should receive it; the origin is now
  resolved once from report.server (new URL(...).origin) and a report.server
  that is not a URL logs a visible error instead of widening delivery. The
  page also says so when it is not on the frame's origin, because the capture
  probe cannot read across origins and that would otherwise look like a
  harness failure.

- startEarlyReplay() accepted any __vaLastMessage once it had posted, and on
  a remount to the same URL the old document is still on screen carrying the
  buffer the previous mount captured. A capture now only counts once the
  document has been replaced (mountDocument), both in the interval and in the
  load-event check. Keying to the document rather than waiting for a load
  event keeps the first post where the early window is - the interval starts
  exactly as before.
2026-11-03 23:11:20 +00:00
dc cde90830fb chore(ci): record why the client-dist artefact stays on upload-artifact v3
Sites / deploy (push) Successful in 3m50s
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m18s
Build / Build-and-test-development (pull_request) Canceled after 12m58s
2026-11-03 23:07:17 +00:00
allan 74d78ffe57 Merge branch 'release/7.22' into fix/449-va-ddc-host-order-free
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
2026-11-03 23:07:14 +00:00
allan bc0a463b5d Merge branch 'release/7.22' into feat/va-report-emulation
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
2026-11-03 23:06:50 +00:00
allan 3b9749f77e Merge pull request 'ci: cancel the run for a superseded pull request head' (#455) from ci/cancel-superseded-runs into release/7.22
Sites / deploy (push) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #455
2026-11-03 23:04:39 +00:00
dc 05bd1e2e7f ci(lighthouse): cancel the check for a superseded pull request head
Lighthouse Checks / lighthouse (pull_request) Successful in 9s
Build / Build-and-ng-test (pull_request) Successful in 5m6s
Build / Build-and-test-development (pull_request) Successful in 29m36s
Same reasoning as build.yaml: the older head cannot be merged, and a ten-minute
check queued ahead of the current one only delays it.
2026-11-03 22:49:30 +00:00
dc 7a2b01db84 ci(build): cancel the build for a superseded pull request head
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m14s
Every push to a pull request queued another full run - lint, licence check, the
mocked estate and the twenty-six minute suite - and nothing cancelled the run for
the head it replaced. With two runners shared by every pull request in the repo,
those stale runs are picked oldest-first and delay the head that can actually be
merged: runs 3002, 3003 and 3005 were queued for heads that had already been
superseded while the current one waited behind them.

The group is the pull request, so a newer push cancels the run for the older
head. Pushes that are not pull requests are not affected - the release workflow
answers to its own group.
2026-11-03 22:48:50 +00:00
dc 4c332ba6c7 chore(sas): restore the trailing newline in package.json
Sites / deploy (push) Successful in 3m53s
Build / Build-and-ng-test (pull_request) Successful in 5m14s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m5s
Build / Build-and-test-development (pull_request) Successful in 27m27s
2026-11-03 22:42:29 +00:00
dc bfbd6bfa93 chore: merge release/7.22 into feat/va-report-emulation
Build / Build-and-ng-test (pull_request) Successful in 6m38s
Lighthouse Checks / lighthouse (pull_request) Successful in 8s
Build / Build-and-test-development (pull_request) Successful in 29m41s
2026-11-03 22:27:50 +00:00
dc 7c63a5c93f fix(va-host): keep the deploy password off argv, and verify the app is served
Build / Build-and-ng-test (pull_request) Successful in 5m8s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m25s
Build / Build-and-test-development (pull_request) Successful in 29m50s
Three review findings on the harness:

- The usage line advertised `--pass`, which puts the secret in argv - readable
  from `ps` by every account on the host, and kept in the shell history. The
  flag is gone: SASJS_PASS stays the documented route, and `--pass-stdin`
  reads one line from stdin for scripts that cannot use the environment.

- deploy() reported success on the deploy route's 2xx alone. That means the
  tree was accepted, not that the app is served: a truncated `type: 'file'`
  member still serves a 200 page, and a stale asset from a previous deploy is
  a 200 by definition - both of which the README already lists as the sharp
  edges of that route. The page and all three assets are now fetched back and
  compared with the bytes that were sent, failing with the size and hash of
  both copies; a 401 is reported as deployed-but-gated, since on a server-mode
  estate that is the gate working. `--verify-only` runs just that half.

- Two harnesses stand in for a VA report (this page and
  client/cypress/e2e/va-ddc-host.cy.ts) and nothing said which does what. The
  README now names both roles and points at the contract of record - the
  message shape and __vaLastMessage in va-messaging.service.ts, and the
  pre-bootstrap capture rule in va-early.js - including why the probe differs
  between them (Cypress proxies the framed document).
2026-11-03 22:26:58 +00:00
dc 28458539f9 chore: merge release/7.22 into fix/449-va-ddc-host-order-free
Build / Build-and-ng-test (pull_request) Successful in 5m14s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m21s
Build / Build-and-test-development (pull_request) Successful in 33m42s
2026-11-03 22:25:04 +00:00