Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ The first milestone is a complete, machine-checkable scaffold for the planned Mu
`docs/index.md` and `docs/pattern-graph.md` are generated from manifests. Do not hand-edit them.
`docs/completion.md` is also generated and reports maturity from manifest status fields.

`T2R4.device-triggered-conveyor` is the first production-beta rare pattern for device-event ingest appliances. `C6.lifecycle-capsule` and `T2R5.signed-update-rail` capture the core install, doctor, update, rollback, and clean-uninstall lifecycle.
`T2R4.device-triggered-conveyor` is the first production-beta rare pattern for device-event ingest appliances. `C6.lifecycle-capsule` and `T2R5.signed-update-rail` capture the core install, doctor, update, rollback, and clean-uninstall lifecycle. `T2R6.home-assistant-mqtt-bridge` adds a mockable Home Assistant MQTT discovery, state, and control bridge.
9 changes: 8 additions & 1 deletion docs/completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Overall maturity: **75.5%**
| Tech 1 Rare | 5 | 100.0% |
| Tech 1 Mythic | 5 | 100.0% |
| Tech 2 Common | 5 | 68.0% |
| Tech 2 Rare | 5 | 55.1% |
| Tech 2 Rare | 6 | 58.7% |
| Tech 3 Common | 1 | 76.7% |
| Tech 3 Rare | 1 | 33.0% |
| Tech 3 Mythic | 5 | 33.0% |
Expand Down Expand Up @@ -46,6 +46,12 @@ Overall maturity: **75.5%**
| `C6.lifecycle-capsule` | Lifecycle Capsule | stable/stable/stable | 100.0% |
| `T2R5.signed-update-rail` | Signed Update Rail | usable/reviewed/reviewed | 76.7% |

## Production-Beta Home Assistant Chain

| ID | Pattern | Status | Completion |
| --- | --- | --- | ---: |
| `T2R6.home-assistant-mqtt-bridge` | Home Assistant MQTT Bridge | usable/reviewed/reviewed | 76.7% |

## Stable Device Conveyor Chain

| ID | Pattern | Status | Completion |
Expand Down Expand Up @@ -84,6 +90,7 @@ Overall maturity: **75.5%**
| `T2R3.edge-control-plane` | Edge Control Plane | draft/draft/draft | 33.0% |
| `T2R4.device-triggered-conveyor` | Device-Triggered Conveyor | stable/stable/stable | 100.0% |
| `T2R5.signed-update-rail` | Signed Update Rail | usable/reviewed/reviewed | 76.7% |
| `T2R6.home-assistant-mqtt-bridge` | Home Assistant MQTT Bridge | usable/reviewed/reviewed | 76.7% |
| `T3C1.edge-appliance-bundle` | Edge Appliance Bundle | usable/reviewed/reviewed | 76.7% |
| `T3M1.machine-priest` | Machine Priest | draft/draft/draft | 33.0% |
| `T3M2.ritualized-recovery-loop` | Ritualized Recovery Loop | draft/draft/draft | 33.0% |
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Generated from `patterns/**/manifest.yaml`.
| [`T2R3.edge-control-plane`](../patterns/t2/rare/T2R3.edge-control-plane/) | Edge Control Plane | 5 | T2C2.dropfolder-spooler, T2C3.scheduled-herald, T2C5.local-sidecar-bridge, R1.socket-anteroom, R2.device-binding, R4.state-ledger | draft/draft/draft |
| [`T2R4.device-triggered-conveyor`](../patterns/t2/rare/T2R4.device-triggered-conveyor/) | Device-Triggered Conveyor | 5 | C1.service-capsule, C2.persistent-tick, C4.lazy-resource-gate, C5.failure-ratchet, C6.lifecycle-capsule, R2.device-binding, R5.capability-mount, T2C1.hot-cold-nas-conveyor, T2C3.scheduled-herald | stable/stable/stable |
| [`T2R5.signed-update-rail`](../patterns/t2/rare/T2R5.signed-update-rail/) | Signed Update Rail | 5 | C2.persistent-tick, C5.failure-ratchet, C6.lifecycle-capsule, R4.state-ledger | usable/reviewed/reviewed |
| [`T2R6.home-assistant-mqtt-bridge`](../patterns/t2/rare/T2R6.home-assistant-mqtt-bridge/) | Home Assistant MQTT Bridge | 5 | C1.service-capsule, C2.persistent-tick, C5.failure-ratchet, C6.lifecycle-capsule, R4.state-ledger, T2C5.local-sidecar-bridge | usable/reviewed/reviewed |

## Tech 3

Expand Down
7 changes: 7 additions & 0 deletions docs/pattern-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ graph TD
T2R3["T2R3.edge-control-plane<br/>Edge Control Plane"]
T2R4["T2R4.device-triggered-conveyor<br/>Device-Triggered Conveyor"]
T2R5["T2R5.signed-update-rail<br/>Signed Update Rail"]
T2R6["T2R6.home-assistant-mqtt-bridge<br/>Home Assistant MQTT Bridge"]
T3C1["T3C1.edge-appliance-bundle<br/>Edge Appliance Bundle"]
T3M1["T3M1.machine-priest<br/>Machine Priest"]
T3M2["T3M2.ritualized-recovery-loop<br/>Ritualized Recovery Loop"]
Expand Down Expand Up @@ -80,6 +81,12 @@ graph TD
C5 --> T2R5
C6 --> T2R5
R4 --> T2R5
C1 --> T2R6
C2 --> T2R6
C5 --> T2R6
C6 --> T2R6
R4 --> T2R6
T2C5 --> T2R6
T2C1 --> T3C1
T2C3 --> T3C1
T2C4 --> T3C1
Expand Down
6 changes: 6 additions & 0 deletions docs/production-beta-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ Network storage must not block boot. Mounts use lazy materialization, bounded ti
A physical device event must hand off to systemd through udev `SYSTEMD_WANTS`; udev must not run the ingest itself.

Before ingest starts, the job must prove the destination capability and confirm enough hot-storage capacity. If capacity is temporarily unavailable because the hot/cold conveyor is still flushing, the job records `waiting_for_capacity`, runs a drain command or waits for the drain timer, and continues when capacity becomes available. If the timeout expires, it exits as a temporary failure and leaves inspectable state.

## Home Assistant MQTT Contract

Home Assistant MQTT bridge patterns must be broker-optional during validation. Discovery, state, and control traffic are first represented as local outbox and inbox files so a doctor or unit test can prove topic names, payloads, and command bounds without network credentials.

Control payloads must be narrow and explicitly accepted. The beta bridge accepts only tokenized entity names and `ON` or `OFF` commands until a deployment adds reviewed broker credentials, TLS, and command authorization.
16 changes: 13 additions & 3 deletions patterns/t1/common/C1.service-capsule/scripts/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,27 @@ test -f "$pattern_dir/README.md"
test -f "$pattern_dir/units/example.service"
test -x "$pattern_dir/scripts/service-capsule-run.sh"

mock_root=${MUSTER_MOCK_ROOT:-${TMPDIR:-/tmp}/muster-c1-doctor}
mkdir -p "$mock_root/etc/muster" "$mock_root/run/muster" "$mock_root/var/lib/muster/service-capsule"

if command -v systemd-analyze >/dev/null 2>&1; then
systemd-analyze verify "$pattern_dir/units/example.service"
verify_unit="$mock_root/example.service"
awk \
-v readme="$pattern_dir/README.md" \
-v runner="$pattern_dir/scripts/service-capsule-run.sh" \
'
/^Documentation=/ { print "Documentation=file:" readme; next }
/^ExecStart=/ { print "ExecStart=" runner " --apply"; next }
{ print }
' "$pattern_dir/units/example.service" > "$verify_unit"
systemd-analyze verify "$verify_unit"
elif [ "$strict" -eq 1 ]; then
printf '%s\n' "systemd-analyze is required in --strict mode" >&2
exit 1
else
printf '%s\n' "warn: systemd-analyze not found; skipped unit verification"
fi

mock_root=${MUSTER_MOCK_ROOT:-${TMPDIR:-/tmp}/muster-c1-doctor}
mkdir -p "$mock_root/etc/muster" "$mock_root/run/muster" "$mock_root/var/lib/muster/service-capsule"
MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/service-capsule-run.sh" >/dev/null
test -d "$mock_root/run/muster"
test -s "$mock_root/run/muster/service-capsule.json"
Expand Down
23 changes: 20 additions & 3 deletions patterns/t1/common/C2.persistent-tick/scripts/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,34 @@ test -f "$pattern_dir/units/example.service"
test -f "$pattern_dir/units/example.timer"
test -x "$pattern_dir/scripts/persistent-tick-run.sh"

mock_root=${MUSTER_MOCK_ROOT:-${TMPDIR:-/tmp}/muster-c2-doctor}
mkdir -p "$mock_root/run/muster" "$mock_root/var/lib/muster/persistent-tick"

if command -v systemd-analyze >/dev/null 2>&1; then
systemd-analyze verify "$pattern_dir/units/example.service" "$pattern_dir/units/example.timer"
verify_service="$mock_root/example.service"
verify_timer="$mock_root/example.timer"
awk \
-v readme="$pattern_dir/README.md" \
-v runner="$pattern_dir/scripts/persistent-tick-run.sh" \
'
/^Documentation=/ { print "Documentation=file:" readme; next }
/^ExecStart=/ { print "ExecStart=" runner " --apply"; next }
{ print }
' "$pattern_dir/units/example.service" > "$verify_service"
awk \
-v readme="$pattern_dir/README.md" \
'
/^Documentation=/ { print "Documentation=file:" readme; next }
{ print }
' "$pattern_dir/units/example.timer" > "$verify_timer"
systemd-analyze verify "$verify_service" "$verify_timer"
elif [ "$strict" -eq 1 ]; then
printf '%s\n' "systemd-analyze is required in --strict mode" >&2
exit 1
else
printf '%s\n' "warn: systemd-analyze not found; skipped unit verification"
fi

mock_root=${MUSTER_MOCK_ROOT:-${TMPDIR:-/tmp}/muster-c2-doctor}
mkdir -p "$mock_root/run/muster" "$mock_root/var/lib/muster/persistent-tick"
MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/persistent-tick-run.sh" >/dev/null
test -d "$mock_root/var/lib/muster/persistent-tick"
test -s "$mock_root/run/muster/persistent-tick.json"
Expand Down
67 changes: 67 additions & 0 deletions patterns/t2/rare/T2R6.home-assistant-mqtt-bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Home Assistant MQTT Bridge

## Intent

Publish Home Assistant MQTT discovery payloads, bridge local appliance state into MQTT-style topics, and consume bounded Home Assistant control commands without requiring a broker during validation.

## Production beta contract

Target platform is Debian/Raspberry Pi OS with systemd timers. The beta artifact is deliberately mockable: discovery, state, and command traffic are represented as files under `mqtt-outbox/` and `mqtt-control/` until a deployment supplies reviewed broker plumbing. Discovery uses a single Home Assistant MQTT device payload with status sensors, a restart button, and an enabled switch. The bridge only accepts tokenized entity names plus allowlisted `enabled` and `restart` control payloads by default.

## When to use this

Use this when an appliance should appear in Home Assistant through MQTT discovery and expose a narrow control surface that can be proven locally before connecting to a real broker.

## When not to use this

Do not use this when the appliance needs arbitrary Home Assistant entities, templated payloads, retained MQTT credentials in the repository, or unaudited command topics.

## System shape

`muster-ha-mqtt-bridge.timer` periodically starts `muster-ha-mqtt-bridge.service`. The service runs `scripts/ha-mqtt-bridge.sh --once --apply`, which emits one Home Assistant device discovery payload, publishes the current enabled state, and processes any pending command file from the control inbox.

## Subpatterns

- `C1.service-capsule`
- `C2.persistent-tick`
- `C5.failure-ratchet`
- `C6.lifecycle-capsule`
- `R4.state-ledger`
- `T2C5.local-sidecar-bridge`

## Files

- `manifest.yaml` declares the pattern contract.
- `units/muster-ha-mqtt-bridge.*` show the periodic bridge service.
- `scripts/ha-mqtt-bridge.sh` implements mockable discovery, state publish, and control handling.
- `scripts/rollback.sh` restores the previous published state.
- `scripts/uninstall.sh` removes installed artifacts while preserving state by default.
- `scripts/install.sh` and `scripts/doctor.sh` provide dry-run install and mock verification.

## Installation

Run `scripts/install.sh` without arguments first. It prints the service, timer, helper, and config copy plan. Use `MUSTER_ROOT=/tmp/root scripts/install.sh --apply` for staged verification, or `scripts/install.sh --apply` as root after configuring `/etc/muster/home-assistant-mqtt-bridge.env`.

## Verification

Run `scripts/doctor.sh`. It writes a mocked Home Assistant device discovery payload, publishes enabled state, processes `OFF` and restart control commands, and proves rollback restores the previous state.

## Failure modes

Malformed entity names, unsupported control payloads, missing previous state, missing installed files, and invalid unit files fail closed. The mock outbox preserves topic-to-file mappings in `topics.log` for operator inspection.

## Rollback

Run `scripts/rollback.sh --apply` with `MUSTER_ROOT` for a staged root or as root on the target host. Rollback restores the previous `state.json` snapshot and queues a replacement MQTT state payload.

## Security notes

Broker credentials are intentionally outside the repository. Keep command topics narrow, map each command to an explicit local action, and do not enable real broker publishing until the deployment has credential storage, TLS, and command authorization reviewed.

## Telemetry budget

Telemetry collectors must be bounded, cheap, and stale-aware. Do not recursively scan large remote mounts, slow NAS trees, or unbounded directories during a periodic bridge tick. Publish counts, sampled entries, filesystem accounting, or precomputed state-ledger facts instead, and make partial snapshots prefer the currently active appliance lifecycle over older retained handoff state.

## Future work

Add a broker adapter with TLS-only defaults, support more entity classes, and emit state-ledger events for every discovery, state, control, and rollback action.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Minimal T2R6 Home Assistant MQTT Bridge

This sketch shows the smallest local artifact set for `T2R6.home-assistant-mqtt-bridge`.

Start in mock mode:

```sh
patterns/t2/rare/T2R6.home-assistant-mqtt-bridge/scripts/ha-mqtt-bridge.sh --once
```

The first run writes Home Assistant discovery and state payloads under a mock `mqtt-outbox/` directory. A Home Assistant command can be tested by writing `ON` or `OFF` to `mqtt-control/relay.cmd`, then running `scripts/ha-mqtt-bridge.sh --control`.

Use `MUSTER_ROOT=/tmp/root scripts/install.sh --apply` for staged-root verification before copying the reviewed service and timer onto a host.
58 changes: 58 additions & 0 deletions patterns/t2/rare/T2R6.home-assistant-mqtt-bridge/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
id: T2R6.home-assistant-mqtt-bridge
name: Home Assistant MQTT Bridge
tech_level: 2
rarity: rare
mrl: 5
summary: >
Publish Home Assistant MQTT discovery payloads, bridge appliance state into MQTT topics, and accept bounded control commands through a mockable local inbox before a real broker is enabled.
provides:
- home_assistant_mqtt_autodiscovery
- appliance_mqtt_telemetry
- scoped_mqtt_controls
requires:
- C1.service-capsule
- C2.persistent-tick
- C5.failure-ratchet
- C6.lifecycle-capsule
- R4.state-ledger
- T2C5.local-sidecar-bridge
subpatterns:
- C1.service-capsule
- C2.persistent-tick
- C5.failure-ratchet
- C6.lifecycle-capsule
- R4.state-ledger
- T2C5.local-sidecar-bridge
composes_with:
- T2R4.device-triggered-conveyor
- T3R1.multi-resource-orchestrator
lifecycle:
managed: true
install_modes:
- dry_run
- staged_root
- apply
doctor_modes:
- mock
- staged_root
rollback: artifact_generation
uninstall: artifacts_only
update: none
artifacts:
units:
- units/muster-ha-mqtt-bridge.service
- units/muster-ha-mqtt-bridge.timer
scripts:
- scripts/ha-mqtt-bridge.sh
- scripts/install.sh
- scripts/doctor.sh
- scripts/rollback.sh
- scripts/uninstall.sh
tests:
- tests/test_manifest.py
examples:
- examples/minimal/README.md
status:
implementation: usable
docs: reviewed
tests: reviewed
46 changes: 46 additions & 0 deletions patterns/t2/rare/T2R6.home-assistant-mqtt-bridge/scripts/doctor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env sh
set -eu

pattern_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
test -f "$pattern_dir/manifest.yaml"
test -f "$pattern_dir/README.md"
test -f "$pattern_dir/units/muster-ha-mqtt-bridge.service"
test -f "$pattern_dir/units/muster-ha-mqtt-bridge.timer"
test -x "$pattern_dir/scripts/ha-mqtt-bridge.sh"
test -x "$pattern_dir/scripts/install.sh"
test -x "$pattern_dir/scripts/rollback.sh"
test -x "$pattern_dir/scripts/uninstall.sh"

if command -v systemd-analyze >/dev/null 2>&1; then
systemd-analyze verify "$pattern_dir/units/muster-ha-mqtt-bridge.service" "$pattern_dir/units/muster-ha-mqtt-bridge.timer"
fi

mock_root=$(mktemp -d "${TMPDIR:-/tmp}/muster-t2r6-doctor.XXXXXX")
cleanup() {
rm -rf "$mock_root"
}
trap cleanup EXIT INT TERM

MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/ha-mqtt-bridge.sh" --discover >/dev/null
test -s "$mock_root/run/muster/home-assistant-mqtt-bridge/mqtt-outbox/homeassistant_device_muster_bridge_config.json"
grep '"restart_service"' "$mock_root/run/muster/home-assistant-mqtt-bridge/mqtt-outbox/homeassistant_device_muster_bridge_config.json" >/dev/null
grep '"enabled"' "$mock_root/run/muster/home-assistant-mqtt-bridge/mqtt-outbox/homeassistant_device_muster_bridge_config.json" >/dev/null

MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/ha-mqtt-bridge.sh" --state enabled ON >/dev/null
grep '"state":"ON"' "$mock_root/run/muster/home-assistant-mqtt-bridge/state.json" >/dev/null

printf '%s\n' "OFF" > "$mock_root/run/muster/home-assistant-mqtt-bridge/mqtt-control/enabled.cmd"
MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/ha-mqtt-bridge.sh" --control >/dev/null
grep '"state":"OFF"' "$mock_root/run/muster/home-assistant-mqtt-bridge/state.json" >/dev/null
test -f "$mock_root/run/muster/home-assistant-mqtt-bridge/mqtt-control/enabled.cmd.processed"

printf '%s\n' "PRESS" > "$mock_root/run/muster/home-assistant-mqtt-bridge/mqtt-control/restart.cmd"
MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/ha-mqtt-bridge.sh" --control >/dev/null
grep '"control":"restart"' "$mock_root/run/muster/home-assistant-mqtt-bridge/control-result.json" >/dev/null

MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/ha-mqtt-bridge.sh" --state enabled ON >/dev/null
MUSTER_MOCK_ROOT="$mock_root" "$pattern_dir/scripts/ha-mqtt-bridge.sh" --state enabled OFF >/dev/null
MUSTER_ROOT="$mock_root" "$pattern_dir/scripts/rollback.sh" --apply >/dev/null
grep '"state":"ON"' "$mock_root/run/muster/home-assistant-mqtt-bridge/state.json" >/dev/null

printf '%s\n' "ok: T2R6.home-assistant-mqtt-bridge"
Loading
Loading