dmx: loud reporting for venue universes the profile can't drive - #424
Merged
Conversation
A venue can patch fixtures on universes the active profile configures no output for; the engine used to drop their DMX silently — three quarters of a four-universe house rig staying dark at a gig with no clue why. Now: - Venue registration reports each unconfigured universe once, naming the fixtures patched there. - The effects loop's routing warns once per universe (not per 44Hz tick) if commands are dropped anyway — the backstop, not the report. - A multi-universe example venue (warehouse) and docs cover the rule: shows never mention universes; every universe a venue references needs an output under dmx.universes. - Design doc: universe-coverage joins the pre-show lint list (P1b), and chase direction is noted as position-resolved once venues carry positions — list order today has no spatial or cross-universe meaning. The render path was already multi-universe end to end (per-fixture universes, per-universe routing and output threads); these are the guardrails that make a multi-universe venue fail loudly instead of partially and silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SKzGqZbcZtMR5ZqtDJoDzz
mdwn
pushed a commit
that referenced
this pull request
Sep 9, 2026
Backfills the GDTF import work (#422, #423, #425, #426) and the multi-universe reporting change (#424), which landed after the 0.16.0 release commit without changelog entries, and adds the Debian packaging entry alongside them. The internal-only groundwork PRs are folded into the single GDTF entry rather than listed separately: on their own they had no user-facing surface, and it is the feature they add up to that is worth reading about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgFMfQVvNPsM4huUYmqXwn
mdwn
added a commit
that referenced
this pull request
Sep 9, 2026
…#427) * packaging: Debian package and R2-hosted apt archive Adds a .deb and the pipeline to publish it, giving the Raspberry Pi image an install payload and everyone else an upgrade path that is not "download a tarball again". The package does what the deployment guide currently asks operators to do by hand: creates the mtrack service account, adds it to audio, creates and chowns the project directory, generates the systemd unit, and enables the service. Because /etc/default/mtrack is unpacked first, MTRACK_PATH is known by the time postinst runs, so the unit is rendered with ProtectSystem=strict and a matching ReadWritePaths rather than the weaker pathless fallback -- the hardened form is what you get by default instead of what you get if you remember the argument. The unit is generated by the installed binary rather than shipped as a file, so src/cli.rs stays its only definition and an upgrade re-renders it -- which is the "regenerate your unit" step the guide has to ask for today. Operator edits are preserved: postinst compares against an MD5 of what it last wrote and backs off if the file has changed, since the same guide tells people to add RequiresMountsFor= themselves. It also resolves the binary absolutely, never through PATH, so a leftover /usr/local/bin install from the tarball era cannot end up baked into ExecStart. Purging keeps /var/lib/mtrack and the account that owns it. A purge should not take a band's set with it, and removing the user would strand those files under a uid a reinstall would not reallocate. Dependencies are hand-written rather than cargo-deb's $auto. dpkg-shlibdeps emits versioned dependencies, and libasound2t64 only carries "Provides: libasound2 (= <version>)"; a versioned Provides satisfies an unversioned Depends but not a versioned one, so an auto-generated package would build on bookworm and refuse to install on trixie. The alternation resolves on both sides of the time_t rename. Packages are built by repackaging the binaries the release already published, so a .deb and a tarball of the same version hold identical bytes and one amd64 runner emits both architectures. The apt archive is a static tree synced to R2, published pool-first so an index never names an object the store does not have yet, and never pruned so `apt install mtrack=<older>` keeps working as a rollback. The publish-apt job is skipped until APT_R2_BUCKET is set, so the .deb on the release page is useful before anyone owns a signing key. packaging/README.md carries the one-time setup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgFMfQVvNPsM4huUYmqXwn * docs: restore [Unreleased] changelog section Backfills the GDTF import work (#422, #423, #425, #426) and the multi-universe reporting change (#424), which landed after the 0.16.0 release commit without changelog entries, and adds the Debian packaging entry alongside them. The internal-only groundwork PRs are folded into the single GDTF entry rather than listed separately: on their own they had no user-facing surface, and it is the feature they add up to that is worth reading about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgFMfQVvNPsM4huUYmqXwn * packaging: install-and-purge test for the Debian package The jobs that build and publish the package live in publish.yaml, which runs on tags, so nothing exercised the maintainer scripts on the change that touched them -- a broken postinst would have surfaced during a release. This adds `make test-deb` and runs it on pull requests. It covers the cases that only exist in the package: that the generated unit names the packaged binary and carries the strict sandbox, that a reinstall leaves an operator-edited unit alone and says so, that an unedited one is re-rendered, that a stale /usr/local/bin copy cannot capture ExecStart, and that purging keeps the song library and the account that owns it. Verified it fails when the PATH resolution fix is reverted, rather than passing regardless. The test builds into its own target directory. It stages a debug binary where cargo-deb expects a release one, and doing that under target/release would leave a debug binary that cargo -- which fingerprints against its own database, not the file -- may then decline to rebuild. Unlike the systemd integration test this runs on pull requests: it costs a debug build against a cache the other jobs have already warmed rather than a container image, and what it checks is what lands on disk, not whether the service comes up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgFMfQVvNPsM4huUYmqXwn * ci: lint shell scripts with shellcheck Nothing checked the shell in this repo, and the Debian maintainer scripts this branch adds run as root on a user's machine at install time -- the least forgiving place to leave a quoting mistake. Adds `make lint-shell` and a job that runs it on pull requests. The file list is discovered rather than written down, so a script added later is linted without anyone remembering to add it. The Debian maintainer scripts carry no extension and so are missed by a *.sh glob; everything in packaging/debian is shell except the conffile. shellcheck is installed rather than taken from the runner image, which carries an older version. A lint gate that moves underneath you flags things nobody changed and misses things nobody added. Two pre-existing scripts needed adjusting to pass: - tests/systemd/test.sh writes its assertions as `bash -c '... "$0" ...' "$arg"`, where $0 is bash -c's positional parameter and must not be expanded by the outer shell. The single quotes are correct -- the file takes an SC2016 disable rather than a change, and the comment says why so nobody "fixes" it later. - scripts/hardware-test.sh ran `cd "$PROJECT_ROOT"` unguarded, so a failed cd would have built and tested in whatever directory it happened to be in (SC2164). All eight shell files in the repo pass. Verified the target fails on a deliberately broken script rather than passing vacuously. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgFMfQVvNPsM4huUYmqXwn --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up from a venue-portability review: the DMX render path is already multi-universe end to end (per-fixture universes in the venue DSL and model, per-universe command routing, per-universe output threads via OLA) — but a venue patching universes the active profile doesn't configure failed silently:
update_effectsdropped those commands with no warning anywhere. At an unfamiliar venue with a multi-universe patch, most of the rig would simply stay dark.warehousevenue and a docs section stating the rule — shows never mention universes (they target groups), and every universe a venue references needs an output underdmx.universes.left_to_rightis group-list order, spatially meaningless on any number of universes).Test plan
cargo test— 3422 pass; fmt/clippy/licensure clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01SKzGqZbcZtMR5ZqtDJoDzz