Repository navigation
Omachord 0.5.0: brightness lifecycle and review follow-ups - #8
Merged
Merged
Conversation
Drop the hard-coded /tmp/opencode temp-root probes in favor of
${TMPDIR:-/tmp}. test/render/render.sh renders the real configuration,
so it now writes to a fresh temporary directory by default and keeps an
explicit output-directory argument. The version check message no longer
names a specific release.
Pin actions/checkout to the v4.4.0 commit SHA and the debian:trixie-slim container to its manifest-list digest. Install strace so the write-error injection and watcher checks run instead of skipping. Run on push only for main and v* tags plus pull requests, and cancel superseded PR runs. Add Dependabot for weekly GitHub Actions updates. Permissions stay contents: read.
The bar icon's ThemePalette ran the theme-palette runner every 2 s whenever the icon was visible, although only the popup uses its success green. Bind it to the popup's opened state; opening already forces a reload.
applyLiveLog, conditionOwned and the runner all accept condition|service as the condition service's own trigger, but seedLatches only recognised condition. A restart with legacy service entries in the history therefore latched a service deactivation and missed a service run.
…ice keeps Panel.conditionReason formatted retryAt with new Date(...).toISOString(), which throws RangeError for a non-finite or out-of-range value. clockTime already accepts milliseconds; its toMs now also rejects values outside the Date range. The Activity view and the bar popup said a routine stays on while its conditions hold whenever it had conditions, but the service only ends condition-owned activations (condition/service trigger, keepUntil conditions). Both now use Conditions.endsWithConditions and one shared wording, and the Panel's active rows carry keepUntil like the service's.
The runner's clean_strings rejects NUL, CR and LF in every configuration string, and its schema caps actions and end actions at 64, conditions at 16 and routines at 256. Model validation accepted all of these, so a save only failed with the runner's generic schema error. Model now checks every action, condition, trigger and name string, gives shell commands a specific one-line hint, enforces the counts in validateRoutineDetails, and exposes validateConfigLimits, which the Panel checks before any apply. The editor refuses to add a 65th action or 17th condition with an explanation and uses Model.validateArguments while staging arguments. Also make Model.CONDITION_TYPES the editor's single condition catalogue and correct the HOOKS comment: the README table is not generated from it.
With an uncommitted configuration the Panel switch reached
mutateConnection("connect") and returned without any feedback. It now
shows the same review-and-approve notice as the failed load, which a later
successful load clears.
README: add a Quick start and contents, explain what enabling Omachord changes (autostart connects as soon as the plugin is enabled, not on first use), name the Arch packages behind each dependency, complete the runner command table (config show, autostart, bindings, commands, trigger hook, logs limits, run-history trimming) and the evaluate/reload IPC calls, add plugin-manager updates, rewrite removal with the CLI path, recovery after removing while On, and what stays behind, and extend the Files table. The Security section is reduced to end-user points with reworded sudo guidance. SECURITY.md: list the supported environment overrides with defaults, state that internal knobs are not honored, and take over the internal invariants formerly in the README. Move release notes into CHANGELOG.md with an Unreleased entry, add the marketplace verification step to RELEASING.md and align its review wording with the zero-approval ruleset, and drop known issues that are being fixed.
Brightness recovery moves activation records to schema version 3 and adds runner commands, so this is a minor release.
prepare-state now exchanges two files in a private scratch directory once per runner invocation and fails with a clear coreutils 9.5 requirement instead of later reporting a phantom concurrent writer. A failed no-replace publish or removal move is reported as an I/O error when the destination is unchanged, and as a compare mismatch only when another writer is evident.
Service, Panel, BarWidget and ThemePalette each copied the runner path fallback chain, and Service and Panel each had their own parseJson. Move both into Runner.js. The QML defaults now honour OMACHORD_OMARCHY_CONFIG_DIR like bin/omachord, for the default plugin path and for the service's watched configuration file. The bar's palette now takes the service's resolved path, which already applies the absolute override first. Also drop the redundant ternary in finishWidget, which parsed the reply the same way on both branches.
The action supervisor now supports only the capture mode the runner uses; the unreachable stop/release/exited/timeout marker protocol is removed, so an unwritable control directory (for example on ENOSPC) no longer fails a successful action. The supervisor tracks its own deadline and writes an advisory outcome line, letting the runner report "timed out" only when the deadline fired, "Action was killed (signal N)" for other kills, and a clear supervisor-failure message for internal errors. Before exec, the action child closes every inherited descriptor above stderr. Capture mode, limit and timeout are explicit run_capture arguments instead of environment variables, so they neither leak into action trees nor can be injected. OMACHORD_CAPTURE_*/OMACHORD_BULK_CLEANUP are dropped from the ambient environment at entry, the supported timeout overrides are validated (invalid-environment), and bulk-cleanup hook suppression is bound to the live runner's pid and start time so detached descendants of a finished runner never suppress hooks.
No QML Process had a deadline, so a hung runner wedged its caller: the service's connection barrier (which also blocks condition work), a manual routine worker, or the Panel's loading and saving locks. ProcessWatchdog.qml attaches a deadline to one Process. When it passes, the process gets SIGTERM (which the runner traps to abort its transaction) and SIGKILL after a grace period; the ordinary exit handler then runs with a non-zero code, and reply() substitutes a runner-shaped timeout refusal so existing failure paths report why. Deadlines: 30 s for probes, 60 s for connect and autostart, and 10 min for work that can run routine actions (run/activate/deactivate workers, disconnect and config apply, which can end routines), well above the runner's 30 s per-action limit. Service and Panel expose them as properties so fixtures can shorten them. test/qml-watchdog-test.sh drives Service and Panel against a new fake runner that hangs on demand, including one that ignores SIGTERM, and checks that every state machine recovers and no hung process survives. The fake enforces the real runner's argv grammar through a shared fake-runner-grammar helper.
The fakes accepted any argv, used revisions such as sha256:test that the
runner's ^sha256:[0-9a-f]{64}$ checks reject, replied with invented states
("complete"), and the Panel fake echoed a config field in its config
apply reply that bin/omachord never sends (it emits ok, connected,
warnings, revision, deactivated).
The bash fakes now source fake-runner-grammar, which accepts exactly the
argv main() and the command_* functions accept and otherwise answers with a
usage-shaped error and exit 2, logging it so each fixture fails on an
UNEXPECTED call. Revisions are real-shaped, routine, connect, disconnect
and apply replies match the runner's JSON, and the one deliberately
malformed connection reply is labelled as such.
With the fake matching production, drop Panel's test-only result.config
branch: an enable batch commits exactly the submitted document.
The state-directory watch check needed path: root.stateDir immediately followed by watchChanges: true, the no-shell check covered Service.qml only, the absolute-runner check skipped ThemePalette and BarWidget, and many patterns used [\s\S]*? from one function name to text that could sit in any later function. Add small brace-matching helpers (fn, block, enclosing, objects) that skip strings and comments. Every FileView in every QML file is now checked for a whole-state-directory watch regardless of property order, every QML file for shell argv, the bar for delegating runner resolution and for polling its palette only while open, and function-level assertions only look inside that function's body.
- Restore brightness to the backend-achievable target: an external display's original 0 is restored and compared as 1, so recovery completes instead of holding forever. - Confirm brightness writes with a bounded re-read loop; accept a one-step backlight snap only when two reads agree, and record the observed value as applied. A value still at the original is only final after a second read agrees (late-applying writes). - Held records now fail with code brightness-held and can be released with the revision-bound `recovery accept-brightness <id> <revision>`, which marks unresolved entries skipped via the CAS helper and finishes ending the routine. - bind-brightness gains --confirm-if-applied and no longer binds v2 end-action records whose committed plan has no brightness action. - Keep LAST_ERROR from brightness helpers (out-parameters instead of command substitution), rename the activation-record fingerprint so it no longer shadows the transaction SNAPSHOT_EXPECTED array, and fix the recovery usage ordering.
- A brightness-unavailable condition start now retries on a capped backoff (1, 2, 5, then 10 minutes by consecutive attempt) instead of waiting for the whole condition period; false edges, revision changes and successes still clear it. - Panel "Retry now" asks the service to clear the failure and re-evaluate, so the start stays condition-owned. - Panel offers "Keep brightness" after an End fails with brightness-held, confirming then running recovery inspect and accept-brightness bound to the inspected revision. - Update the Lean retry model, conformance vectors and proof notes; liveness/progress is explicitly not proved.
Connect always appends "\n<loader>\n"; Disconnect drops the loader and, when the loader is the final line, exactly what the append added: the one blank separator line, or the newline that terminated an unterminated last line. Connect/Disconnect cycles therefore no longer add a blank line to the user's bindings.lua each time; other user blank lines are never removed. bindings.lua backups (one per Connect and Disconnect) are pruned to the ten newest plus the oldest, the pre-Omachord original, via the fs helper's remove-file. Reads of user-controlled integration files (bindings.lua, generated Lua, desktop entry, icon, hook wrappers, shell.json) now go through a nonblocking, no-follow dd, so a FIFO swapped in while a lock is held fails the operation instead of blocking it forever.
Attach a ProcessWatchdog to the panel's brightness recovery process, teach the shared fake runner grammar the recovery subcommands, and rename the Python brightness fixture so CI's bash -n loop over test/qml-runtime/fake-* no longer parses it; it now enforces the shared argv grammar.
A routine command now holds the shared config lock only while it resolves
its definition from the committed snapshot and, for a stateful activation,
until its activation record is claimed. After that the routine lock alone
serializes it with writers, so config apply, connect and disconnect no
longer time out ("Timed out locking configuration state") while a routine
runs a long action. Bulk writers wait (bounded by LOCK_TIMEOUT) for a
routine's lock before ending it and report the retryable code
routine-running when it is still busy; an end-plan check that races a
checkpoint write is re-read under the routine lock. Hook fan-out keeps the
lock until its last routine, whose claim releases it.
config apply and disconnect now take every snapshot and concurrent-edit
fingerprint before ending orphaned/all routines, so a side-effect-free
failure leaves routines active. Ending routines is irreversible: once it
has happened, every failure JSON carries the ended ids in `deactivated`
(also on the failure that interrupted the ending itself); routines that
finished on their own while waited on are not reported.
- Route the unused shell.json backup removal through the fs helper. - transaction_abort cancels and reaps an interrupted action capture before its recovery reload starts a new one. - `omachord active` skips a record that ended between listing and reading instead of failing the whole listing. - safe_open_private_fd and log_run report their own failures rather than a stale LAST_ERROR; previously silent return paths (stat, mktemp) now set an error, and unchecked mktemp calls fail cleanly. - A signal abort also removes the routine's private temporary files. - usage() lists themes, service-status and theme-palette. - widget ensure/forget serialize on a runtime-directory lock.
Describe the routine-running wait, invalid-environment rejection, the per-command coreutils probe and action outcome attribution as built. Run the runner lifecycle suite in CI, and give its live bulk-cleanup owner check a real live process instead of a pipeline subshell PID.
anothaDev
force-pushed
the
fix/review-followups
branch
from
September 26, 2026 15:19
8a98a36 to
5f52b15
Compare
Check the finished-owner case with the real exited owner identity instead of an unrelated short-lived PID, print the runner reply when either check fails, and pin actions/checkout v5.1.0 (Node 24).
Both CI failures happen at the finished-owner hook call, which passes locally and under parallel load. Capture each call's exit status and reply so the next failure shows the runner's own error.
Under set -e, bash 5.2 exits the whole script when $(<file) cannot open its file, even inside a conditional. Checking a bulk-cleanup owner or process that had already exited therefore killed the runner silently on Debian's bash. A failed redirection on read just returns 1.
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.
Omachord 0.5.0 gates brightness routines on an available controller and binds writes and recovery to the original display. It also releases the configuration lock during routine actions, improves transaction failure reporting and process cancellation, and adds QML request deadlines. See CHANGELOG.md for the complete release notes.
Brightness activation records now use schema version 3. Restore active routines before downgrading. QML routine execution and bulk requests have a ten-minute aggregate deadline; longer operations can use the CLI. The deadline limits command execution, not how long a successfully activated routine can remain active.
Release candidate:
98774eb50c21efb31f96a94c9d197489e5c28774.Validation results
LEAN=<Lean 4.34.0> bash test/lean/run.shpassed for the checkout and committed archive: 68 theorems, 1,536 retry vectors (6,144 comparisons), and 48 description vectors. NosorryAx, project-defined axioms, or compiler errors were reported. These are scoped model proofs, not a proof of the whole application.bash test/run.shpassed from both the clean checkout and itsgit archive, including all 18 parsing/write-failure tests with strace enabled, all 39 brightness tests, QML interaction/service/watchdog tests, plugin validation, and linting. No fault-injection tests were skipped./procenvironment files; its service and panel assertions and both complete suites exited successfully.Review and repository controls
portablecheck, and prohibit force-pushes/deletion with no bypass. Zero GitHub approvals are required; CODEOWNERS approval is not enforced.Validation environment
The host runs Omarchy Edge, so its exact-version check correctly refused the stable release gate. Required automated validation uses the actual cached Omarchy 4.0.2 package CLI and QML payload in a temporary mount namespace, with host Hyprland 0.56.2 and Quickshell 0.3.1. Its native pacman records come from the same official cached packages; neither version output nor the repository gate was replaced. The runtime payload and the clean Git archive were independently checked against their source manifests.
This is an Omarchy 4.0.2 userspace test environment, not a booted stable installation. Node 26.10.0 and signature-verified Arch strace 7.2 are supplied only to the temporary environment. It uses private home/config/state/temp/runtime directories and read-only host filesystem mounts; Hyprland sockets remain available for the actual compositor version check. Earlier runs with missing test dependencies are excluded from release evidence.
Live validation also ran the actual Omarchy 4.0.2 shell and this candidate in a separate nested Hyprland instance with fresh configuration: service ready/enabled, widget present and visible, panel opens, and Tokyo Night → Catppuccin Latte crossfade reaches the new palette without QML loading, reference, type, or binding-loop errors. Only an empty fixture configuration was connected; no routines were executed. The user's installed v0.4.1 plugin and normal desktop were not upgraded or reconfigured. Eight offscreen renders covering panel views, compact layout, and popup states were also reviewed.