Skip to content

Earthshaker

Purpose

Earthshaker is AncientOS's governed sensorimotor subsystem: the boundary through which AncientOS observes physical state and produces physical action.

It is two-way.

  • Afferent: identify physical entities, observe device state, expose capabilities, and observe outcomes of commanded actions.
  • Efferent: translate authorized AncientOS physical intent into device operations using the appropriate adapter.

Earthshaker does not independently decide what should happen. It is not a reasoning or governance engine. Luna owns operator intent. Rubick owns capability availability. Lich owns approval. Zeus owns evidence.

Underlying Plex Companion, Apple TV Companion, Home Assistant, Matter, MQTT, and vendor APIs are adapters beneath Earthshaker.

Boundary

Earthshaker receives work only after a transport has already submitted a canonical Luna message through RuntimeKernel.process.

A physical concept such as "Living Room TV" is represented as an Earthshaker physical entity. Luna must not need Apple TV IP addresses, Companion credentials, or Plex client HTTP details.

Earthshaker must not become a Home Assistant transport and must not accept arbitrary Home Assistant service calls from user text.

Physical playback slice

The first non-HA actuation lane plays an already-available Plex title on the pinned Living Room Apple TV.

Luna intent
  -> Chen/media availability (`already_present` + library reference)
  -> Earthshaker `play_available_title(place, media_reference)`
  -> unique Plex client bind
  -> bounded Companion wake when asleep
  -> optional Companion launch of `com.plexapp.plex`
  -> `playMedia` to the Apple TV Plex client
  -> bounded `/status/sessions` observation
  -> Luna

HTTP 200 from playMedia is not playback. Outcomes are:

  • command rejected/failed (blocked)
  • command dispatched but playback unverified (dispatched_unverified)
  • desired playback observed (observed)

Unknown rooms, zero matching clients, and 2+ matching clients fail closed. Companion launch readiness requires an awake device when Companion reports the paired Apple TV asleep; a launch call returning without error is not readiness or playback evidence. Observation does not grant execution authority. Generalized discovery, arbitrary-place onboarding, and recursive "figure it out" are out of scope for this slice. See docs/architecture/media_playback.md.

Bounded physical binding

The second playback place, Basement, exercises Earthshaker's first reusable physical-binding loop without creating generic device authority:

observe -> unique bind -> ready protocol -> authorize -> act
        -> verify world -> pin
  • Observe: bounded read-only Companion/AirPlay discovery considers only Companion-capable Apple TVs on the configured /24. Plex /clients remains application readiness evidence. The separately observed TCL Roku display is not assumed to be the playback effector.
  • Unique bind: zero candidates fail closed; two or more eligible candidates are ambiguous. Named devices belonging to other places and the stable Living Room identifiers are excluded. A generic candidate still requires explicit human Companion pairing before first actuation.
  • Ready protocol: the selected device must re-advertise the stable Apple identifiers, the off-git pairing must match those identifiers, Plex must become uniquely reachable, and the existing client/session checks apply.
  • Authorize: play_library_title_on_named_room is scoped exactly to living_room and basement. Discovery and pinning do not alter authority.
  • Act and verify: the existing typed Plex launch and playMedia path is used. Only an independently matching Plex session yields observed.
  • Pin: after observed playback, Earthshaker atomically writes non-secret stable Apple/Plex identifiers and address hints to /data/earthshaker/physical_bindings.json. Companion credentials remain outside git under ~/.config/luna-appletv/.

Pinned requests probe the last-known address first but match only stable Apple identifiers. If DHCP changes, bounded rediscovery may recover the same stable device. An absent identity, duplicate stable match, reused address, unavailable protocol, or invalid pairing fails closed; Earthshaker never redirects a pin to a different identity at the old IP.

Resumable Companion pairing

An unpaired unique Basement candidate creates a durable, transport-neutral pairing challenge instead of ending the operator flow. The challenge binds the canonical operator principal when available; otherwise it binds the exact transport, author, and channel context. It records the place, stable candidate identity, Companion protocol, expiry, expected four-digit response, and the immutable Chen-resolved playback continuation. It never records the PIN or credentials.

The live pyatv handler is process-local and bounded by challenge expiry. Luna intercepts four digits only when exactly one unexpired challenge belongs to that operator/context. Zero matches leave numeric input to ordinary routing, and multiple matches fail closed. A wrong code, missing TV code, expired live session, or restart requires an explicit retry; try again starts a fresh protocol session and generates a new TV code. cancel terminates pairing and the suspended playback. Conversation and the authenticated Web pairing page submit to the same canonical challenge; the Web path is not a bearer token and grants no authority. Recoverable pairing states remain human_interaction_required. Completed or cancelled prompts do not. See Human Interaction Challenges.

After successful pairing, credentials are written atomically with mode 0600 to the exact configured off-git Apple TV credential directory. Luna then revalidates exact Basement authority and current physical identity/readiness before resuming the stored play_available_title operation. Pairing success is not playback success: the existing blocked, dispatched_unverified, and observed semantics remain authoritative, and the stable binding is pinned only through the validated playback path.

“Figure it out” means discovering how to execute an already-understood and authorized physical intent. It does not mean recursively attempting arbitrary actions.

The optional live Home Assistant provider is environment-gated:

export HOME_ASSISTANT_URL="http://homeassistant.local:8123"
export HOME_ASSISTANT_TOKEN="replace-with-long-lived-access-token"

If either value is missing or Home Assistant is unavailable, discovery returns explicit unavailable or degraded evidence and execution remains blocked.

Initial Home Assistant Slice

The first governed Home Assistant slice supports lights only:

  • light.turn_on
  • light.turn_off
  • light.toggle

Natural-language target resolution is exact and fail-closed. For example, turn on the patio light may resolve to light.patio only when exactly one available light entity matches the normalized alias.

Earthshaker refuses live Home Assistant execution when:

  • the target is ambiguous
  • the entity is unavailable
  • the service is not allowlisted
  • no Home Assistant provider is configured
  • Lich approval is required
  • Home Assistant discovery is unavailable or degraded enough to prevent exact resolution

Governance

Default behavior is fail-closed.

Dry-run is enabled by default through EARTHSHAKER_HOME_ASSISTANT_DRY_RUN=true behavior. Runtime routing remains fail-closed when dry-run is disabled until a separate Lich-approved execution lane exists.

EARTHSHAKER_SAFE_HOME_ACTIONS_AUTO_APPROVE=true currently permits only a dry-run provider preview:

export EARTHSHAKER_SAFE_HOME_ACTIONS_AUTO_APPROVE=true

Named-room library playback uses the existing auto-execute capability play_library_title_on_named_room, limited exactly to living_room and the separately established basement binding. That lane does not broaden Home Assistant authority or make discovered devices executable.

Rubick owns capability availability evidence. Lich owns approval authority. Zeus owns validation/evidence boundaries. Earthshaker does not silently elevate or bypass those owners.

The live Home Assistant provider records execution evidence fields such as domain, service, entity id, dry-run state, REST endpoint, provider status, and a stable evidence id. Physical playback records command_dispatched versus playback_observed on the same Zeus-compatible evidence path.

TODO: Wire Rubick capability evidence into Earthshaker Home Assistant availability reporting before claiming Rubick is the active Home Assistant capability authority. Earthshaker currently runs before the explicit Rubick command router in the runtime pipeline, so this remains a known boundary for later cleanup.

Voice Satellite Visibility

Earthshaker also provides read-only visibility for Home Assistant voice satellites. This is observability, not transport logic and not execution.

For Home Assistant Voice PE, Earthshaker reads Home Assistant state and reports:

  • satellite entity id
  • friendly name
  • wake word selection entities
  • current wake word
  • supported wake words
  • mute state when available
  • media player entity when available

Home Assistant remains responsible for wake word detection, audio, STT, and TTS. Luna remains the reasoning layer behind the Home Assistant Voice transport. Custom Luna or Hey Luna wake words are Home Assistant wake word artifacts, not Luna runtime behavior.

If Home Assistant does not expose a wake word selector for a satellite, Earthshaker reports degraded metadata and fails closed. It must not infer a configured wake word as active without state evidence, and it must not mutate Home Assistant wake word configuration until an explicit governed mutation lane exists.