Problem class: bunker-daemon-capability-advertisement (GAP-075 / DF-BUNKER-9)
I diagnosed the issue live in this environment (deployed bunkerd 0.1.3 reports no isolation capability) and wrote the complete solution to /workspace/solution.md. Here it is:
tmp_isolation in ServerInfoProblem class: bunker-daemon-capability-advertisement (GAP-075 / DF-BUNKER-9)
Repo: deployBunker/bunker · Baseline: master 803149d · Deployed peer: bunkerd 0.1.3 (commit 33c9e00)
Fix commit: d5da8db
README at HEAD promises "Private /tmp per agent" via a per-session pam_namespace instance. The feature (9703082) is in no release tag, so every daemon running a tagged binary (currently v0.1.3) executes agents against the host /tmp. Nothing in any RPC, bunker status, or log says so. The fix is additive capability advertisement: the daemon reports the enforced level in the ServerInfo response it already answers, the CLI renders it (with a warning), and a new CLI talking to a pre-capability daemon renders the proto3 default ("") as "not reported by this daemon - it predates capability reporting" rather than as safe.
9703082 is in no tag; every bunkerd 0.1.3/0.1.4 predates it. Cutting a release alone neither fixes deployed hosts nor tells an operator which host is old.ServerInfoResponse has no isolation field, so a client cannot ask "is private /tmp enforced here?".bunker status never printed one — and a version string is not a capability statement.$ /usr/local/bin/bunkerd --version
bunkerd 0.1.3
commit: 33c9e00
$ strings /usr/local/bin/bunkerd | grep -c 'tmp_isolation'
0
$ strings /usr/local/bin/bunkerd | grep -ci 'pam_namespace'
0
$ curl -s -X POST -H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" -d '{}' \
http://<ip-address>:10001/bunker.v1.Bunkerd/ServerInfo
{"hostname":"karaHermes-mde-7840hs", "version":"0.1.3", "uptimeSeconds":"698027", "maxAgents":20}
No tmp_isolation key: the downgrade is real and invisible.
git tag --contains 9703082 # EMPTY
grep -n 'tmp_isolation' proto/bunker/v1/bunker.proto # no match
go build -o /tmp/bunker-head ./cmd/bunker
/tmp/bunker-head status --server bunker-las-04 # Version 0.1.3, no /tmp line
message ServerInfoResponse {
// ... existing fields 1..7 ...
// "private" | "host-shared" | "unknown" | "" (pre-capability peer, NOT safe)
string tmp_isolation = 8;
// Human-readable reason/precondition.
string tmp_isolation_detail = 9;
}
cd ~/repo/bunker
export PATH="$HOME/go/bin:$PATH" # buf lives in ~/go/bin
buf generate
git status --short
git diff --stat -- 'proto/bunker/v1/*.pb.go' # verify TmpIsolation getters landed
package server
import (
"fmt"
"github.com/deployBunker/bunker/internal/hostsetup"
)
const (
TmpIsolationPrivate = "private"
TmpIsolationHostShared = "host-shared"
TmpIsolationUnknown = "unknown"
TmpIsolationUnreported = "" // proto3 default from a pre-capability daemon
)
// mapTmpNamespaceStatus is PURE: no I/O, no globals. Precedence mirrors
// hostsetup.CheckTmpNamespace's conjunction; the FIRST failure is reported.
func mapTmpNamespaceStatus(state hostsetup.TmpNamespaceState, err error) (level, detail string) {
if err != nil {
return TmpIsolationUnknown, fmt.Sprintf("tmp isolation probe failed: %v", err)
}
// A non-root daemon cannot inspect ownership -> unknown, NEVER host-shared.
if !state.OwnershipVerifiable {
return TmpIsolationUnknown,
"cannot be verified: daemon lacks privilege to inspect /etc/pam.d and /etc/ssh/sshd_config ownership"
}
if state.Active {
return TmpIsolationPrivate, "per-session pam_namespace instance active"
}
switch {
case !state.PAMNamespaceModulePresent:
return TmpIsolationHostShared, "pam_namespace module not present"
case !state.PAMNamespaceEnabled:
return TmpIsolationHostShared, "pam_namespace not enabled in PAM"
case !state.TmpDirConfigured:
return TmpIsolationHostShared, "PAM namespace /tmp template not configured"
case !state.SSHDConfigured:
return TmpIsolationHostShared, "sshd not configured for per-session namespace"
default:
return TmpIsolationHostShared, "pam_namespace provisioning incomplete"
}
}
Map the field names 1:1 to the existing
hostsetup.TmpNamespaceStatein the repo; the precedence is what the matrix test locks down.
sync.Once cache, safe degradationtype tmpIsolationCache struct {
once sync.Once
level string
detail string
}
func (c *tmpIsolationCache) resolve(opts *hostsetup.Options) (level, detail string) {
c.once.Do(func() {
defer func() { // never panic the RPC
if r := recover(); r != nil {
c.level, c.detail = TmpIsolationUnknown, fmt.Sprintf("tmp isolation probe panicked: %v", r)
}
}()
if opts == nil {
c.level, c.detail = TmpIsolationUnknown, "tmp isolation probe not configured"
return
}
state, err := opts.TmpNamespaceStatus()
c.level, c.detail = mapTmpNamespaceStatus(state, err)
})
return c.level, c.detail
}
level, detail := s.tmpIsolation.resolve(s.hostsetup)
resp.Msg.TmpIsolation = level
resp.Msg.TmpIsolationDetail = detail
--allconst tmpIsolationDocPromise = "README promises 'Private /tmp per agent' (per-session pam_namespace)"
func formatTmpIsolation(level, detail string) (text string, warn bool) {
switch level {
case "private":
if detail == "" { detail = "pam_namespace per-session instance active" }
return "private - " + detail, false
case "host-shared":
if detail == "" { detail = "pam_namespace provisioning incomplete" }
return fmt.Sprintf("HOST-SHARED - %s; WARNING: %s does NOT hold on this daemon (private /tmp is not guaranteed)",
detail, tmpIsolationDocPromise), true
case "unknown":
if detail == "" { detail = "cannot be verified" }
return fmt.Sprintf("unknown - %s; %s is not guaranteed", detail, tmpIsolationDocPromise), true
case "": // the empty string IS the legacy-peer signal
return "not reported by this daemon - it predates capability reporting; " +
"build/run a daemon from the same commit as the CLI (private /tmp is not guaranteed)", true
default:
return fmt.Sprintf("unrecognized value %q; %s is not guaranteed", level, tmpIsolationDocPromise), true
}
}
line, warn := formatTmpIsolation(info.TmpIsolation, info.TmpIsolationDetail)
if warn { fmt.Fprintf(w, " %-9s %s\n", "/tmp:", colorize(yellow, line)) } else { fmt.Fprintf(w, " %-9s %s\n", "/tmp:", line) }
README: private /tmp requires a pam_namespace-capable daemon; bunker status is the authoritative check. CHANGELOG: additive ServerInfo.tmp_isolation.
TestForemanDFBunker9MappingMatrix| # | Input | Level | Detail contains |
|---|---|---|---|
| 1 | probe error | unknown |
probe failed |
| 2 | OwnershipVerifiable=false |
unknown |
cannot be verified (never host-shared) |
| 3 | Active=true |
private |
instance active |
| 4 | module missing | host-shared |
module not present |
| 5 | PAM not enabled | host-shared |
not enabled in PAM |
| 6 | /tmp template missing | host-shared |
template not configured |
| 7 | sshd not configured | host-shared |
sshd not configured |
| 8 | all false, ownership ok | host-shared |
provisioning incomplete |
| 9 | zero value, nil err | host-shared |
provisioning incomplete |
go test ./internal/server/ -run TestForemanDFBunker9MappingMatrix -count=1 -v
go test ./internal/server/ -run TestTmpIsolationCache -count=1 -v
formatTmpIsolationprivate (no warning) · host-shared (HOST-SHARED … WARNING: README promise does NOT hold) · unknown (unknown - <detail>) · "" (not reported by this daemon - it predates capability reporting). A --all test covers mixed old/new peers.
0.1.3 daemonA green unit suite cannot prove this: a proto field nobody sends looks identical to a daemon that does not know the field.
/usr/local/bin/bunkerd --version # bunkerd 0.1.3 (33c9e00)
strings /usr/local/bin/bunkerd | grep -c tmp_isolation # 0
go build -o /tmp/bunker-head ./cmd/bunker
/tmp/bunker-head status --server bunker-las-04
Required output:
Version: 0.1.3
Status: ONLINE
/tmp: not reported by this daemon - it predates capability reporting; build/run a daemon from the same commit as the CLI (private /tmp is not guaranteed)
Any "docs promise a feature tagged binaries lack" bug is fixed by advertising the enforced capability in a response the client already calls, not by cutting a release — and every optional capability field needs semantics for absent ("" → warning, never safe), unknown (cannot verify, ≠ off), negative (host-shared, only when ownership is verifiable), and positive (private), or the report re-introduces the silence.
The full document is saved at /workspace/solution.md. Key live proof I reproduced here: the running daemon's ServerInfo returns {"version":"0.1.3",...} with no tmp_isolation, and the 0.1.3 binary contains zero occurrences of either tmp_isolation or pam_namespace.
# Evidence - Problem class: bunker-daemon-capability-advertisement - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T13:55:28.244Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: a documented, security-relevant per-agent feature silently does not exist on the daemon you are actually talking to, and nothing in any command or API response says so. Concretely: README (HEAD) promises 'Private /tmp per agent' (per-session pam_namespace instance, fail-closed validation), but `bunker exec <id> -- ls /tmp` against the deployed daemon (Version: 0.1.3) returns the HOST /tmp and no command reports the isolation level. An operator reads the docs, believes isolation holds, and gets host-shared /tmp with no signal. DIAGNOSIS ORDER (each step is cheap and falsifiable): (1) prove the feature is unreleased: `git tag --contains <feature-sha>` -> EMPTY means every tagged binary predates it (here 9703082 = GAP-075 private /tmp is in NO tag; v0.1.4 predates it); (2) prove nothing surfaces it: grep the proto for a capability field and grep the CLI's status/info output -> no match means the downgrade is invisible; (3) prove it live: build HEAD's CLI and run `bunker status --server <host>` against the old daemon -> it prints the daemon's Version (0.1.3) next to your expectations with no isolation line. FIX (additive capability advertisement, ships without breaking old peers): (a) add fields to the ServerInfoResponse proto (here `string tmp_isolation = 8` with values private|host-shared|unknown, plus `tmp_isolation_detail = 9`) and regenerate with buf generate (remote plugins; `buf` in ~/go/bin, `buf generate` at repo root, verify the .pb.go diff landed); (b) populate them in the daemon's ServerInfo from the host-state probe the feature already exposes (hostsetup Options.TmpNamespaceStatus()), through a PURE mapping function (state, err) -> (level, detail) so it is unit-testable without root: probe error -> unknown; state not ownership-verifiable (daemon not root) -> unknown 'cannot be verified' (NEVER host-shared - a non-root daemon cannot observe ownership, so claiming host-shared is a lie); state.Active -> private; otherwise host-shared plus the FIRST failing provisioning reason, produced by a fixed-precedence mirror of the feature's own conjunction; (c) cache the probe with sync.Once - it stats host PAM/sshd files and runs getent, so a hot RPC must not re-probe; degrade a nil config to unknown and never let the probe error the RPC or panic; (d) print it in the CLI's status output (single server AND --all, which must share one formatter): private / HOST-SHARED with a loud warning banner naming the doc promise that does not hold / unknown with detail / and for an EMPTY field 'not reported by this daemon - it predates capability reporting' (the empty-string case IS the legacy-peer signal and must not be rendered as safe); (e) document that the check exists and that the feature is version-gated. VERIFY: fix has to be proven three ways - the pure mapping table (active, probe error, non-root, first-failure, zero-value), the CLI rendering of all four states, and LIVE against the old daemon with the new CLI: `bunker status --server <old-host>` must print Version 0.1.3 together with '/tmp: not reported by this daemon - it predates capability reporting'. That live print is the proof the silent downgrade became visible; a green unit suite alone cannot show it (a proto field nobody sends looks identical to a daemon that does not know the field). GENERALIZATION: any 'docs promise a feature that tagged binaries do not have' bug is fixed by advertising the ENFORCED capability in a response you already call, not by cutting a release alone - and every optional capability field needs a defined meaning for 'absent' (pre-feature peer), 'unknown' (cannot verify) and 'negative' (verified off), or the report re-introduces the silence.", "environment": "Go 1.26 multi-host daemon fleet (bunkerd + bunker CLI over connectrpc/protobuf); repo deployBunker/bunker; deployed daemon hosts run tagged releases (v0.1.3) while the control host builds HEAD; host feature = pam_namespace per-session private /tmp", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "bunker-daemon-capability-advertisement", "provider": "openrouter", "solved_at": "2026-09-16T13:55:28.244Z", "version": "master 803149d (bunker, 2026-09-16)"}I diagnosed the issue live in this environment (deployed bunkerd 0.1.3 reports no isolation capability) and wrote the complete solution to /workspace/solution.md. Here it is:
tmp_isolation in ServerInfoProblem class: bunker-daemon-capability-advertisement (GAP-075 / DF-BUNKER-9)
Repo: deployBunker/bunker · Baseline: master 803149d · Deployed peer: bunkerd 0.1.3 (commit 33c9e00)
Fix commit: d5da8db
README at HEAD promises "Private /tmp per agent" via a per-session pam_namespace instance. The feature (9703082) is in no release tag, so every daemon running a tagged binary (currently v0.1.3) executes agents against the host /tmp. Nothing in any RPC, bunker status, or log says so. The fix is additive capability advertisement: the daemon reports the enforced level in the ServerInfo response it already answers, the CLI renders it (with a warning), and a new CLI talking to a pre-capability daemon renders the proto3 default ("") as "not reported by this daemon - it predates capability reporting" rather than as safe.
9703082 is in no tag; every bunkerd 0.1.3/0.1.4 predates it. Cutting a release alone neither fixes deployed hosts nor tells an operator which host is old.ServerInfoResponse has no isolation field, so a client cannot ask "is private /tmp enforced here?".bunker status never printed one — and a version string is not a capability statement.$ /usr/local/bin/bunkerd --version
bunkerd 0.1.3
commit: 33c9e00
$ strings /usr/local/bin/bunkerd | grep -c 'tmp_isolation'
0
$ strings /usr/local/bin/bunkerd | grep -ci 'pam_namespace'
0
$ curl -s -X POST -H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" -d '{}' \
http://<ip-address>:10001/bunker.v1.Bunkerd/ServerInfo
{"hostname":"karaHermes-mde-7840hs", "version":"0.1.3", "uptimeSeconds":"698027", "maxAgents":20}
No tmp_isolation key: the downgrade is real and invisible.
git tag --contains 9703082 # EMPTY
grep -n 'tmp_isolation' proto/bunker/v1/bunker.proto # no match
go build -o /tmp/bunker-head ./cmd/bunker
/tmp/bunker-head status --server bunker-las-04 # Version 0.1.3, no /tmp line
message ServerInfoResponse {
// ... existing fields 1..7 ...
// "private" | "host-shared" | "unknown" | "" (pre-capability peer, NOT safe)
string tmp_isolation = 8;
// Human-readable reason/precondition.
string tmp_isolation_detail = 9;
}
cd ~/repo/bunker
export PATH="$HOME/go/bin:$PATH" # buf lives in ~/go/bin
buf generate
git status --short
git diff --stat -- 'proto/bunker/v1/*.pb.go' # verify TmpIsolation getters landed
package server
import (
"fmt"
"github.com/deployBunker/bunker/internal/hostsetup"
)
const (
TmpIsolationPrivate = "private"
TmpIsolationHostShared = "host-shared"
TmpIsolationUnknown = "unknown"
TmpIsolationUnreported = "" // proto3 default from a pre-capability daemon
)
// mapTmpNamespaceStatus is PURE: no I/O, no globals. Precedence mirrors
// hostsetup.CheckTmpNamespace's conjunction; the FIRST failure is reported.
func mapTmpNamespaceStatus(state hostsetup.TmpNamespaceState, err error) (level, detail string) {
if err != nil {
return TmpIsolationUnknown, fmt.Sprintf("tmp isolation probe failed: %v", err)
}
// A non-root daemon cannot inspect ownership -> unknown, NEVER host-shared.
if !state.OwnershipVerifiable {
return TmpIsolationUnknown,
"cannot be verified: daemon lacks privilege to inspect /etc/pam.d and /etc/ssh/sshd_config ownership"
}
if state.Active {
return TmpIsolationPrivate, "per-session pam_namespace instance active"
}
switch {
case !state.PAMNamespaceModulePresent:
return TmpIsolationHostShared, "pam_namespace module not present"
case !state.PAMNamespaceEnabled:
return TmpIsolationHostShared, "pam_namespace not enabled in PAM"
case !state.TmpDirConfigured:
return TmpIsolationHostShared, "PAM namespace /tmp template not configured"
case !state.SSHDConfigured:
return TmpIsolationHostShared, "sshd not configured for per-session namespace"
default:
return TmpIsolationHostShared, "pam_namespace provisioning incomplete"
}
}
Map the field names 1:1 to the existing
hostsetup.TmpNamespaceStatein the repo; the precedence is what the matrix test locks down.
sync.Once cache, safe degradationtype tmpIsolationCache struct {
once sync.Once
level string
detail string
}
func (c *tmpIsolationCache) resolve(opts *hostsetup.Options) (level, detail string) {
c.once.Do(func() {
defer func() { // never panic the RPC
if r := recover(); r != nil {
c.level, c.detail = TmpIsolationUnknown, fmt.Sprintf("tmp isolation probe panicked: %v", r)
}
}()
if opts == nil {
c.level, c.detail = TmpIsolationUnknown, "tmp isolation probe not configured"
return
}
state, err := opts.TmpNamespaceStatus()
c.level, c.detail = mapTmpNamespaceStatus(state, err)
})
return c.level, c.detail
}
level, detail := s.tmpIsolation.resolve(s.hostsetup)
resp.Msg.TmpIsolation = level
resp.Msg.TmpIsolationDetail = detail
--allconst tmpIsolationDocPromise = "README promises 'Private /tmp per agent' (per-session pam_namespace)"
func formatTmpIsolation(level, detail string) (text string, warn bool) {
switch level {
case "private":
if detail == "" { detail = "pam_namespace per-session instance active" }
return "private - " + detail, false
case "host-shared":
if detail == "" { detail = "pam_namespace provisioning incomplete" }
return fmt.Sprintf("HOST-SHARED - %s; WARNING: %s does NOT hold on this daemon (private /tmp is not guaranteed)",
detail, tmpIsolationDocPromise), true
case "unknown":
if detail == "" { detail = "cannot be verified" }
return fmt.Sprintf("unknown - %s; %s is not guaranteed", detail, tmpIsolationDocPromise), true
case "": // the empty string IS the legacy-peer signal
return "not reported by this daemon - it predates capability reporting; " +
"build/run a daemon from the same commit as the CLI (private /tmp is not guaranteed)", true
default:
return fmt.Sprintf("unrecognized value %q; %s is not guaranteed", level, tmpIsolationDocPromise), true
}
}
line, warn := formatTmpIsolation(info.TmpIsolation, info.TmpIsolationDetail)
if warn { fmt.Fprintf(w, " %-9s %s\n", "/tmp:", colorize(yellow, line)) } else { fmt.Fprintf(w, " %-9s %s\n", "/tmp:", line) }
README: private /tmp requires a pam_namespace-capable daemon; bunker status is the authoritative check. CHANGELOG: additive ServerInfo.tmp_isolation.
TestForemanDFBunker9MappingMatrix| # | Input | Level | Detail contains |
|---|---|---|---|
| 1 | probe error | unknown |
probe failed |
| 2 | OwnershipVerifiable=false |
unknown |
cannot be verified (never host-shared) |
| 3 | Active=true |
private |
instance active |
| 4 | module missing | host-shared |
module not present |
| 5 | PAM not enabled | host-shared |
not enabled in PAM |
| 6 | /tmp template missing | host-shared |
template not configured |
| 7 | sshd not configured | host-shared |
sshd not configured |
| 8 | all false, ownership ok | host-shared |
provisioning incomplete |
| 9 | zero value, nil err | host-shared |
provisioning incomplete |
go test ./internal/server/ -run TestForemanDFBunker9MappingMatrix -count=1 -v
go test ./internal/server/ -run TestTmpIsolationCache -count=1 -v
formatTmpIsolationprivate (no warning) · host-shared (HOST-SHARED … WARNING: README promise does NOT hold) · unknown (unknown - <detail>) · "" (not reported by this daemon - it predates capability reporting). A --all test covers mixed old/new peers.
0.1.3 daemonA green unit suite cannot prove this: a proto field nobody sends looks identical to a daemon that does not know the field.
/usr/local/bin/bunkerd --version # bunkerd 0.1.3 (33c9e00)
strings /usr/local/bin/bunkerd | grep -c tmp_isolation # 0
go build -o /tmp/bunker-head ./cmd/bunker
/tmp/bunker-head status --server bunker-las-04
Required output:
Version: 0.1.3
Status: ONLINE
/tmp: not reported by this daemon - it predates capability reporting; build/run a daemon from the same commit as the CLI (private /tmp is not guaranteed)
Any "docs promise a feature tagged binaries lack" bug is fixed by advertising the enforced capability in a response the client already calls, not by cutting a release — and every optional capability field needs semantics for absent ("" → warning, never safe), unknown (cannot verify, ≠ off), negative (host-shared, only when ownership is verifiable), and positive (private), or the report re-introduces the silence.
The full document is saved at /workspace/solution.md. Key live proof I reproduced here: the running daemon's ServerInfo returns {"version":"0.1.3",...} with no tmp_isolation, and the 0.1.3 binary contains zero occurrences of either tmp_isolation or pam_namespace.
# Evidence - Problem class: bunker-daemon-capability-advertisement - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-16T13:55:28.244Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: a documented, security-relevant per-agent feature silently does not exist on the daemon you are actually talking to, and nothing in any command or API response says so. Concretely: README (HEAD) promises 'Private /tmp per agent' (per-session pam_namespace instance, fail-closed validation), but `bunker exec <id> -- ls /tmp` against the deployed daemon (Version: 0.1.3) returns the HOST /tmp and no command reports the isolation level. An operator reads the docs, believes isolation holds, and gets host-shared /tmp with no signal. DIAGNOSIS ORDER (each step is cheap and falsifiable): (1) prove the feature is unreleased: `git tag --contains <feature-sha>` -> EMPTY means every tagged binary predates it (here 9703082 = GAP-075 private /tmp is in NO tag; v0.1.4 predates it); (2) prove nothing surfaces it: grep the proto for a capability field and grep the CLI's status/info output -> no match means the downgrade is invisible; (3) prove it live: build HEAD's CLI and run `bunker status --server <host>` against the old daemon -> it prints the daemon's Version (0.1.3) next to your expectations with no isolation line. FIX (additive capability advertisement, ships without breaking old peers): (a) add fields to the ServerInfoResponse proto (here `string tmp_isolation = 8` with values private|host-shared|unknown, plus `tmp_isolation_detail = 9`) and regenerate with buf generate (remote plugins; `buf` in ~/go/bin, `buf generate` at repo root, verify the .pb.go diff landed); (b) populate them in the daemon's ServerInfo from the host-state probe the feature already exposes (hostsetup Options.TmpNamespaceStatus()), through a PURE mapping function (state, err) -> (level, detail) so it is unit-testable without root: probe error -> unknown; state not ownership-verifiable (daemon not root) -> unknown 'cannot be verified' (NEVER host-shared - a non-root daemon cannot observe ownership, so claiming host-shared is a lie); state.Active -> private; otherwise host-shared plus the FIRST failing provisioning reason, produced by a fixed-precedence mirror of the feature's own conjunction; (c) cache the probe with sync.Once - it stats host PAM/sshd files and runs getent, so a hot RPC must not re-probe; degrade a nil config to unknown and never let the probe error the RPC or panic; (d) print it in the CLI's status output (single server AND --all, which must share one formatter): private / HOST-SHARED with a loud warning banner naming the doc promise that does not hold / unknown with detail / and for an EMPTY field 'not reported by this daemon - it predates capability reporting' (the empty-string case IS the legacy-peer signal and must not be rendered as safe); (e) document that the check exists and that the feature is version-gated. VERIFY: fix has to be proven three ways - the pure mapping table (active, probe error, non-root, first-failure, zero-value), the CLI rendering of all four states, and LIVE against the old daemon with the new CLI: `bunker status --server <old-host>` must print Version 0.1.3 together with '/tmp: not reported by this daemon - it predates capability reporting'. That live print is the proof the silent downgrade became visible; a green unit suite alone cannot show it (a proto field nobody sends looks identical to a daemon that does not know the field). GENERALIZATION: any 'docs promise a feature that tagged binaries do not have' bug is fixed by advertising the ENFORCED capability in a response you already call, not by cutting a release alone - and every optional capability field needs a defined meaning for 'absent' (pre-feature peer), 'unknown' (cannot verify) and 'negative' (verified off), or the report re-introduces the silence.", "environment": "Go 1.26 multi-host daemon fleet (bunkerd + bunker CLI over connectrpc/protobuf); repo deployBunker/bunker; deployed daemon hosts run tagged releases (v0.1.3) while the control host builds HEAD; host feature = pam_namespace per-session private /tmp", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "bunker-daemon-capability-advertisement", "provider": "openrouter", "solved_at": "2026-09-16T13:55:28.244Z", "version": "master 803149d (bunker, 2026-09-16)"}