Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
167 commits
Select commit Hold shift + click to select a range
f485bca
examples: add the application-ladder stress-test plan
Yaraslaut Aug 5, 2026
ddbb778
examples: add the ladder testing strategy (dual-mode GUI, multi-client)
Yaraslaut Aug 5, 2026
cac793a
examples: add implementation rules for ladder applications
Yaraslaut Aug 6, 2026
a21fe69
examples: restructure the ladder per the holistic program review
Yaraslaut Aug 6, 2026
6a155fc
chore: gitignore the superpowers SDD scratch workspace
Yaraslaut Aug 6, 2026
3aa5ada
docs: back-fill ladder framework findings 001-016 (rung 0)
Yaraslaut Aug 6, 2026
7f8e0c3
fix: correct subsystem enum value in finding 003 (util -> units)
Yaraslaut Aug 6, 2026
658b576
ladder: add rung-0 build wiring (MORPH_BUILD_LADDER, examples/common …
Yaraslaut Aug 6, 2026
5bd7b3a
ladder: defer examples/ subdirectory until after Catch2 is resolved
Yaraslaut Aug 6, 2026
9b8ff1c
ladder: add pump.hpp and the Qt-owning testkit main
Yaraslaut Aug 6, 2026
0745449
ladder: add db_fixture.hpp (real on-disk database, mirrors Lightweigh…
Yaraslaut Aug 6, 2026
1fad31e
ladder: add db_fault_fixture.hpp (genuine SqlScopedLock cross-session…
Yaraslaut Aug 6, 2026
617f862
ladder: add backend_rig.hpp (Local/LocalSingleThread/Socket BackendRig)
Yaraslaut Aug 6, 2026
f8ea3c8
ladder: use a stateful counter model for BackendRig's isolation test
Yaraslaut Aug 6, 2026
6ca9704
ladder: add AppContext + Presenter base (examples/common/gui)
Yaraslaut Aug 6, 2026
c37d265
ladder: add the fault-injection wire proxy (finding 004, proxy half)
Yaraslaut Aug 6, 2026
7eeb89a
ladder: keep fault-proxy test locals alive across the rig's teardown
Yaraslaut Aug 6, 2026
a6e15a0
testkit: fix stack-use-after-scope in awaitQt's timeout path
Yaraslaut Aug 6, 2026
a26d0a1
ladder: add the deterministic strand interleaver
Yaraslaut Aug 6, 2026
7a0dd2e
ladder: add @param doc tag to DeterministicExecutor::runSchedule
Yaraslaut Aug 6, 2026
5b51794
ci: add the ladder-tests job (path-filtered on examples/common, inclu…
Yaraslaut Aug 6, 2026
cdaf95a
ladder: add the WASM-remote spike (proves QtWebSocketBackend from Ems…
Yaraslaut Aug 6, 2026
afef282
ladder: fix WASM-spike link + BridgeHandler lifetime bugs from review
Yaraslaut Aug 6, 2026
ce75cea
ladder: split AppContext out of morph_ladder_gui and make Remote WASM…
Yaraslaut Aug 6, 2026
a53d005
ladder: close finding 004, file 018/019, fix the CI filter and ctest …
Yaraslaut Aug 6, 2026
748fd61
ladder: correct AppContext's lifetime comment and close a CI filter gap
Yaraslaut Aug 6, 2026
acba401
docs: drop the nightly CI tier from the ladder's testing strategy
Yaraslaut Aug 6, 2026
12c2c55
examples/ladder: raise testkit coverage via DI, remove dead sqlite_se…
Yaraslaut Aug 6, 2026
4918a04
ci: measure and enforce coverage on the ladder's hand-written code
Yaraslaut Aug 6, 2026
047c603
examples: fold rung-0 coverage lessons into the ladder's implementati…
Yaraslaut Aug 6, 2026
85a17cb
pastebin: resolve rung-1's design questions in writing
Yaraslaut Aug 6, 2026
5a87ddd
pastebin: file finding 021, justify the GUI's controller-wiring devia…
Yaraslaut Aug 6, 2026
e520fb9
pastebin: refine expiry sweep to periodic, not per-request
Yaraslaut Aug 6, 2026
533f1d7
docs: add the rung-1 (pastebin) implementation plan
Yaraslaut Aug 6, 2026
4f92cfa
examples/common: add the ladder-wide injectable clock
Yaraslaut Aug 6, 2026
25390d6
pastebin: add unit system, PasteId, and the typed error set
Yaraslaut Aug 6, 2026
f56a7c5
pastebin: fix ambiguous PasteId/PasteCursor construction from string …
Yaraslaut Aug 6, 2026
377a906
pastebin: add the PasteModel action/result DTOs
Yaraslaut Aug 6, 2026
baf474f
pastebin: add PasteRecord entity, its migration, and the WithMapper m…
Yaraslaut Aug 6, 2026
d53944b
findings: file 022 — sqliteodbc opens no cursor for UPDATE ... RETURNING
Yaraslaut Aug 6, 2026
51eb33c
pastebin: add PasteModel (create/get/edit/delete/list/expire)
Yaraslaut Aug 6, 2026
70bee48
pastebin: gate GetPaste disclosure on exactly one consumed row
Yaraslaut Aug 6, 2026
c22590b
pastebin: add App (RemoteServer bootstrap, FileActionLog, expiry sweep)
Yaraslaut Aug 6, 2026
d9cdcc6
examples/common: add DbBusyFixture, resolving finding 018's SQLITE_BU…
Yaraslaut Aug 6, 2026
c5e2eaf
cmake: implement morph_add_rung(), wire up examples/pastebin
Yaraslaut Aug 6, 2026
db075ca
cmake: gate ladder_${_rung}_lib behind if(NOT EMSCRIPTEN)
Yaraslaut Aug 6, 2026
f2ad662
core: escape control bytes in action and result JSON bodies
Yaraslaut Aug 6, 2026
b013297
testkit: configurable server limits, a raw backend accessor, and a te…
Yaraslaut Aug 6, 2026
0819e23
cmake: make a rung's test target actually linkable and runnable
Yaraslaut Aug 6, 2026
daaa633
pastebin: fix App teardown ordering and give the sweep a settle seam
Yaraslaut Aug 6, 2026
8b50b40
pastebin: add PasteModel's model test suite
Yaraslaut Aug 6, 2026
c3873a7
ci: measure examples/pastebin in the coverage gate
Yaraslaut Aug 6, 2026
2c8898e
testkit: correct BackendRig's stale teardown-order doc comment
Yaraslaut Aug 6, 2026
1d95b07
pastebin: cover a malformed expiresAt, and name the race test's blind…
Yaraslaut Aug 6, 2026
0cacfd1
pastebin: add PastePresenter and the finding-021 forms-controller glue
Yaraslaut Aug 6, 2026
46c08f3
tests: add regression coverage for finding 023 (Completion::onError s…
Yaraslaut Aug 6, 2026
6182867
pastebin: add PastePresenter tests (full backend-mode matrix)
Yaraslaut Aug 6, 2026
f9404c1
pastebin: add desktop GUI shell, standalone server binary, demo seeding
Yaraslaut Aug 6, 2026
c832152
ladder: announce every desktop-client skip; file finding 024
Yaraslaut Aug 6, 2026
7e1f1b7
pastebin: move the QML adapters and schema document into gui_lib
Yaraslaut Aug 7, 2026
6ecb0be
ladder: build the client-side stack under Emscripten
Yaraslaut Aug 7, 2026
a7294df
pastebin: add the WASM client
Yaraslaut Aug 7, 2026
e774738
ci: build the ladder's GUI half and gate its WASM clients
Yaraslaut Aug 7, 2026
f52c1f4
docs: close out rung 1's definition of done
Yaraslaut Aug 7, 2026
d1ef31c
pastebin: include <QString>, not the QStringLiteral forwarding header
Yaraslaut Aug 7, 2026
1ade0aa
ci: install the SQLite ODBC driver where the ladder is built
Yaraslaut Aug 7, 2026
c54a117
findings: triage 018, file 026 for the three unfixed sibling writers
Yaraslaut Aug 7, 2026
56e0067
pastebin: reject an over-length syntax instead of truncating it silently
Yaraslaut Aug 7, 2026
6b6ed14
testkit: stop QtDrivenMainThreadExecutor's drain timer outliving the …
Yaraslaut Aug 7, 2026
8d2ea8f
pastebin: test the QML adapters, and measure them in the coverage gate
Yaraslaut Aug 7, 2026
d327d56
pastebin: say plainly that the rung is shipped, not exited
Yaraslaut Aug 7, 2026
b3f43bb
pastebin: take the created id from the create reply, not from the lis…
Yaraslaut Aug 7, 2026
c3275a4
pastebin: fix three README inaccuracies caught by the fix-wave re-review
Yaraslaut Aug 7, 2026
e3a416e
Fix three correctness bugs found by the whole-branch code review
Yaraslaut Aug 7, 2026
e660443
bookmarks: resolve rung-2 design questions and write the implementati…
Yaraslaut Aug 7, 2026
7f7a6d2
bookmarks: add the rung's signed-token authorizer and principal charset
Yaraslaut Aug 7, 2026
1e0749d
bookmarks: add core strong types, unit system, and error hierarchy
Yaraslaut Aug 7, 2026
d91460a
bookmarks: add Bookmark DTOs
Yaraslaut Aug 7, 2026
a5026da
bookmarks: add Tag, BulkEdit, SharedFeed, and import/export DTOs
Yaraslaut Aug 7, 2026
ab351c4
bookmarks: add entities, schema migration, and the WithMapper mixin
Yaraslaut Aug 7, 2026
0182040
bookmarks: add BookmarkModel CRUD, archive/unarchive, and tag replace…
Yaraslaut Aug 7, 2026
f0668cd
bookmarks: add BookmarkModel ListBookmarks and GetChangesSince
Yaraslaut Aug 7, 2026
bdf90d7
bookmarks: set ListBookmarks nextCursor independent of post-filter em…
Yaraslaut Aug 7, 2026
307db68
bookmarks: add BulkEdit (outbox-managed) and RecordMetadata
Yaraslaut Aug 7, 2026
834f086
bookmarks: fix BulkEdit outbox idempotency-key collision (review find…
Yaraslaut Aug 7, 2026
92fc127
bookmarks: add TagModel (RenameTag, outbox-managed MergeTags, ListTags)
Yaraslaut Aug 7, 2026
f234679
bookmarks: add SharedFeedModel
Yaraslaut Aug 7, 2026
82596a6
bookmarks: add Netscape import/export
Yaraslaut Aug 7, 2026
5993ad9
bookmarks: fix URL entity-decode/escape asymmetry on import/export
Yaraslaut Aug 7, 2026
5699fb8
bookmarks: add App (server bootstrap, AuthModel/Login, metadata worke…
Yaraslaut Aug 7, 2026
9690ccc
bookmarks: add CMakeLists.txt, completing the buildable rung skeleton
Yaraslaut Aug 7, 2026
b794a45
bookmarks: fix pre-existing -Wmissing-designated-field-initializers a…
Yaraslaut Aug 7, 2026
6676407
bookmarks: add the backend-mode matrix for BookmarkModel's create/lis…
Yaraslaut Aug 7, 2026
15455b9
bookmarks: add BulkEdit atomicity, cross-user Socket auth, wrong-secr…
Yaraslaut Aug 7, 2026
e287fcf
bookmarks: document the cross-model rename race and prove the worker'…
Yaraslaut Aug 7, 2026
1dae74b
bookmarks: add BookmarkPresenter, TagPresenter, SharedFeedPresenter
Yaraslaut Aug 7, 2026
d1f2ef7
bookmarks: fix a permanent handler-not-bound flake in TagPresenter/Sh…
Yaraslaut Aug 7, 2026
ee399f2
bookmarks: make Login reachable over a real server, and title optional
Yaraslaut Aug 7, 2026
34872ed
bookmarks: add the schema-driven GUI shell, server binary, and QML sm…
Yaraslaut Aug 7, 2026
93a15f1
bookmarks: stop the server's background timers before draining fetches
Yaraslaut Aug 7, 2026
297f879
bookmarks: give the QML adapter layer its own test suite
Yaraslaut Aug 7, 2026
f18af66
findings: file 031 -- DynamicForm has no control for JSON array-typed…
Yaraslaut Aug 7, 2026
4c8b9bf
bookmarks: add the WASM client and extend the WASM CI gate to cover it
Yaraslaut Aug 7, 2026
3252050
bookmarks: clear the 13 rung-owned compiler warnings that crept back in
Yaraslaut Aug 7, 2026
8158777
bookmarks: keep the server alive when a background pass or a bad env …
Yaraslaut Aug 7, 2026
a47db71
bookmarks: bound ImportBookmarks' field writes and give TooLarge a th…
Yaraslaut Aug 7, 2026
ba65e94
bookmarks: correct the rung's stale comments, and disclose its real gaps
Yaraslaut Aug 7, 2026
9112fca
bookmarks: make TooLarge actually reachable, not just correctly-thrown
Yaraslaut Aug 7, 2026
e32e7f5
bookmarks: guard the unsetenv call with the same check as its include
Yaraslaut Aug 7, 2026
2be5ef8
polls: resolve rung-3 design questions and plan its framework prerequ…
Yaraslaut Aug 7, 2026
3457b14
core: add a client-side execute deadline (Bridge::setExecuteDeadline)
Yaraslaut Aug 7, 2026
8dbbb0b
core: cover the execute deadline's disarm and verify its header stand…
Yaraslaut Aug 7, 2026
7a37b2f
core: add an async register-or-attach/attach path for shared/keyed mo…
Yaraslaut Aug 7, 2026
222091b
core: never invoke an async attach's onDone from inside the dispatch …
Yaraslaut Aug 8, 2026
6f1f46a
qt: cover the shared/keyed async wire methods against a real server
Yaraslaut Aug 8, 2026
ee2061e
docs: scope the async attach claims to what actually became non-blocking
Yaraslaut Aug 8, 2026
607be05
docs: correct closeConnection's refcount description in shared_instan…
Yaraslaut Aug 8, 2026
0fcdcff
findings: file 032 -- assignPrimary has no async path
Yaraslaut Aug 8, 2026
b9922e7
polls: record the ModelKey plain-std::string constraint on pollId
Yaraslaut Aug 8, 2026
e96a9c5
polls: write the rung-3 app implementation plan
Yaraslaut Aug 8, 2026
cc0a219
polls: add core strong types and error hierarchy
Yaraslaut Aug 8, 2026
bf2d0e0
polls: add poll/vote/comment DTOs
Yaraslaut Aug 8, 2026
6a2662f
polls: add vote/undo/event DTOs
Yaraslaut Aug 8, 2026
ff465db
polls: add entities, schema, and db_model.hpp
Yaraslaut Aug 8, 2026
2166e4b
polls: use SqlAnsiString for the three token fields, not plain std::s…
Yaraslaut Aug 8, 2026
5574c95
polls: add PollModel -- CreatePoll, OpenPoll, GetPollState
Yaraslaut Aug 8, 2026
1e2dc7d
polls: add SubmitVotes, UpdateVotes, AddComment
Yaraslaut Aug 8, 2026
32fc5ae
polls: close the Count-empty footgun at the type, and fix a stale tes…
Yaraslaut Aug 8, 2026
7508cf8
polls: add FinalizePoll and PollsAuthorizer
Yaraslaut Aug 8, 2026
77a8ac5
polls: add UndoLastVoteChange -- principal-scoped compensating action
Yaraslaut Aug 8, 2026
e19fff7
polls: fix UndoLastVoteChange's two-transaction window and test gap
Yaraslaut Aug 8, 2026
e6ef3f8
polls: add GetEventsSince -- the Zulip-pattern event log read path
Yaraslaut Aug 8, 2026
996c39b
polls: add App -- server bootstrap
Yaraslaut Aug 8, 2026
11fb427
polls: add CMakeLists.txt, completing the buildable rung skeleton
Yaraslaut Aug 8, 2026
cfe43e6
polls: drop a redundant CMake source listing, make finding 033's repr…
Yaraslaut Aug 8, 2026
476cd73
polls: add backend-mode matrix, shared-instance lifetime, and poisone…
Yaraslaut Aug 8, 2026
1d2966b
polls: cite finding 030 by name for a third reproduction site
Yaraslaut Aug 8, 2026
1a8d79a
findings: add the attachModel production-code reproduction to finding…
Yaraslaut Aug 8, 2026
3f55ed2
polls: add cross-poll admin-token isolation and messagesPerSecond+dea…
Yaraslaut Aug 8, 2026
87f050b
polls: add PollPresenter
Yaraslaut Aug 8, 2026
9272683
ladder: add the event-polling helper (this rung's framework-level del…
Yaraslaut Aug 8, 2026
f668425
ladder: fix EventPoller's use-after-free and success-callback ordering
Yaraslaut Aug 8, 2026
9e09bba
ladder: add EventPoller::resume and correct its production-wiring recipe
Yaraslaut Aug 8, 2026
7cfe292
polls: add the schema-driven GUI shell wired to the event-polling helper
Yaraslaut Aug 8, 2026
866af3d
polls: add the server binary
Yaraslaut Aug 8, 2026
04bbac6
polls: add the WASM client -- the first real exercise of async keyed …
Yaraslaut Aug 8, 2026
a6a4138
polls: give the DTOs rule-3 discipline -- no bare bools, no loose tokens
Yaraslaut Aug 8, 2026
09d19d7
core: give TimeoutScheduler a browser-timer build for single-threaded…
Yaraslaut Aug 8, 2026
2b07905
polls: delete the dead requireParticipant() and correct the security …
Yaraslaut Aug 8, 2026
1815b0d
findings: close 001 and 002 -- both were resolved by rung 3's own pre…
Yaraslaut Aug 8, 2026
fb3b66f
polls: bring README/Main.qml back in line with what the rung actually…
Yaraslaut Aug 8, 2026
0155b0c
core: close a switchBackend staleness race and two exception-safety g…
Yaraslaut Aug 10, 2026
98da089
polls: reject cross-poll option references, duplicate votes, and malf…
Yaraslaut Aug 10, 2026
aea624f
docs: design for applying PR #41's storage-type review comments ladde…
Aug 11, 2026
1043b7c
build: fix MSVC/Windows portability issues across ladder tests and Li…
Aug 11, 2026
9286e35
ci: fix the four standing CI failures blocking a green build
Aug 11, 2026
c970779
ladder: fix wasm_spike's QtWebSocketBackend call for QT_NO_SSL builds
Aug 11, 2026
db601e5
build: probe three -Weverything suppressions instead of assuming clan…
Aug 11, 2026
f8fe6da
build: suppress -Wc++20-compat and -Wdisabled-macro-expansion
Aug 11, 2026
d4b4a59
build+docs: fix three more WASM-leg-only Clang diagnostics
Aug 11, 2026
dcd411c
ci+bookmarks: fix the four remaining CI failures now that -Werror clears
Aug 11, 2026
2ddbb85
core+ladder: work around a libc++ formattability bug for auto-NTTP fo…
Aug 11, 2026
cc2ae7b
docs: update quantity_type.md spec for the toString() addition
Aug 11, 2026
124b4a2
ladder: fix a WASM-only undefined-symbol link gap in bookmarks and polls
Aug 11, 2026
18d9438
build: demote Lightweight's transitive includes to SYSTEM for gui_lib…
Aug 11, 2026
e737a77
ci+bookmarks: fix the four remaining CI failures now that -Werror clears
Aug 11, 2026
37e333a
ci: fix the four remaining CI failures now that -Werror clears further
Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 189 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,40 @@ jobs:
sudo apt-get install -y ninja-build catch2 libsqlite3-dev
wget -qO- https://apt.llvm.org/llvm.sh | sudo bash -s -- ${{ env.CLANG_VERSION }}

# Only the coverage leg builds the ladder: examples/common's
# hand-written GUI/testkit code is real coverage of morph's client
# stack (Bridge, backends, QtExecutor, completions — see
# examples/TESTING.md's "round-7 T4 reframe"), so it belongs in the
# coverage number the same way the models it will host later do
# (examples/IMPLEMENTATION.md rule 5). asan/tsan/ubsan skip this, same
# as before — "a GUI stack under TSan is mostly noise" — coverage
# instrumentation carries none of that risk.
- name: Install ODBC + SQLite driver (coverage leg only)
if: matrix.preset == 'clang-coverage'
run: |
# unixodbc-dev + libsqliteodbc: the application ladder (built by this
# leg only) fetches the Lightweight ORM, whose CMake runs
# `pkg_check_modules(ODBC REQUIRED odbc)`, and whose ladder fixtures
# open a real `DRIVER=SQLite3` connection at test time.
# Named explicitly rather than relied on from the runner image.
sudo apt-get install -y libgl1-mesa-dev unixodbc-dev libsqliteodbc

# Not the distro's Qt: examples/common/CMakeLists.txt requires 6.5+
# unconditionally (not gated on MORPH_BUILD_FORMS_QML) and Ubuntu
# 24.04 still ships 6.4.2 -- the identical gap the "all optional
# features" and "Application ladder" jobs' own install-qt-action steps
# already document. Named qt6-base-dev/qt6-websockets-dev/qt6-tools-dev
# used to be installed above; replaced wholesale rather than kept
# alongside aqtinstall's Qt, which would leave two Qt6 installs on the
# same runner for find_package() to pick between.
- name: Install Qt ${{ env.QT_VERSION }} (coverage leg only)
if: matrix.preset == 'clang-coverage'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
modules: qtwebsockets
cache: true

- name: Cache sccache
uses: actions/cache@v4
with:
Expand All @@ -159,23 +193,35 @@ jobs:
# morph::net and the SQLite offline queue are opt-in, but they are also
# where the memory/threading/UB risk actually lives (raw sockets, an I/O
# thread, a hand-rolled frame reader, a C API). Left off, the sanitizers
# and the coverage number both silently skipped them. Qt/QML and the
# fuzzers stay out of this matrix — they are covered by the
# linux-all-features job, and a GUI stack under TSan is mostly noise.
# and the coverage number both silently skipped them. QML and the
# fuzzers stay out of this matrix entirely — they are covered by the
# linux-all-features job, and a GUI stack under TSan is mostly noise;
# the ladder (Qt6::WebSockets, no QML) is the one exception, built only
# on the coverage leg, for the reason in the Qt install step above.
- name: Configure
run: |
EXTRA_ARGS=()
if [ "${{ matrix.preset }}" = "clang-coverage" ]; then
EXTRA_ARGS+=(-DMORPH_BUILD_QT=ON -DMORPH_BUILD_LADDER=ON -DMORPH_LADDER_RUNGS=all)
fi
cmake --preset ${{ matrix.preset }} \
-DMORPH_BUILD_NET=ON \
-DMORPH_BUILD_OFFLINE_SQLITE=ON \
-DCMAKE_C_COMPILER=clang-${{ env.CLANG_VERSION }} \
-DCMAKE_CXX_COMPILER=clang++-${{ env.CLANG_VERSION }} \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
"${EXTRA_ARGS[@]}"

- name: Build
run: cmake --build --preset ${{ matrix.preset }}

- name: Test
env:
# Harmless for the non-Qt legs (nothing reads it); required for the
# coverage leg's ladder tests, which open real Qt widgets/sockets
# on a runner with no display.
QT_QPA_PLATFORM: offscreen
run: |
if [ "${{ matrix.preset }}" = "clang-coverage" ]; then
LLVM_PROFILE_FILE="build/clang-coverage/%p.profraw" ctest --preset clang-coverage
Expand Down Expand Up @@ -253,6 +299,120 @@ jobs:
QT_QPA_PLATFORM: offscreen
run: ctest --preset gcc-debug

# ── Linux: application ladder testkit (path-filtered) ─────────────────
ladder-tests:
name: Application ladder
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # need history for the changed-paths diff below

- name: Determine whether the ladder needs to run
id: filter
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"
else
base="${{ github.event.before }}"
fi
if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
echo "run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
changed=$(git diff --name-only "$base" HEAD)
# src/qt/: the compiled bodies of morph_qt_impl — the very thing the
# testkit exists to conformance-test (and where finding 017's fix
# lands). CMakeLists.txt/cmake/ and this workflow itself: a change to
# any of them can break or silently skip this job.
if echo "$changed" | grep -qE '^(examples/(common|pastebin|bookmarks|polls|kanban)/|examples/CMakeLists\.txt$|include/morph/|src/qt/|cmake/|CMakeLists\.txt$|CMakePresets\.json$|\.github/workflows/ci\.yml$|examples/LADDER\.md|examples/IMPLEMENTATION\.md|examples/TESTING\.md)'; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
fi

- name: Cache apt packages
if: steps.filter.outputs.run == 'true'
uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-qt-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: apt-qt-

- name: Install GCC 15, ninja, catch2
if: steps.filter.outputs.run == 'true'
run: |
sudo apt-get update -q
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -q
# unixodbc-dev + libsqliteodbc: the application ladder fetches the
# Lightweight ORM, whose CMake runs
# `pkg_check_modules(ODBC REQUIRED odbc)`, and whose ladder fixtures
# open a real `DRIVER=SQLite3` connection at test time.
# Named explicitly rather than relied on from the runner image.
# libyaml-cpp-dev + libzip-dev: Lightweight's own CMakeLists.txt
# does `find_package(yaml-cpp)`/`find_package(libzip)` as system
# CONFIG packages, not through CPM (examples/bank/CMakeLists.txt's
# comment on the identical fetch) — without these, configure fails
# the moment MORPH_BUILD_LADDER=ON pulls Lightweight in.
# Qt itself is installed by the aqtinstall step below, not apt: see
# that step's comment for why the distro package is unusable here.
sudo apt-get install -y gcc-15 g++-15 ninja-build catch2 \
libyaml-cpp-dev libzip-dev libgl1-mesa-dev \
unixodbc-dev libsqliteodbc
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 15
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 15

# Not the distro's Qt: examples/common/CMakeLists.txt requires 6.5+
# (QQmlApplicationEngine::loadFromModule, used by MORPH_BUILD_FORMS_QML
# rungs) and Ubuntu 24.04 still ships 6.4.2 — the exact gap the "all
# optional features" job's identical step already documents. This job
# configures MORPH_BUILD_LADDER=ON without MORPH_BUILD_FORMS_QML, but
# examples/common/CMakeLists.txt's Qt6 6.5 REQUIRED applies unconditionally
# (it is not gated on MORPH_BUILD_FORMS_QML), so the floor still bites here.
- name: Install Qt ${{ env.QT_VERSION }}
if: steps.filter.outputs.run == 'true'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
modules: qtwebsockets
cache: true

- name: Cache sccache
if: steps.filter.outputs.run == 'true'
uses: actions/cache@v4
with:
path: /home/runner/.cache/sccache
key: sccache-ladder-${{ github.sha }}
restore-keys: sccache-ladder-

- name: Install sccache
if: steps.filter.outputs.run == 'true'
run: |
curl -sSL https://github.com/mozilla/sccache/releases/download/v0.9.1/sccache-v0.9.1-x86_64-unknown-linux-musl.tar.gz \
| tar -xz --strip-components=1 -C /usr/local/bin sccache-v0.9.1-x86_64-unknown-linux-musl/sccache

- name: Configure (gcc-debug, ladder + Qt on)
if: steps.filter.outputs.run == 'true'
run: |
cmake --preset gcc-debug \
-DMORPH_BUILD_QT=ON \
-DMORPH_BUILD_LADDER=ON \
-DMORPH_LADDER_RUNGS=all \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache

- name: Build
if: steps.filter.outputs.run == 'true'
run: cmake --build --preset gcc-debug

- name: Test (offscreen Qt platform, ladder tests only, stress excluded)
if: steps.filter.outputs.run == 'true'
env:
QT_QPA_PLATFORM: offscreen
run: ctest --preset gcc-debug -L ladder -LE stress --output-on-failure

# ── Linux: every optional feature enabled at once ─────────────────────
# Every MORPH_BUILD_* option below is off by default, and until this job
# existed no CI configuration turned any of them on — so several thousand
Expand Down Expand Up @@ -292,8 +452,21 @@ jobs:
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -q
# unixodbc-dev + libsqliteodbc: the application ladder (enabled in
# the configure step below) fetches the Lightweight ORM, whose CMake
# runs `pkg_check_modules(ODBC REQUIRED odbc)`, and whose ladder
# fixtures open a real `DRIVER=SQLite3` connection at test time.
# Named explicitly rather than relied on from the runner image.
# libyaml-cpp-dev + libzip-dev: Lightweight's own CMakeLists.txt
# (examples/bank/CMakeLists.txt's comment on the same fetch) does
# `find_package(yaml-cpp)`/`find_package(libzip)` as system CONFIG
# packages, not through CPM — without these, Lightweight's configure
# fails with "could not find a package configuration file" the
# moment MORPH_BUILD_LADDER=ON pulls it in here.
sudo apt-get install -y ninja-build catch2 \
libsqlite3-dev libsodium-dev libssl-dev \
unixodbc-dev libsqliteodbc \
libyaml-cpp-dev libzip-dev \
libgl1-mesa-dev libxkbcommon-x11-0 libxcb-cursor0 libxcb-icccm4 \
libxcb-keysyms1 libxcb-shape0 libxcb-xinerama0
if [ "${{ matrix.compiler }}" = "gcc" ]; then
Expand Down Expand Up @@ -336,10 +509,22 @@ jobs:
EXTRA="-DCMAKE_C_COMPILER=clang-${{ env.CLANG_VERSION }} -DCMAKE_CXX_COMPILER=clang++-${{ env.CLANG_VERSION }}"
fi
# shellcheck disable=SC2086
# MORPH_BUILD_LADDER belongs in this job by its own charter ("every
# MORPH_BUILD_* option … enabling them together also proves they
# compose") and closes a real hole: until it was added here, *no* CI
# leg configured MORPH_BUILD_LADDER=ON together with
# MORPH_BUILD_FORMS_QML=ON. The ladder-tests job below cannot — its
# distro Qt is 6.4.2, under the 6.5 floor MORPH_BUILD_FORMS_QML
# requires — so each rung's QML module, desktop client and offscreen
# engine-load smoke test were built by nothing at all. This job has
# Qt ${{ env.QT_VERSION }} from aqtinstall, so here they are built,
# and the smoke test runs, on every push.
cmake --preset ${{ matrix.preset }} \
-DMORPH_BUILD_NET=ON \
-DMORPH_BUILD_QT=ON \
-DMORPH_BUILD_FORMS_QML=ON \
-DMORPH_BUILD_LADDER=ON \
-DMORPH_LADDER_RUNGS=all \
-DMORPH_BUILD_OFFLINE_SQLITE=ON \
-DMORPH_BUILD_LOAD_TESTS=ON \
-DMORPH_BUILD_HMAC_EXAMPLES=ON \
Expand Down
149 changes: 149 additions & 0 deletions .github/workflows/wasm-ladder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: WASM ladder gate

# Compile gate for the application ladder's WebAssembly clients — the one
# examples/TESTING.md's CI tiering promises ("the WASM compile gate for the
# affected rungs") and the only thing in this repository that can actually
# verify them: no Emscripten toolchain was available where rung 0's WASM-remote
# spike (examples/common/wasm_spike) or rung 1's WASM client
# (examples/pastebin/gui_wasm) were authored, so both shipped structurally
# complete and never compiled. Until this job runs green, treat every WASM
# target here as unverified.
#
# Deliberately separate from wasm-demo.yml (bank's WASM GUI): different sources,
# different path filter, and nothing here is deployed anywhere — this builds and
# stops. Single-threaded Qt-for-WASM, same as that workflow.

on:
push:
branches:
- master
paths:
- 'examples/common/**'
- 'examples/pastebin/**'
- 'examples/bookmarks/**'
- 'examples/polls/**'
- 'examples/kanban/**'
- 'examples/CMakeLists.txt'
- 'cmake/**'
- 'include/morph/**'
- 'src/qt/**'
- 'CMakeLists.txt'
- '.github/workflows/wasm-ladder.yml'
pull_request:
branches:
- master
paths:
- 'examples/common/**'
- 'examples/pastebin/**'
- 'examples/bookmarks/**'
- 'examples/polls/**'
- 'examples/kanban/**'
- 'examples/CMakeLists.txt'
- 'cmake/**'
- 'include/morph/**'
- 'src/qt/**'
- 'CMakeLists.txt'
- '.github/workflows/wasm-ladder.yml'

concurrency:
group: wasm-ladder-${{ github.ref }}
cancel-in-progress: true

env:
QT_VERSION: 6.8.3
EMSDK_VERSION: 3.1.56 # the emscripten Qt 6.8 was built against

jobs:
build-ladder-wasm:
name: Build the ladder's WASM clients
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install build tools
run: |
sudo apt-get update -q
sudo apt-get install -y ninja-build

# aqtinstall gives a matched host + wasm Qt pair (same cmake glue), so no
# host/target version skew. qtwebsockets on *both*: morph::qt links
# Qt6::WebSockets, and a ladder WASM client is a remote client by rule
# (examples/IMPLEMENTATION.md rule 4's WASM clause), so the transport is
# not optional here the way it is for bank's local-only demo.
- name: Install Qt (host desktop)
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: linux
target: desktop
arch: linux_gcc_64
modules: qtwebsockets
dir: ${{ runner.temp }}/qt

- name: Install Qt (wasm, single-threaded)
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: all_os
target: wasm
arch: wasm_singlethread
modules: qtwebsockets
dir: ${{ runner.temp }}/qt

- name: Set up emsdk
uses: mymindstorm/setup-emsdk@v14
with:
version: ${{ env.EMSDK_VERSION }}
actions-cache-folder: emsdk-ladder-cache

# MORPH_CLIENT_ONLY is mandatory, not a tuning knob: a rung's presenters
# are BridgeHandler<Model> templates, so the client names its model type
# even though it never hosts one — and without this option morph still
# emits the registrars that closure over that model's ODBC-backed
# execute() bodies, which cannot link in a browser
# (docs/spec/core/registry.md). morph_add_rung() fails the configure with
# that explanation if it is missing.
#
# MORPH_BUILD_TESTS=OFF: Catch2 binaries are not browser artifacts, and
# examples/common/CMakeLists.txt returns before its Catch2/Lightweight
# section under Emscripten for exactly that reason.
- name: Configure
run: |
export EM_CACHE="$PWD/.emcache"
mkdir -p "$EM_CACHE"
HOST=${{ runner.temp }}/qt/Qt/${{ env.QT_VERSION }}/gcc_64
WASM=${{ runner.temp }}/qt/Qt/${{ env.QT_VERSION }}/wasm_singlethread
# The all_os/wasm package extracts its scripts without the exec bit.
chmod +x "$WASM"/bin/* || true
"$WASM/bin/qt-cmake" -S . -B build-wasm-ladder -G Ninja \
-DQT_HOST_PATH="$HOST" \
-DMORPH_BUILD_QT=ON \
-DMORPH_BUILD_FORMS_QML=ON \
-DMORPH_BUILD_LADDER=ON \
-DMORPH_LADDER_RUNGS=all \
-DMORPH_CLIENT_ONLY=ON \
-DMORPH_BUILD_TESTS=OFF \
-DMORPH_BUILD_EXAMPLES=OFF

# The rung-0 spike and rungs 1-3's clients, built by name so a target
# that silently stops being generated (morph_add_rung() skips a rung's
# gui_wasm when its prerequisites are missing, announcing why) fails this
# job instead of passing it vacuously. The plain build that follows
# covers any further rung automatically, so this file does not need
# editing again just to add another named target.
- name: Build the WASM-remote spike and every rung's WASM client
run: |
export EM_CACHE="$PWD/.emcache"
cmake --build build-wasm-ladder --target morph_ladder_wasm_spike
cmake --build build-wasm-ladder --target ladder_pastebin_gui_wasm
cmake --build build-wasm-ladder --target ladder_bookmarks_gui_wasm
cmake --build build-wasm-ladder --target ladder_polls_gui_wasm
# Catches any further rung's WASM client too, without editing this
# file again -- closing the gap rung 1's own final review flagged.
cmake --build build-wasm-ladder

# Informational: the build steps above are the gate. Listed rather than
# asserted by path, since where Qt drops a wasm bundle is Qt's business.
- name: Show the produced artifacts
run: find build-wasm-ladder -name '*.wasm' -o -name '*.html' | sort
Loading
Loading