novox/hq issue 289. While a controller update was rolling out, the witness moved the running controller's build into .witness/mesh-controller/previous/. That directory is 0700 and owned by root, and the old controller was still serving and running its own binary as each verb. Every verb failed with permission denied. On proof the witness also deleted previous/ without checking whether anything still ran from it.
The order stays the same: move the running build aside, unpack the new one, restart (systemd stops the old process, then starts the new), prove, retire. What changes is that each step is now safe for a process still running from the old build (internal/witness/draining.go):
The directories the witness keeps for a process are 0711. The process's own user can enter them; only root can list them. Existing 0700 directories are chmod'ed on the next placement or save.
A build that is no longer needed is retired, not deleted. That covers the previous build once proved, a build on trial replaced by a newer one, and the failed build after a restore. It is renamed under .witness/<name>/retired/ and deleted only when no process runs from it. RunningFrom reads every /proc/<pid>/exe, including ones that show (deleted). That finds the PID the old build ran as and every command it started.
Watcher.Look sweeps retired builds on every look. The "healthy" line says whether the old build was deleted or is waiting for named PIDs.
Also included: internal/bootstrap/publish_test.go is restructured so that the toolchain's gofmt (Go 1.26) and a newer gofmt lay it out the same way. The build seat's check failed on that file as it was on main.
Tests (real processes, Linux):
TestABuildIsKeptReachableAndUndeletedWhileAProcessRunsFromIt: the directories are 0711, the running PID is found in previous/, and after proof the build is still kept until the process stops, then removed at the next sweep.
mesh-controller check-here: merge-gate PASS (4 of 4 compose), repo-check PASS.
novox/hq issue 289. While a controller update was rolling out, the witness moved the running controller's build into `.witness/mesh-controller/previous/`. That directory is 0700 and owned by root, and the old controller was still serving and running its own binary as each verb. Every verb failed with permission denied. On proof the witness also deleted `previous/` without checking whether anything still ran from it.
The order stays the same: move the running build aside, unpack the new one, restart (systemd stops the old process, then starts the new), prove, retire. What changes is that each step is now safe for a process still running from the old build (`internal/witness/draining.go`):
- The directories the witness keeps for a process are **0711**. The process's own user can enter them; only root can list them. Existing 0700 directories are chmod'ed on the next placement or save.
- A build that is no longer needed is **retired, not deleted**. That covers the previous build once proved, a build on trial replaced by a newer one, and the failed build after a restore. It is renamed under `.witness/<name>/retired/` and deleted only when **no process runs from it**. `RunningFrom` reads every `/proc/<pid>/exe`, including ones that show `(deleted)`. That finds the PID the old build ran as and every command it started.
- `Watcher.Look` sweeps retired builds on every look. The "healthy" line says whether the old build was deleted or is waiting for named PIDs.
Also included: `internal/bootstrap/publish_test.go` is restructured so that the toolchain's gofmt (Go 1.26) and a newer gofmt lay it out the same way. The build seat's check failed on that file as it was on main.
Tests (real processes, Linux):
- `TestABuildIsKeptReachableAndUndeletedWhileAProcessRunsFromIt`: the directories are 0711, the running PID is found in `previous/`, and after proof the build is still kept until the process stops, then removed at the next sweep.
- `TestABuildOnTrialReplacedIsRetiredNotDeletedUnderItsProcess`
- `TestAProcessRunsFromABuildDeletedUnderIt`
`mesh-controller check-here`: merge-gate PASS (4 of 4 compose), repo-check PASS.
Deliverynovox/mesh-host@3a117c2d2bdd — delivered since 2026-10-07T01:05:07Z
Delivery plan — builds mesh-host → ace, g14, novox, shanks; no bus step
tier 0: mesh-host
ace: receives mesh-host
g14: receives mesh-host
novox: receives mesh-host
shanks: receives mesh-host
Machines
mesh-host on ace: passed — healthy 3 times over 8m59s
mesh-host on the rest running it: sent
Transitions
2026-10-07 00:45 (new) → proposed (announced): novox/mesh-host#48's head announced
2026-10-07 00:48 proposed → checked (checked): the verdict names this commit
2026-10-07 00:48 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-07 00:55 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-07 00:55 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path
2026-10-07 01:05 delivering → delivered (done): its walk is done: every machine of its deploy plan passed, or was left as its policy says
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-host@3a117c2d2bdd` — **delivered** since 2026-10-07T01:05:07Z
**Delivery plan** — builds mesh-host → ace, g14, novox, shanks; no bus step
- tier 0: mesh-host
- ace: receives mesh-host
- g14: receives mesh-host
- novox: receives mesh-host
- shanks: receives mesh-host
**Machines**
- mesh-host on ace: passed — healthy 3 times over 8m59s
- mesh-host on the rest running it: sent
**Transitions**
- 2026-10-07 00:45 (new) → proposed (announced): novox/mesh-host#48's head announced
- 2026-10-07 00:48 proposed → checked (checked): the verdict names this commit
- 2026-10-07 00:48 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-07 00:55 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-07 00:55 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path
- 2026-10-07 01:05 delivering → delivered (done): its walk is done: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host)
mesh/repo-check: PASS — its merge-check.sh passed
Change plan — builds mesh-host → ace, g14, novox, shanks; no bus step
tier 0: mesh-host
ace: receives mesh-host
g14: receives mesh-host
novox: receives mesh-host
shanks: receives mesh-host
Run by the build seat on g14 as build-1791332936840909516 (builds --log build-1791332936840909516).
--- the gate: mesh-host
mesh-host: ok
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-07T00:30:23Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-07T00:30:23Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mesh-host
--- its merge-check.sh (go toolchain)
go: downloading golang.org/x/crypto v0.57.0
go: downloading github.com/nats-io/nats.go v1.54.0
go: downloading github.com/nats-io/nuid v1.0.1
go: downloading github.com/klauspost/compress v1.20.0
go: downloading github.com/nats-io/nkeys v0.4.16
go: downloading golang.org/x/sys v0.48.0
ok github.com/novox/mesh-host/cmd/mesh-bootstrap 1.009s
ok github.com/novox/mesh-host/cmd/mesh-host 1.242s
ok github.com/novox/mesh-host/examples 1.013s
ok github.com/novox/mesh-host/internal/apply 1.691s
ok github.com/novox/mesh-host/internal/bootstrap 1.328s
ok github.com/novox/mesh-host/internal/bundle 1.012s
ok github.com/novox/mesh-host/internal/declaration 1.047s
ok github.com/novox/mesh-host/internal/firewall 1.734s
ok github.com/novox/mesh-host/internal/identity 1.048s
ok github.com/novox/mesh-host/internal/image 1.011s
ok github.com/novox/mesh-host/internal/inventory 1.026s
ok github.com/novox/mesh-host/internal/link 10.366s
ok github.com/novox/mesh-host/internal/outward 1.029s
ok github.com/novox/mesh-host/internal/profile 1.498s
ok github.com/novox/mesh-host/internal/reachable 1.029s
ok github.com/novox/mesh-host/internal/store 1.099s
ok github.com/novox/mesh-host/internal/system 1.030s
ok github.com/novox/mesh-host/internal/tunnel 1.032s
ok github.com/novox/mesh-host/internal/upgrade 1.050s
ok github.com/novox/mesh-host/internal/witness 2.071s
ok github.com/novox/mesh-host/packaging 1.008s
ok github.com/novox/mesh-host/validate 1.007s
**Merge check** at `4b23bed3`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Change plan** — builds mesh-host → ace, g14, novox, shanks; no bus step
- tier 0: mesh-host
- ace: receives mesh-host
- g14: receives mesh-host
- novox: receives mesh-host
- shanks: receives mesh-host
Run by the build seat on g14 as `build-1791332936840909516` (`builds --log build-1791332936840909516`).
```
--- the gate: mesh-host
mesh-host: ok
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-07T00:30:23Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-07T00:30:23Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mesh-host
--- its merge-check.sh (go toolchain)
go: downloading golang.org/x/crypto v0.57.0
go: downloading github.com/nats-io/nats.go v1.54.0
go: downloading github.com/nats-io/nuid v1.0.1
go: downloading github.com/klauspost/compress v1.20.0
go: downloading github.com/nats-io/nkeys v0.4.16
go: downloading golang.org/x/sys v0.48.0
ok github.com/novox/mesh-host/cmd/mesh-bootstrap 1.009s
ok github.com/novox/mesh-host/cmd/mesh-host 1.242s
ok github.com/novox/mesh-host/examples 1.013s
ok github.com/novox/mesh-host/internal/apply 1.691s
ok github.com/novox/mesh-host/internal/bootstrap 1.328s
ok github.com/novox/mesh-host/internal/bundle 1.012s
ok github.com/novox/mesh-host/internal/declaration 1.047s
ok github.com/novox/mesh-host/internal/firewall 1.734s
ok github.com/novox/mesh-host/internal/identity 1.048s
ok github.com/novox/mesh-host/internal/image 1.011s
ok github.com/novox/mesh-host/internal/inventory 1.026s
ok github.com/novox/mesh-host/internal/link 10.366s
ok github.com/novox/mesh-host/internal/outward 1.029s
ok github.com/novox/mesh-host/internal/profile 1.498s
ok github.com/novox/mesh-host/internal/reachable 1.029s
ok github.com/novox/mesh-host/internal/store 1.099s
ok github.com/novox/mesh-host/internal/system 1.030s
ok github.com/novox/mesh-host/internal/tunnel 1.032s
ok github.com/novox/mesh-host/internal/upgrade 1.050s
ok github.com/novox/mesh-host/internal/witness 2.071s
ok github.com/novox/mesh-host/packaging 1.008s
ok github.com/novox/mesh-host/validate 1.007s
```
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
The witness moved a running controller's build into a 0700 directory and deleted it
on proof, while the old process could still be serving. Its directories are now
0711, and a build without a reader is retired and swept once /proc shows nothing
runs from it.
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host)
mesh/repo-check: PASS — its merge-check.sh passed
Change plan — builds mesh-host → ace, g14, novox, shanks; no bus step
tier 0: mesh-host
ace: receives mesh-host
g14: receives mesh-host
novox: receives mesh-host
shanks: receives mesh-host
Run by the build seat on ace as build-1791333959468090814 (builds --log build-1791333959468090814).
--- the gate: mesh-host
mesh-host: ok
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-07T00:39:23Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-07T00:39:23Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mesh-host
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-host/cmd/mesh-bootstrap 1.014s
ok github.com/novox/mesh-host/cmd/mesh-host 1.302s
ok github.com/novox/mesh-host/examples 1.018s
ok github.com/novox/mesh-host/internal/apply 3.981s
ok github.com/novox/mesh-host/internal/bootstrap 1.515s
ok github.com/novox/mesh-host/internal/bundle 1.033s
ok github.com/novox/mesh-host/internal/declaration 1.060s
ok github.com/novox/mesh-host/internal/firewall 1.637s
ok github.com/novox/mesh-host/internal/identity 1.105s
ok github.com/novox/mesh-host/internal/image 1.027s
ok github.com/novox/mesh-host/internal/inventory 1.033s
ok github.com/novox/mesh-host/internal/link 10.331s
ok github.com/novox/mesh-host/internal/liveness 1.057s
ok github.com/novox/mesh-host/internal/outward 1.038s
ok github.com/novox/mesh-host/internal/profile 1.498s
ok github.com/novox/mesh-host/internal/reachable 1.035s
ok github.com/novox/mesh-host/internal/store 1.222s
ok github.com/novox/mesh-host/internal/system 1.039s
ok github.com/novox/mesh-host/internal/tunnel 1.047s
ok github.com/novox/mesh-host/internal/upgrade 1.148s
ok github.com/novox/mesh-host/internal/witness 4.556s
ok github.com/novox/mesh-host/packaging 1.032s
ok github.com/novox/mesh-host/validate 1.032s
**Merge check** at `3a117c2d`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Change plan** — builds mesh-host → ace, g14, novox, shanks; no bus step
- tier 0: mesh-host
- ace: receives mesh-host
- g14: receives mesh-host
- novox: receives mesh-host
- shanks: receives mesh-host
Run by the build seat on ace as `build-1791333959468090814` (`builds --log build-1791333959468090814`).
```
--- the gate: mesh-host
mesh-host: ok
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-07T00:39:23Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-07T00:39:23Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mesh-host
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-host/cmd/mesh-bootstrap 1.014s
ok github.com/novox/mesh-host/cmd/mesh-host 1.302s
ok github.com/novox/mesh-host/examples 1.018s
ok github.com/novox/mesh-host/internal/apply 3.981s
ok github.com/novox/mesh-host/internal/bootstrap 1.515s
ok github.com/novox/mesh-host/internal/bundle 1.033s
ok github.com/novox/mesh-host/internal/declaration 1.060s
ok github.com/novox/mesh-host/internal/firewall 1.637s
ok github.com/novox/mesh-host/internal/identity 1.105s
ok github.com/novox/mesh-host/internal/image 1.027s
ok github.com/novox/mesh-host/internal/inventory 1.033s
ok github.com/novox/mesh-host/internal/link 10.331s
ok github.com/novox/mesh-host/internal/liveness 1.057s
ok github.com/novox/mesh-host/internal/outward 1.038s
ok github.com/novox/mesh-host/internal/profile 1.498s
ok github.com/novox/mesh-host/internal/reachable 1.035s
ok github.com/novox/mesh-host/internal/store 1.222s
ok github.com/novox/mesh-host/internal/system 1.039s
ok github.com/novox/mesh-host/internal/tunnel 1.047s
ok github.com/novox/mesh-host/internal/upgrade 1.148s
ok github.com/novox/mesh-host/internal/witness 4.556s
ok github.com/novox/mesh-host/packaging 1.032s
ok github.com/novox/mesh-host/validate 1.032s
```
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
novox/hq issue 289. While a controller update was rolling out, the witness moved the running controller's build into
.witness/mesh-controller/previous/. That directory is 0700 and owned by root, and the old controller was still serving and running its own binary as each verb. Every verb failed with permission denied. On proof the witness also deletedprevious/without checking whether anything still ran from it.The order stays the same: move the running build aside, unpack the new one, restart (systemd stops the old process, then starts the new), prove, retire. What changes is that each step is now safe for a process still running from the old build (
internal/witness/draining.go):.witness/<name>/retired/and deleted only when no process runs from it.RunningFromreads every/proc/<pid>/exe, including ones that show(deleted). That finds the PID the old build ran as and every command it started.Watcher.Looksweeps retired builds on every look. The "healthy" line says whether the old build was deleted or is waiting for named PIDs.Also included:
internal/bootstrap/publish_test.gois restructured so that the toolchain's gofmt (Go 1.26) and a newer gofmt lay it out the same way. The build seat's check failed on that file as it was on main.Tests (real processes, Linux):
TestABuildIsKeptReachableAndUndeletedWhileAProcessRunsFromIt: the directories are 0711, the running PID is found inprevious/, and after proof the build is still kept until the process stops, then removed at the next sweep.TestABuildOnTrialReplacedIsRetiredNotDeletedUnderItsProcessTestAProcessRunsFromABuildDeletedUnderItmesh-controller check-here: merge-gate PASS (4 of 4 compose), repo-check PASS.Delivery
novox/mesh-host@3a117c2d2bdd— delivered since 2026-10-07T01:05:07ZDelivery plan — builds mesh-host → ace, g14, novox, shanks; no bus step
Machines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
4b23bed3mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host)mesh/repo-check: PASS — its merge-check.sh passedChange plan — builds mesh-host → ace, g14, novox, shanks; no bus step
Run by the build seat on g14 as
build-1791332936840909516(builds --log build-1791332936840909516).4b23bed325to3a117c2d2bMerge check at
3a117c2dmesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host)mesh/repo-check: PASS — its merge-check.sh passedChange plan — builds mesh-host → ace, g14, novox, shanks; no bus step
Run by the build seat on ace as
build-1791333959468090814(builds --log build-1791333959468090814).