Applying a preset was a conversation the app held with the module: snapshot
runtime.ini, ask whether the service was running, read runtime.ini again, stage
a candidate, commit it, verify it, then restart. Seven privileged round trips,
each paying a libsu round trip and a full common.sh sourcing, and — worse — the
rollback decisions lived in Kotlin. The app was deciding what the module's state
was, which is exactly what the typed machine payload exists to prevent.
zapret2/scripts/zapret-apply-preset.sh is now the single app-facing entry point
for that mutation. It sources the lifecycle helpers once, inherits the Android
lease through ZAPRET2_LIFECYCLE_TOKEN, and under that one lock it validates the
request with the packaged name policy, qualifies the preset through the same
compile-and-dry-run the preview machinery already owned, publishes the selection
through runtime-config.sh --commit-candidate, and hands the replacement to
zapret-start.sh --replace exactly as zapret-restart.sh does. Nothing here
reimplements a transaction that already had an owner.
Because the qualification happens before the commit, an incompatible preset is
now refused with the live selection untouched instead of being written, failed
and rolled back. A failed replacement restores the previous generation and says
so, and a rollback that cannot restore it says that instead — the payload
distinguishes the two, carrying the exact Z2_ERROR identity of whichever nested
stage refused. A service the user had stopped is still only saved to, measured
from the committed lifecycle receipt the way zapret-status.sh derives Z2_PROCESS.
The app projects that payload and nothing more. It keeps the stepwise flow for
one reason: a module generation installed before this entry point existed cannot
grow it, so the same round trip answers with an unsupported sentinel and the old
path runs. A payload that is truncated, inconsistent or written by a newer module
is never guessed at — the repository reads the published selection, and accepts
an application only when the live owner generation is the one this lease stamped,
which is the same proof the restart path already required.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>