# ADR-146 — Managed machines pull by outbound beacon; nothing ever reaches in

**Date:** 2026-08-29 · **Status:** Accepted · **Supersedes nothing; extends ADR-142**
**Context owner:** hkl · **Advisor-reviewed:** yes (binding amendments folded in)

## Context

Two machines are provisioned from DL's `laptop-setup` bundle and both drift as DL's harness
improves: **Dev-PC** (Ashok's design station, also "DD1") and **DL2** (`DESKTOP-F0DH68L`, the
"student laptop", second backup DL, used by Yesha for JEE planning). hkl asked for the Provision
PWA to (1) create a full backup, (2) update an existing one, (3) start a backup from the PWA, and
(4) read each machine's system state — and said Syncthing "is not working properly from the VPS
point of view."

**Measured 2026-08-29, before any design:**

| Fact | Value |
|---|---|
| DL2 SSH | **:22 CLOSED** — no sshd. :445 open but no credentials. Tailnet-online, same LAN |
| DL2 Syncthing | device `student-laptop` **connected**, `dl-bundle` **100 % complete, need=0, errors=0** |
| Dev-PC Syncthing | **not a device at all**; 0 syncthing processes |
| `dl-share-designer` | **0 files** |
| `vps-obsidian` device | **never connected** |
| Vault zip vs Syncthing bundle | **Aug 24 vs Aug 18 — two vintages of the same artifact** |

So the premise needed correcting: Syncthing to DL2 **works**. The defects are that **Dev-PC is on
no delivery path**, and that **two uncoordinated paths carry different versions**.

## Decision

**Every managed machine runs an outbound-only beacon. Nothing ever connects *to* a managed
machine.** The beacon POSTs machine state to `provision-proxy` and polls for a desired version;
the Provision PWA reads the fleet and approves applies.

Corollaries, each binding:

1. **The beacon path carries ONLY the scanned bundle.** A machine token **cannot** fetch the ARMED
   secrets capsule — that stays PWA + human PIN + arming window (ADR-142). A negative-control test
   ships with the feature and must return 403/404.
2. **Machines are write-only**: POST own state, GET manifest. `/agent/fleet` is human-session only
   (existing HS256 + `groups` + `PROVISION_ACTORS` double gate).
3. **State reports carry machine facts only.** No user-directory contents — **a minor uses DL2**.
4. **Apply defaults to MANUAL, per machine.** Report always; download+apply only after a human
   approves. **Nothing auto-applies or restarts on Dev-PC.**
5. **One publisher run feeds both the vault and `D:\sync-out\bundle`**, with **version = content
   hash**, not a timestamp.
6. Per-file **sha256 in the manifest, verified by the agent before apply**; stage → verify → swap.

## Why (the reasoning that survives, including the wrong turn)

- **The architecture was chosen by a measurement, not a preference.** The first instinct was
  "have the PWA read DL2's state" — impossible: DL2 accepts no inbound connection. Every design
  that assumed reach-in was dead before it was drawn. ⭐ **Probe the transport before designing on
  it.**
- **The premise the request rested on was wrong in a way that mattered.** Had we "fixed Syncthing
  to DL2", we would have spent the effort on the one link that already worked at 100 %, and still
  left Dev-PC on nothing. ⭐ **Measure the thing the user says is broken before repairing it.**
- **Honesty about direction.** "Start backup from the PWA" cannot push — the VPS cannot reach DL
  either. It sets a flag DL's own beacon collects. Saying so is the design; implying a push would
  be a lie the first slow refresh exposes.
- **The unsigned-header precedent.** `Authorization: Hub <b64>` elsewhere in this stack is
  *unsigned*; ADR-142 already refused to depend on it. This ADR does the same: no new trust in
  that header, and hashes-over-HTTPS + per-machine bearer is named as the honest tier rather than
  dressed up as signing.

## Revisit if

- **DL2 gains an inbound route** (sshd, or a VPN with stable addressing) → a pull model could be
  simplified, though outbound-only would still be the safer default.
- **The beacon survives two full update cycles** → retiring DL2's Syncthing leaves one moving part
  instead of two. Do not do this before that evidence exists.
- **A third-party fleet tool is adopted** (Intune/Tailscale SSH/Ansible) → this becomes redundant;
  keep the manifest format so migration is mechanical.
- **Any requirement appears to auto-apply secrets to a machine** → re-open ADR-142 explicitly.
  Do not fold it into an apply flow silently.
- **Dev-PC gets a second human user** → the "never auto-restart" rule needs re-stating, not
  re-deriving.

## Consequences

- "Is DL2 alive, and what is on it?" becomes one glance at the PWA. **Today that question is
  unanswerable** — three routes were tried (SSH, VPS-side TCP, SMB) and none gave a positive
  answer.
- Two delivery paths collapse to one source of truth; the Aug-24/Aug-18 split cannot recur.
- A machine that stops checking in is **visible as stale**, not silently absent (degrade-loudly).
- New surface on the VPS: four endpoints, one state directory, one token per machine. Each is
  audited.
