Repository navigation
Releases: stewart-php/hass-stewart
Release list
v1.0.0
The first stable release. From here on, stewart-php/contracts, the generated classes, configuration keys,
environment variables, console commands and the image entrypoint settings change only in a major release. The code is
the same as 0.11.1.
Added
- Documentation at stewart-php.github.io/docs, and for contributors at
stewart-php.github.io/docs-dev - The runtime image is also tagged with its major,
1and1-dev, rebuilt every week
Changed
- The skeleton uses the
runtime:1image tag, andmake upgradewrites the major for image tags
Upgrading
- Run
make upgrade VERSION=1.0. Protocols are unchanged, so a 0.11 daemon,stewartCLI and Home Assistant
integration work with 1.0. - Change the image tag in
Dockerfileand the compose files fromruntime:1.0toruntime:1(1-devin
compose.dev.yaml), so the image keeps getting PHP and Alpine fixes after 1.1. Copybin/upgrade-stewart.shfrom
the skeleton so later upgrades do the same.
v0.11.1
Publishes the runtime image, Helm chart and GitHub release that 0.11.0 did not.
Fixed
- The release scans each runtime image for its own platform, so the arm64 build no longer fails it
v0.11.0
Handlers can wait for states and events, apps can define their own Home Assistant actions, and AppHarness makes
apps easy to test. Workers can be restarted on demand, Stewart can run from environment variables alone, and many
reliability issues are fixed.
Added
- Awaitable waits with timeouts:
waitForState(),EventStream::waitForNext()andTimers::waitFor() - App-defined Home Assistant actions with
EntityExposure::exposeAction() AppHarnessandManualSchedulerinstewart-php/testingfor testing apps on virtual timeHaContext::wasCausedByStewart()to tell Stewart's own changes apartstewart worker:restartandPOST /api/workers/{id}/restart- Running without a
stewart.yaml, configured from the environment alone - App options read from a file through
STEWART_APPS__<APP>__OPTIONS__<NAME>_FILE StateChange::withFrom()
Changed
whenChangedTo()takesfrom:andfromAnyState:arguments instead of chained callsstewart-php/contractsholds only the app-facing API; internal helpers moved tostewart-php/supportstewart-php/testingis public and covered by the backward-compatibility check- Exceptions are created with
createForReason(), and collections withfromList(); most shortcut factories are gone - A full subscription queue drops superseded changes first, so each entity's latest change still arrives
- History queries share the service-call in-flight limit
offor an empty value disablespersistence.url,mqtt.urland thelistensettings- App IDs in
stewart.yamlmay use_in place of- - Control and IPC protocol 29, component protocol 2 and generated code format 6
Fixed
- Large resyncs and bursts of state changes are no longer partly lost
- Exposed entities come back right after the
stewartintegration reloads - Commands to a paused or stopped app are refused at once instead of timing out
- Environment overrides work for hyphenated app IDs and camelCase options
- One-shot timers that come due while an app is paused run when it resumes
- Startup stops when no worker can start, and readiness reports when no worker is running
- Handlers that keep failing from timers log far less
- MQTT no longer loops on invalid topics, unacknowledged messages or rejected connections
- Release pipeline: package tags, CI gate and image scanning
Removed
StateTransitionStream,->from()and->fromAnyState()- Injectable
StewartIdentity runAtSunrise()andrunAtSunset()
Upgrading
- Run
make upgrade VERSION=0.11, thenmake generate. Upgrade the daemon, thestewartCLI and the Home Assistant
integration together; older versions refuse to talk to 0.11. - Replace
->whenChangedTo('on')->from('off')with->whenChangedTo('on', from: 'off'), and->fromAnyState()
withfromAnyState: true. - Replace
runAtSunrise()andrunAtSunset()withrunAtSunEvent(SunEvent::Sunrise, …)orSunEvent::Sunset. - Replace an injected
StewartIdentitywith$ha->wasCausedByStewart($change). - Replace removed exception factories with
createForReason(),fromWire()withrestoreFromWire(), and removed
collection factories such asfromIds()withfromList(). - Import classes that moved out of
stewart-php/contractsfromStewart\Support\…. - Custom implementations of
Timers,EventStream,HaContextorEntityExposureadd the new methods. - An app option variable ending in
_FILEis now read as a file; set the option instewart.yamlto keep passing
the path itself.
v0.10.0
A maintenance release: quieter worker shutdown, tighter package manifests and a hardened skeleton CI.
Fixed
- Workers stopping during daemon shutdown no longer log errors: a worker in the new
Stoppingstate drops sends after
its shutdown request, and a disconnecting worker counts as closed - Generated field summaries keep a label or description of
"0"
Changed
stewart-php/contractsandstewart-php/runtimerequireext-filter;stewart-php/clientand
stewart-php/store-redisno longer requirerevolt/event-loopdirectlyRecordingLoggerandRecordedLoginstewart-php/testingaccept any log context array, as PSR-3 does- The skeleton's workflows check out without persisted credentials and grant
packages: writeonly to the image jobs;
itsDockerfilesetsWORKDIR /appin the build stage
Upgrading
- Run
make upgrade VERSION=0.10. Control and IPC protocols are unchanged, but broker, workers andstewart
commands should all run 0.10. - Update the
stewartintegration to 0.10.0 in Home Assistant. - Optionally take over the skeleton's
.github/workflows/*.ymlandDockerfilechanges.
v0.9.0
Apps now show up in Home Assistant as entities of their own. The new stewart integration, installed through HACS,
lets an app expose sensors, switches, buttons, numbers, selects and other controls, receive their commands, and edit
entity registry entries.
Highlights
- The
stewartHome Assistant integration. Installed fromstewart-php/hass-stewartthrough HACS; the daemon
detects it on every connect andstewart statusshows it on acomponentrow. - Exposed entities. An injected
EntityExposuregives appssensor,binary_sensor,switch,button,
number,select,text,time,dateanddatetimeentities.watchCommands()streams typed commands, and
entities turn unavailable while their app or daemon is down. - Registry edits. An injected
RegistryEditorrenames, labels, hides or moves entities in Home Assistant's entity
registry. - Testing.
RecordingEntityExposureandRecordingRegistryEditorinstewart-php/testingcover both in unit
tests.
Added
- The protocol between Stewart and its Home Assistant integration,
integrations/home-assistant/PROTOCOL.md, with
JSON goldens for every message inintegrations/home-assistant/tests/protocol/ - The
stewartHome Assistant integration, installed through HACS fromstewart-php/hass-stewartor by unpacking
stewart.zipfrom its releases; Home Assistant 2026.4 or newer. It has a config flow,stewart/versionand
stewart/session/subscribe - The
stewartintegration creates and drivessensorandbinary_sensorentities through
stewart/entity/upsert,stateandremove. Each daemon gets a hub device and each app a device under it, or an
entity names its own device. Entities restore their state and attributes after a Home Assistant restart and become
unavailable while their daemon has no session stewart/entity/reconcileremoves every entity of a daemon that it no longer exposes, except those of the apps it
names inkeep_appsmake ha-check(ruff, mypy, pytest;HA=minfor Home Assistant 2026.4) as part ofmake check, and
make ha-hassfest- The daemon detects the
stewartintegration on every Home Assistant connect and holds its session open;
stewart statusshows it on acomponentrow, with the reason when it is missing, speaks another protocol or was
taken over by another daemon expose.instancesetting (default), naming this daemon in Home Assistant; daemons sharing one Home Assistant need
different names- Shortly after startup, once every worker is ready or quarantined, the daemon removes its entities that no running
app exposed; entities of apps that failed or did not start stay.expose.prune: falseturns it off - Metrics
stewart_component_info{version,protocol}andstewart_component_state{state} stewart statusshows how many entities each app exposes in anexposedcolumn, also exported as
stewart_app_exposed_entities- Apps expose their own
sensorandbinary_sensorentities through an injectedEntityExposure:
exposeSensor()andexposeBinarySensor()return handles that set the value, attributes and availability and
read back the entity ID and the state Home Assistant restored. Exposed entities are sent again after every
reconnect; without a compatible integration,expose*()throwsExposureException Stewart\Testing\Exposure\RecordingEntityExposurefor unit tests of apps that expose entitiesStewart\Testing\Registry\RecordingRegistryEditorrecords registry edits and applies them to anInMemoryRegistry- The skeleton's
HelloAppexposessensor.stewart_hello_changes_seen; without the integration it logs why and keeps
running make test-ha-e2e(HA=min|latest) boots a real Home Assistant with the integration and checks that the skeleton's
sensor appears, updates and turns unavailable when the daemon stops; CI runs it on both versions- The
stewartintegration createsswitchandbuttonentities. Their service calls send acommandevent, with
the call's context, on the session and wait forstewart/command/result; a refusal, a timeout or the session
ending fails the call - Apps expose switches and buttons with
exposeSwitch()andexposeButton().watchCommands()streams
SwitchCommandandButtonPress; a handler that returns accepts the command,CommandException::rejected()
fails it with its message. Commands for paused or stopped apps fail at once, and a worker dying mid-command fails
it without waiting for the timeout expose.command_timeoutsetting (10s, at most300s): how long Home Assistant waits for an app's answer- Metric
stewart_app_exposed_commands_total{outcome}(accepted,rejected,refused,lost) RecordingExposedSwitch::pushCommand()andRecordingExposedButton::pushCommand()run an app's command handlers in
unit tests- The skeleton's
HelloAppexposesswitch.stewart_hello_counting, which pauses its change counter, and
number.stewart_hello_step, how much each change adds, whose icon shows the pause throughupdateConfig(); the
e2e smoke test sets the step and turns counting off and on - The
stewartintegration createsnumber,select,text,time,dateanddatetimeentities; their
set_valueandselect_optionservice calls sendset_valueandselect_optioncommands - Apps expose numbers, selects, texts, times, dates and datetimes with
exposeNumber(),exposeSelect(),
exposeText(),exposeTime(),exposeDate()andexposeDateTime(). Times areTimeOfDay, dates and datetimes
DateTimeImmutable.watchCommands()streams a typed command per platform; an accepted command sets the handle's
value. TheRecordingExposed*handles push them in unit tests - Every exposed handle has
getConfig()andupdateConfig(), which changes the entity's name, icon, options or
limits in place and keeps its device; Home Assistant drops a value that no longer fits to unknown RegisteredEntitycarries the entity'sicon, andaliasesasEntityAliaswhere Home Assistant sends them; the
mirrored registry has none, so it holdsnullthere- Apps edit Home Assistant entity registry entries through an injected
RegistryEditor:updateEntity()takes an
EntityRegistryUpdatethat renames, sets the icon, area, labels and aliases, hides or disables an entity, and
returns the updatedRegisteredEntity. Added and removed labels and aliases merge with the entry Home Assistant
holds. Edits share the service-call budget and followservice_calls.dry_run; failures throw
RegistryEditException. Editing an entity that a Stewart app exposes logs a warning naming the app and key
Changed
- Entities of an app that fails, stops or loses its worker turn unavailable until the app exposes them again, instead
of keeping their last state as if live - The control protocol is version 28: snapshots carry
componentand each app'sexposed_entitiesand
exposed_commands - IPC protocol 26; broker and workers must run the same version
Upgrading
- Run
make upgrade VERSION=0.9. Broker, workers andstewartcommands must all run 0.9;stewart statusagainst
a 0.8 daemon is refused (control protocol 28). - Install the
stewartintegration 0.9.0 in Home Assistant 2026.4 or newer before an app callsexpose*(); without
it,expose*()throwsExposureException. Apps that expose nothing need no integration. - Daemons sharing one Home Assistant set a different
expose.instanceeach.