From 8ed0f16004aaeacfe59e3b6d62e252f24795b6e7 Mon Sep 17 00:00:00 2001 From: dc Date: Thu, 15 Oct 2026 18:28:36 +0000 Subject: [PATCH 1/2] docs(build): pin the deploy tooling versions and disable install scripts The publish workflow installed cloudron-surfer, the mkdocs pip set and @marp-team/marp-cli with no version pins, so a breaking or compromised upstream release would ship straight to the live site on the next merge to main. - pin cloudron-surfer@6.4.1, mkdocs==1.6.1, mkdocs-material==9.7.7, fontawesome_markdown==0.2.6, mkdocs-redirects==1.2.3 and @marp-team/marp-cli@4.5.1 (the versions the site builds with today) - add .npmrc with ignore-scripts=true and save-exact=true; neither npm package ships an install/preinstall/postinstall hook, so nothing is skipped --- .gitea/workflows/publish.yml | 17 ++++++++++------- .npmrc | 11 +++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 .npmrc diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index e4d011a..25b6332 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -31,19 +31,22 @@ jobs: apt install -y ./google-chrome*.deb; export CHROME_BIN=/usr/bin/google-chrome + # Deploy tooling is pinned to exact versions: the workflow builds whatever + # is committed, and an unpinned `latest` would let a breaking or + # compromised upstream release ship straight to the live site. - name: Install Surfer run: | - npm -g install cloudron-surfer + npm -g install cloudron-surfer@6.4.1 - name: build site run: | - pip install mkdocs - pip install mkdocs-material - pip install fontawesome_markdown - pip install mkdocs-redirects + pip install mkdocs==1.6.1 + pip install mkdocs-material==9.7.7 + pip install fontawesome_markdown==0.2.6 + pip install mkdocs-redirects==1.2.3 mkdocs build --clean mkdir site/slides - npx @marp-team/marp-cli slides/innovation/innovation.md -o ./site/slides/innovation/index.html - npx @marp-team/marp-cli slides/if/if.md -o site/if.pdf --allow-local-files --html=true + npx @marp-team/marp-cli@4.5.1 slides/innovation/innovation.md -o ./site/slides/innovation/index.html + npx @marp-team/marp-cli@4.5.1 slides/if/if.md -o site/if.pdf --allow-local-files --html=true - name: Deploy docs run: surfer put --token ${{ secrets.SURFERKEY }} --server docs.datacontroller.io site/* / diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ca82182 --- /dev/null +++ b/.npmrc @@ -0,0 +1,11 @@ +# npm configuration for this repo's CI and local installs. +# +# ignore-scripts keeps package install scripts (an arbitrary-code execution +# point) from running during the deploy workflow. Both npm packages the +# workflow installs - cloudron-surfer and @marp-team/marp-cli - ship no +# install, preinstall or postinstall hook, so nothing is skipped. +# +# save-exact pins anything installed from here to an exact version rather than +# a caret range. +ignore-scripts=true +save-exact=true From 8a22e28a463de157a448379f1a1c2aaebd96b739 Mon Sep 17 00:00:00 2001 From: dc Date: Thu, 15 Oct 2026 18:28:36 +0000 Subject: [PATCH 2/2] docs(developer-guide): correct the LIBREF/DS macro-scope note The post-edit hook section claimed a `%let` "creates a new variable in the hook's own scope and is silently discarded" while `call symputx` updates the existing one. That is not how SAS resolves it: LIBREF and DS already exist in an enclosing symbol table (mpe_loader's local table), and a macro only gets a new local when the name is absent from every enclosing table, so both forms assign to the same variable. Reworded to state that, and to keep the practical advice (call symputx is explicit and does not depend on the enclosing scope still holding the name). Raised in review of #16. --- docs/developer-guide/two-rule-sets-one-table.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer-guide/two-rule-sets-one-table.md b/docs/developer-guide/two-rule-sets-one-table.md index bf8dc3b..e42ef8a 100644 --- a/docs/developer-guide/two-rule-sets-one-table.md +++ b/docs/developer-guide/two-rule-sets-one-table.md @@ -102,9 +102,9 @@ From that moment the changeset is an approval against `ORDERS.ORDERS`. The appro ![A change submitted against the mirror, raised against the real table](/img/approval-routed.png) -### The detail that will bite you +### Reassigning LIBREF and DS -Use `call symputx`, not `%let`. `LIBREF` and `DS` are not declared `%local` in `mpe_loader`, and the hook is included into that scope - so `call symputx` finds the existing variable and updates it, while a `%let` creates a new variable in the hook's own scope and is silently discarded. The hook runs, the log looks clean, and the changeset goes to the mirror anyway. +`LIBREF` and `DS` are ordinary macro variables inside `mpe_loader`, which creates them with `%let` and then updates them from the staged row. A `%let libref=ORDERS;` reassigns them just as well as the `call symputx` above: both statements assign to the *existing* variable, because it lives in an enclosing symbol table (`mpe_loader`'s local table) and SAS only creates a new local when the name is absent from every enclosing table. `call symputx` is used above because it is explicit about which variable is being set, and because it does not depend on the enclosing scope still holding the name - a wrapper macro that declared `%local libref` would turn a bare `%let` into an assignment to that shadow instead. ### Other things to watch