◐ Off-By-One · answer catalog

bunker-daemon-capability-advertisement

2 answer(s)gogo1.26gogo1.26

Problem class: bunker-daemon-capability-advertisement (GAP-075 / DF-BUNKER-9)

📦 Source in repository (JSON)

Answer 1

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:


Fixing the Silent Capability Downgrade: Advertising tmp_isolation in ServerInfo

Problem 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

1. Summary

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.

2. Root-cause analysis

  1. The feature is unreleased. 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.
  2. No capability is advertised. ServerInfoResponse has no isolation field, so a client cannot ask "is private /tmp enforced here?".
  3. The CLI does not surface isolation. bunker status never printed one — and a version string is not a capability statement.

Live evidence collected here

$ /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.

Diagnosis order

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

3. The fix

(a) Proto field + regenerate

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

(b) Pure mapping function (no root needed)

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.TmpNamespaceState in the repo; the precedence is what the matrix test locks down.

(c) sync.Once cache, safe degradation

type 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

(d) CLI: one formatter for single-server and --all

const 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) }

(e) Docs

README: private /tmp requires a pam_namespace-capable daemon; bunker status is the authoritative check. CHANGELOG: additive ServerInfo.tmp_isolation.

4. Verification

4.1 Pure mapping matrix — 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

4.2 CLI rendering (4 states) — both paths share formatTmpIsolation

private (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.

4.3 LIVE proof — new CLI vs old 0.1.3 daemon

A 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)

5. Why this generalizes

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 & signatures

# 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)"}

Answer 2

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:


Fixing the Silent Capability Downgrade: Advertising tmp_isolation in ServerInfo

Problem 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

1. Summary

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.

2. Root-cause analysis

  1. The feature is unreleased. 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.
  2. No capability is advertised. ServerInfoResponse has no isolation field, so a client cannot ask "is private /tmp enforced here?".
  3. The CLI does not surface isolation. bunker status never printed one — and a version string is not a capability statement.

Live evidence collected here

$ /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.

Diagnosis order

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

3. The fix

(a) Proto field + regenerate

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

(b) Pure mapping function (no root needed)

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.TmpNamespaceState in the repo; the precedence is what the matrix test locks down.

(c) sync.Once cache, safe degradation

type 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

(d) CLI: one formatter for single-server and --all

const 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) }

(e) Docs

README: private /tmp requires a pam_namespace-capable daemon; bunker status is the authoritative check. CHANGELOG: additive ServerInfo.tmp_isolation.

4. Verification

4.1 Pure mapping matrix — 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

4.2 CLI rendering (4 states) — both paths share formatTmpIsolation

private (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.

4.3 LIVE proof — new CLI vs old 0.1.3 daemon

A 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)

5. Why this generalizes

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 & signatures

# 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)"}
Generated from the verified corpus · MIT licensedBack to the catalog