Skip to content

Omachord 0.5.0: brightness lifecycle and review follow-ups - #8

Merged
anothaDev merged 30 commits into
mainfrom
fix/review-followups
Sep 26, 2026
Merged

anothaDev merged 30 commits into
mainfrom
fix/review-followups

Conversation

@anothaDev

@anothaDev anothaDev commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

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.sh passed for the checkout and committed archive: 68 theorems, 1,536 retry vectors (6,144 comparisons), and 48 description vectors. No sorryAx, project-defined axioms, or compiler errors were reported. These are scoped model proofs, not a proof of the whole application.
  • Complete, unmodified bash test/run.sh passed from both the clean checkout and its git 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.
  • Required portable CI and CodeQL checks passed on the exact candidate. The portable run completed in 13m32s.
  • Live candidate service/bar/panel/theme checks and offscreen visual review passed as described below.
  • The watchdog cleanup scan emitted permission-denied warnings for unrelated protected /proc environment files; its service and panel assertions and both complete suites exited successfully.

Review and repository controls

  • An independent agent reviewed this exact candidate and approved its source changes subject to the release gates. This is not a GitHub approval or an independent human security audit.
  • No credential patterns, changed binary artifacts, generated state, new symlinks/submodules, or additional integration-owned paths were found in the release diff.
  • Active main-branch rules require a PR, resolved conversations, an up-to-date portable check, and prohibit force-pushes/deletion with no bypass. Zero GitHub approvals are required; CODEOWNERS approval is not enforced.
  • Private vulnerability reporting, secret scanning and push protection are enabled. Actions has default read permissions and cannot approve PRs. No open secret-scanning, code-scanning or dependency alerts were returned during review.
  • No tag protection or local release signing identity is configured; existing releases are mutable. No repository settings were changed.

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.

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.
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.
@anothaDev
anothaDev merged commit 9163b25 into main Sep 26, 2026
5 of 6 checks passed
@anothaDev
anothaDev deleted the fix/review-followups branch September 26, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant