test(app): run local broker probe tests against a real daemon
- commit
- 352658cc9263ecf95b7ea06e1115b6543d435bf0
- author
- brice · · 2026-09-23 15:04 UTC
- parent
- c00536e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +32 -157
M internal/app/broker_offline_local_attach_test.go +21 -66
| @@ -32,11 +32,11 @@ import ( | |||
| 32 | 32 | // carriage, so the authenticated physical preamble, the observation open, the | |
| 33 | 33 | // typed remote-catalog command, and the catalogue validation all compose against | |
| 34 | 34 | // production daemon code. They prove the observation is real: it creates no | |
| 35 | - | // session, no process, and no durable identity, and the catalogue it publishes | |
| 35 | + | // session or process and does not change daemon-owned identity; the catalogue it publishes | |
| 36 | 36 | // is exactly what makes an exact terminal attach resolvable. | |
| 37 | 37 | ||
| 38 | 38 | // realLocalDaemonFixture is a real daemon serving one private Unix daemonmux | |
| 39 | - | // carriage, plus the sandbox state directory a probe must never write to. | |
| 39 | + | // carriage, plus the production daemon state directory a probe must never mutate. | |
| 40 | 40 | type realLocalDaemonFixture struct { | |
| 41 | 41 | binding brokerconfig.LocalBinding | |
| 42 | 42 | route string | |
| @@ -52,37 +52,16 @@ type realLocalDaemonFixture struct { | |||
| 52 | 52 | // carriage and returns the fixture the probe dials. | |
| 53 | 53 | func startRealLocalDaemon(t *testing.T) *realLocalDaemonFixture { | |
| 54 | 54 | t.Helper() | |
| 55 | - | _, prodRuntime, prodState := isolateSandboxEnv(t) | |
| 56 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |
| 57 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |
| 58 | - | policy := brokerTestPolicy() | |
| 59 | - | route := filepath.Join(root, "local-mux.sock") | |
| 60 | - | stateDir := filepath.Join(root, "state") | |
| 61 | - | require.NoError(t, os.MkdirAll(stateDir, 0o700)) | |
| 62 | - | writeSandboxConfig(t, root, map[string]any{ | |
| 63 | - | "marker": brokerconfig.Marker, | |
| 64 | - | "registrations": []any{}, | |
| 65 | - | "local": map[string]any{ | |
| 66 | - | "identity": brokerLocalTestIdentity, | |
| 67 | - | "displayOrigin": "local", | |
| 68 | - | "route": route, | |
| 69 | - | "policy": map[string]any{ | |
| 70 | - | "protocolVersion": policy.ProtocolVersion, | |
| 71 | - | "catalogSchemaVersion": policy.CatalogSchemaVersion, | |
| 72 | - | "environmentPolicy": "client-owned", | |
| 73 | - | "transport": policy.Transport, | |
| 74 | - | "trust": policy.Trust, | |
| 75 | - | "launch": policy.Launch, | |
| 76 | - | "isolation": policy.Isolation, | |
| 77 | - | }, | |
| 78 | - | }, | |
| 79 | - | }) | |
| 80 | - | layout, err := offlineLayout(root) | |
| 55 | + | layout := emptyProductionBrokerLayout(t, "") | |
| 56 | + | stateDir := filepath.Dir(layout.Root) | |
| 57 | + | identity, err := daemonidentity.LoadOrCreate(stateDir) | |
| 81 | 58 | require.NoError(t, err) | |
| 82 | - | config, err := brokerconfig.Load(layout) | |
| 59 | + | config, err := brokerconfig.LoadProduction(layout, productionBrokerConfigPath(), identity, localDaemonPolicy(), daemonmux.SocketPath(ipc.SocketDir())) | |
| 83 | 60 | require.NoError(t, err) | |
| 84 | 61 | binding, ok := config.LocalBinding() | |
| 85 | 62 | require.True(t, ok) | |
| 63 | + | route := binding.Route.Path() | |
| 64 | + | require.NoError(t, os.MkdirAll(filepath.Dir(route), 0o700)) | |
| 86 | 65 | ||
| 87 | 66 | listener, err := ipc.ListenMux(route) | |
| 88 | 67 | require.NoError(t, err) | |
| @@ -109,7 +88,6 @@ func startRealLocalDaemon(t *testing.T) *realLocalDaemonFixture { | |||
| 109 | 88 | _ = listener.Close() | |
| 110 | 89 | _ = supervisor.Close() | |
| 111 | 90 | _ = aggregate.Close() | |
| 112 | - | requireProductionUntouched(t, prodRuntime, prodState) | |
| 113 | 91 | }) | |
| 114 | 92 | ||
| 115 | 93 | dial := func(ctx context.Context, _ ports.BrokerDialTarget) (daemonmux.RawFramedTransport, error) { | |
| @@ -257,44 +235,21 @@ func TestBrokerLocalProbeRealCatalogueEnablesExactAttach(t *testing.T) { | |||
| 257 | 235 | require.NotNil(t, exactWelcome.CommittedIdentity) | |
| 258 | 236 | require.Equal(t, resolved, exactWelcome.CommittedIdentity.Target) | |
| 259 | 237 | ||
| 260 | - | // Probing creates no durable identity: only a daemon-owned startup path ever | |
| 261 | - | // creates the identity file, and the probe is not one. | |
| 238 | + | // The daemon-owned identity persists unchanged after probing. | |
| 262 | 239 | _, statErr := os.Lstat(daemonidentity.Path(fixture.stateDir)) | |
| 263 | - | require.ErrorIs(t, statErr, os.ErrNotExist, "an observation must never create daemon identity") | |
| 240 | + | require.NoError(t, statErr, "the daemon-owned identity remains after observation") | |
| 264 | 241 | } | |
| 265 | 242 | ||
| 266 | - | // TestBrokerLocalProbeAbsentDaemonStaysUnavailable proves an absence daemon | |
| 267 | - | // leaves the local observation unavailable with zero identity and creates | |
| 268 | - | // nothing: no session, no process, no durable identity. brokerconfig refuses a | |
| 269 | - | // missing or malformed configuration before any probe exists, and an absent | |
| 270 | - | // carriage is reported, never started. | |
| 243 | + | // TestBrokerLocalProbeAbsentDaemonStaysUnavailable proves that without daemon | |
| 244 | + | // identity, production observation stays unknown and never dials or starts a daemon. | |
| 271 | 245 | func TestBrokerLocalProbeAbsentDaemonStaysUnavailable(t *testing.T) { | |
| 272 | - | _, prodRuntime, prodState := isolateSandboxEnv(t) | |
| 273 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |
| 274 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |
| 275 | - | stateDir := filepath.Join(root, "state") | |
| 276 | - | require.NoError(t, os.MkdirAll(stateDir, 0o700)) | |
| 277 | - | // A local binding provisioned on a socket nothing listens on: the probe must | |
| 278 | - | // report unavailable rather than start anything. | |
| 279 | - | route := filepath.Join(root, "absent-mux.sock") | |
| 280 | - | writeSandboxConfig(t, root, map[string]any{ | |
| 281 | - | "marker": brokerconfig.Marker, | |
| 282 | - | "registrations": []any{}, | |
| 283 | - | "local": map[string]any{ | |
| 284 | - | "identity": brokerLocalTestIdentity, "displayOrigin": "local", "route": route, | |
| 285 | - | "policy": map[string]any{ | |
| 286 | - | "protocolVersion": brokerTestPolicy().ProtocolVersion, "catalogSchemaVersion": brokerTestPolicy().CatalogSchemaVersion, | |
| 287 | - | "environmentPolicy": "client-owned", "transport": brokerTestPolicy().Transport, | |
| 288 | - | "trust": brokerTestPolicy().Trust, "launch": brokerTestPolicy().Launch, "isolation": brokerTestPolicy().Isolation, | |
| 289 | - | }, | |
| 290 | - | }, | |
| 291 | - | }) | |
| 292 | - | layout, err := offlineLayout(root) | |
| 293 | - | require.NoError(t, err) | |
| 294 | - | config, err := brokerconfig.Load(layout) | |
| 246 | + | layout := emptyProductionBrokerLayout(t, "") | |
| 247 | + | stateDir := filepath.Dir(layout.Root) | |
| 248 | + | config, err := brokerconfig.LoadProduction(layout, productionBrokerConfigPath(), "", localDaemonPolicy(), daemonmux.SocketPath(ipc.SocketDir())) | |
| 295 | 249 | require.NoError(t, err) | |
| 296 | 250 | binding, ok := config.LocalBinding() | |
| 297 | 251 | require.True(t, ok) | |
| 252 | + | route := binding.Route.Path() | |
| 298 | 253 | ||
| 299 | 254 | var dialed []string | |
| 300 | 255 | var mu sync.Mutex | |
| @@ -302,26 +257,26 @@ func TestBrokerLocalProbeAbsentDaemonStaysUnavailable(t *testing.T) { | |||
| 302 | 257 | mu.Lock() | |
| 303 | 258 | dialed = append(dialed, target.Address) | |
| 304 | 259 | mu.Unlock() | |
| 305 | - | return ipc.DialMuxContext(ctx, target.Address) | |
| 260 | + | return ipc.DialMuxContext(ctx, binding.Route.Path()) | |
| 306 | 261 | }, daemonmux.DefaultMuxCeilings()) | |
| 307 | 262 | require.NoError(t, err) | |
| 308 | 263 | ||
| 309 | 264 | ctx, cancel := context.WithTimeout(context.Background(), brokerTestWait) | |
| 310 | 265 | defer cancel() | |
| 311 | 266 | observation, err := probe.ProbeLocal(ctx) | |
| 312 | - | require.Error(t, err) | |
| 313 | - | require.Equal(t, domain.RemoteAvailabilityUnreachable, observation.Availability) | |
| 267 | + | require.NoError(t, err) | |
| 268 | + | require.Equal(t, domain.RemoteAvailabilityUnknown, observation.Availability) | |
| 314 | 269 | require.Zero(t, observation.Identity) | |
| 315 | 270 | require.False(t, observation.InventoryKnown) | |
| 316 | 271 | require.Empty(t, observation.Sessions) | |
| 317 | 272 | ||
| 318 | 273 | mu.Lock() | |
| 319 | - | require.Equal(t, []string{binding.Route.Address()}, dialed, "the probe dials only the address its provisioned local binding published") | |
| 274 | + | require.Empty(t, dialed, "without daemon identity the probe must not dial") | |
| 320 | 275 | mu.Unlock() | |
| 321 | 276 | // Nothing was created: no socket, no identity, no production path entry. | |
| 322 | 277 | _, statErr := os.Lstat(route) | |
| 323 | 278 | require.ErrorIs(t, statErr, os.ErrNotExist, "a probe must never create a carriage") | |
| 324 | 279 | _, identityErr := os.Lstat(daemonidentity.Path(stateDir)) | |
| 325 | 280 | require.ErrorIs(t, identityErr, os.ErrNotExist, "a probe must never create daemon identity") | |
| 326 | - | requireProductionUntouched(t, prodRuntime, prodState) | |
| 281 | + | ||
| 327 | 282 | } | |
| @@ -32,11 +32,11 @@ import ( | |||||
| 32 | // carriage, so the authenticated physical preamble, the observation open, the | 32 | // carriage, so the authenticated physical preamble, the observation open, the | ||
| 33 | // typed remote-catalog command, and the catalogue validation all compose against | 33 | // typed remote-catalog command, and the catalogue validation all compose against | ||
| 34 | // production daemon code. They prove the observation is real: it creates no | 34 | // production daemon code. They prove the observation is real: it creates no | ||
| 35 | - | // session, no process, and no durable identity, and the catalogue it publishes | 35 | + | // session or process and does not change daemon-owned identity; the catalogue it publishes |
| 36 | // is exactly what makes an exact terminal attach resolvable. | 36 | // is exactly what makes an exact terminal attach resolvable. | ||
| 37 | 37 | ||||
| 38 | // realLocalDaemonFixture is a real daemon serving one private Unix daemonmux | 38 | // realLocalDaemonFixture is a real daemon serving one private Unix daemonmux | ||
| 39 | - | // carriage, plus the sandbox state directory a probe must never write to. | 39 | + | // carriage, plus the production daemon state directory a probe must never mutate. |
| 40 | type realLocalDaemonFixture struct { | 40 | type realLocalDaemonFixture struct { | ||
| 41 | binding brokerconfig.LocalBinding | 41 | binding brokerconfig.LocalBinding | ||
| 42 | route string | 42 | route string | ||
| @@ -52,37 +52,16 @@ type realLocalDaemonFixture struct { | |||||
| 52 | // carriage and returns the fixture the probe dials. | 52 | // carriage and returns the fixture the probe dials. | ||
| 53 | func startRealLocalDaemon(t *testing.T) *realLocalDaemonFixture { | 53 | func startRealLocalDaemon(t *testing.T) *realLocalDaemonFixture { | ||
| 54 | t.Helper() | 54 | t.Helper() | ||
| 55 | - | _, prodRuntime, prodState := isolateSandboxEnv(t) | 55 | + | layout := emptyProductionBrokerLayout(t, "") |
| 56 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | 56 | + | stateDir := filepath.Dir(layout.Root) |
| 57 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | 57 | + | identity, err := daemonidentity.LoadOrCreate(stateDir) |
| 58 | - | policy := brokerTestPolicy() | |||
| 59 | - | route := filepath.Join(root, "local-mux.sock") | |||
| 60 | - | stateDir := filepath.Join(root, "state") | |||
| 61 | - | require.NoError(t, os.MkdirAll(stateDir, 0o700)) | |||
| 62 | - | writeSandboxConfig(t, root, map[string]any{ | |||
| 63 | - | "marker": brokerconfig.Marker, | |||
| 64 | - | "registrations": []any{}, | |||
| 65 | - | "local": map[string]any{ | |||
| 66 | - | "identity": brokerLocalTestIdentity, | |||
| 67 | - | "displayOrigin": "local", | |||
| 68 | - | "route": route, | |||
| 69 | - | "policy": map[string]any{ | |||
| 70 | - | "protocolVersion": policy.ProtocolVersion, | |||
| 71 | - | "catalogSchemaVersion": policy.CatalogSchemaVersion, | |||
| 72 | - | "environmentPolicy": "client-owned", | |||
| 73 | - | "transport": policy.Transport, | |||
| 74 | - | "trust": policy.Trust, | |||
| 75 | - | "launch": policy.Launch, | |||
| 76 | - | "isolation": policy.Isolation, | |||
| 77 | - | }, | |||
| 78 | - | }, | |||
| 79 | - | }) | |||
| 80 | - | layout, err := offlineLayout(root) | |||
| 81 | require.NoError(t, err) | 58 | require.NoError(t, err) | ||
| 82 | - | config, err := brokerconfig.Load(layout) | 59 | + | config, err := brokerconfig.LoadProduction(layout, productionBrokerConfigPath(), identity, localDaemonPolicy(), daemonmux.SocketPath(ipc.SocketDir())) |
| 83 | require.NoError(t, err) | 60 | require.NoError(t, err) | ||
| 84 | binding, ok := config.LocalBinding() | 61 | binding, ok := config.LocalBinding() | ||
| 85 | require.True(t, ok) | 62 | require.True(t, ok) | ||
| 63 | + | route := binding.Route.Path() | |||
| 64 | + | require.NoError(t, os.MkdirAll(filepath.Dir(route), 0o700)) | |||
| 86 | 65 | ||||
| 87 | listener, err := ipc.ListenMux(route) | 66 | listener, err := ipc.ListenMux(route) | ||
| 88 | require.NoError(t, err) | 67 | require.NoError(t, err) | ||
| @@ -109,7 +88,6 @@ func startRealLocalDaemon(t *testing.T) *realLocalDaemonFixture { | |||||
| 109 | _ = listener.Close() | 88 | _ = listener.Close() | ||
| 110 | _ = supervisor.Close() | 89 | _ = supervisor.Close() | ||
| 111 | _ = aggregate.Close() | 90 | _ = aggregate.Close() | ||
| 112 | - | requireProductionUntouched(t, prodRuntime, prodState) | |||
| 113 | }) | 91 | }) | ||
| 114 | 92 | ||||
| 115 | dial := func(ctx context.Context, _ ports.BrokerDialTarget) (daemonmux.RawFramedTransport, error) { | 93 | dial := func(ctx context.Context, _ ports.BrokerDialTarget) (daemonmux.RawFramedTransport, error) { | ||
| @@ -257,44 +235,21 @@ func TestBrokerLocalProbeRealCatalogueEnablesExactAttach(t *testing.T) { | |||||
| 257 | require.NotNil(t, exactWelcome.CommittedIdentity) | 235 | require.NotNil(t, exactWelcome.CommittedIdentity) | ||
| 258 | require.Equal(t, resolved, exactWelcome.CommittedIdentity.Target) | 236 | require.Equal(t, resolved, exactWelcome.CommittedIdentity.Target) | ||
| 259 | 237 | ||||
| 260 | - | // Probing creates no durable identity: only a daemon-owned startup path ever | 238 | + | // The daemon-owned identity persists unchanged after probing. |
| 261 | - | // creates the identity file, and the probe is not one. | |||
| 262 | _, statErr := os.Lstat(daemonidentity.Path(fixture.stateDir)) | 239 | _, statErr := os.Lstat(daemonidentity.Path(fixture.stateDir)) | ||
| 263 | - | require.ErrorIs(t, statErr, os.ErrNotExist, "an observation must never create daemon identity") | 240 | + | require.NoError(t, statErr, "the daemon-owned identity remains after observation") |
| 264 | } | 241 | } | ||
| 265 | 242 | ||||
| 266 | - | // TestBrokerLocalProbeAbsentDaemonStaysUnavailable proves an absence daemon | 243 | + | // TestBrokerLocalProbeAbsentDaemonStaysUnavailable proves that without daemon |
| 267 | - | // leaves the local observation unavailable with zero identity and creates | 244 | + | // identity, production observation stays unknown and never dials or starts a daemon. |
| 268 | - | // nothing: no session, no process, no durable identity. brokerconfig refuses a | |||
| 269 | - | // missing or malformed configuration before any probe exists, and an absent | |||
| 270 | - | // carriage is reported, never started. | |||
| 271 | func TestBrokerLocalProbeAbsentDaemonStaysUnavailable(t *testing.T) { | 245 | func TestBrokerLocalProbeAbsentDaemonStaysUnavailable(t *testing.T) { | ||
| 272 | - | _, prodRuntime, prodState := isolateSandboxEnv(t) | 246 | + | layout := emptyProductionBrokerLayout(t, "") |
| 273 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | 247 | + | stateDir := filepath.Dir(layout.Root) |
| 274 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | 248 | + | config, err := brokerconfig.LoadProduction(layout, productionBrokerConfigPath(), "", localDaemonPolicy(), daemonmux.SocketPath(ipc.SocketDir())) |
| 275 | - | stateDir := filepath.Join(root, "state") | |||
| 276 | - | require.NoError(t, os.MkdirAll(stateDir, 0o700)) | |||
| 277 | - | // A local binding provisioned on a socket nothing listens on: the probe must | |||
| 278 | - | // report unavailable rather than start anything. | |||
| 279 | - | route := filepath.Join(root, "absent-mux.sock") | |||
| 280 | - | writeSandboxConfig(t, root, map[string]any{ | |||
| 281 | - | "marker": brokerconfig.Marker, | |||
| 282 | - | "registrations": []any{}, | |||
| 283 | - | "local": map[string]any{ | |||
| 284 | - | "identity": brokerLocalTestIdentity, "displayOrigin": "local", "route": route, | |||
| 285 | - | "policy": map[string]any{ | |||
| 286 | - | "protocolVersion": brokerTestPolicy().ProtocolVersion, "catalogSchemaVersion": brokerTestPolicy().CatalogSchemaVersion, | |||
| 287 | - | "environmentPolicy": "client-owned", "transport": brokerTestPolicy().Transport, | |||
| 288 | - | "trust": brokerTestPolicy().Trust, "launch": brokerTestPolicy().Launch, "isolation": brokerTestPolicy().Isolation, | |||
| 289 | - | }, | |||
| 290 | - | }, | |||
| 291 | - | }) | |||
| 292 | - | layout, err := offlineLayout(root) | |||
| 293 | - | require.NoError(t, err) | |||
| 294 | - | config, err := brokerconfig.Load(layout) | |||
| 295 | require.NoError(t, err) | 249 | require.NoError(t, err) | ||
| 296 | binding, ok := config.LocalBinding() | 250 | binding, ok := config.LocalBinding() | ||
| 297 | require.True(t, ok) | 251 | require.True(t, ok) | ||
| 252 | + | route := binding.Route.Path() | |||
| 298 | 253 | ||||
| 299 | var dialed []string | 254 | var dialed []string | ||
| 300 | var mu sync.Mutex | 255 | var mu sync.Mutex | ||
| @@ -302,26 +257,26 @@ func TestBrokerLocalProbeAbsentDaemonStaysUnavailable(t *testing.T) { | |||||
| 302 | mu.Lock() | 257 | mu.Lock() | ||
| 303 | dialed = append(dialed, target.Address) | 258 | dialed = append(dialed, target.Address) | ||
| 304 | mu.Unlock() | 259 | mu.Unlock() | ||
| 305 | - | return ipc.DialMuxContext(ctx, target.Address) | 260 | + | return ipc.DialMuxContext(ctx, binding.Route.Path()) |
| 306 | }, daemonmux.DefaultMuxCeilings()) | 261 | }, daemonmux.DefaultMuxCeilings()) | ||
| 307 | require.NoError(t, err) | 262 | require.NoError(t, err) | ||
| 308 | 263 | ||||
| 309 | ctx, cancel := context.WithTimeout(context.Background(), brokerTestWait) | 264 | ctx, cancel := context.WithTimeout(context.Background(), brokerTestWait) | ||
| 310 | defer cancel() | 265 | defer cancel() | ||
| 311 | observation, err := probe.ProbeLocal(ctx) | 266 | observation, err := probe.ProbeLocal(ctx) | ||
| 312 | - | require.Error(t, err) | 267 | + | require.NoError(t, err) |
| 313 | - | require.Equal(t, domain.RemoteAvailabilityUnreachable, observation.Availability) | 268 | + | require.Equal(t, domain.RemoteAvailabilityUnknown, observation.Availability) |
| 314 | require.Zero(t, observation.Identity) | 269 | require.Zero(t, observation.Identity) | ||
| 315 | require.False(t, observation.InventoryKnown) | 270 | require.False(t, observation.InventoryKnown) | ||
| 316 | require.Empty(t, observation.Sessions) | 271 | require.Empty(t, observation.Sessions) | ||
| 317 | 272 | ||||
| 318 | mu.Lock() | 273 | mu.Lock() | ||
| 319 | - | require.Equal(t, []string{binding.Route.Address()}, dialed, "the probe dials only the address its provisioned local binding published") | 274 | + | require.Empty(t, dialed, "without daemon identity the probe must not dial") |
| 320 | mu.Unlock() | 275 | mu.Unlock() | ||
| 321 | // Nothing was created: no socket, no identity, no production path entry. | 276 | // Nothing was created: no socket, no identity, no production path entry. | ||
| 322 | _, statErr := os.Lstat(route) | 277 | _, statErr := os.Lstat(route) | ||
| 323 | require.ErrorIs(t, statErr, os.ErrNotExist, "a probe must never create a carriage") | 278 | require.ErrorIs(t, statErr, os.ErrNotExist, "a probe must never create a carriage") | ||
| 324 | _, identityErr := os.Lstat(daemonidentity.Path(stateDir)) | 279 | _, identityErr := os.Lstat(daemonidentity.Path(stateDir)) | ||
| 325 | require.ErrorIs(t, identityErr, os.ErrNotExist, "a probe must never create daemon identity") | 280 | require.ErrorIs(t, identityErr, os.ErrNotExist, "a probe must never create daemon identity") | ||
| 326 | - | requireProductionUntouched(t, prodRuntime, prodState) | 281 | + | |
| 327 | } | 282 | } | ||
M internal/app/broker_offline_local_test.go +11 -91
| @@ -34,34 +34,14 @@ const ( | |||
| 34 | 34 | brokerLocalTestEpoch = ports.BrokerEpoch(11) | |
| 35 | 35 | ) | |
| 36 | 36 | ||
| 37 | - | // testLocalSandboxConfig writes and loads one marker-valid sandbox whose only | |
| 38 | - | // provisioned carriage is the broker-owned local binding. | |
| 37 | + | // testLocalSandboxConfig composes the production local authority with a | |
| 38 | + | // daemon-owned identity under private XDG directories. | |
| 39 | 39 | func testLocalSandboxConfig(t *testing.T) (*brokerconfig.Config, brokerconfig.LocalBinding) { | |
| 40 | 40 | t.Helper() | |
| 41 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |
| 42 | - | policy := brokerTestPolicy() | |
| 43 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |
| 44 | - | writeSandboxConfig(t, root, map[string]any{ | |
| 45 | - | "marker": brokerconfig.Marker, | |
| 46 | - | "registrations": []any{}, | |
| 47 | - | "local": map[string]any{ | |
| 48 | - | "identity": brokerLocalTestIdentity, | |
| 49 | - | "displayOrigin": "local", | |
| 50 | - | "route": filepath.Join(root, "local-mux.sock"), | |
| 51 | - | "policy": map[string]any{ | |
| 52 | - | "protocolVersion": policy.ProtocolVersion, | |
| 53 | - | "catalogSchemaVersion": policy.CatalogSchemaVersion, | |
| 54 | - | "environmentPolicy": "client-owned", | |
| 55 | - | "transport": policy.Transport, | |
| 56 | - | "trust": policy.Trust, | |
| 57 | - | "launch": policy.Launch, | |
| 58 | - | "isolation": policy.Isolation, | |
| 59 | - | }, | |
| 60 | - | }, | |
| 61 | - | }) | |
| 62 | - | layout, err := offlineLayout(root) | |
| 41 | + | layout := emptyProductionBrokerLayout(t, "") | |
| 42 | + | identity, err := daemonidentity.LoadOrCreate(filepath.Dir(layout.Root)) | |
| 63 | 43 | require.NoError(t, err) | |
| 64 | - | config, err := brokerconfig.Load(layout) | |
| 44 | + | config, err := brokerconfig.LoadProduction(layout, productionBrokerConfigPath(), identity, localDaemonPolicy(), daemonmux.SocketPath(ipc.SocketDir())) | |
| 65 | 45 | require.NoError(t, err) | |
| 66 | 46 | binding, ok := config.LocalBinding() | |
| 67 | 47 | require.True(t, ok) | |
| @@ -496,47 +476,19 @@ func TestBrokerLocalObservationComposition(t *testing.T) { | |||
| 496 | 476 | require.Same(t, connector, probe.connector) | |
| 497 | 477 | require.Equal(t, brokerLocalTestEpoch, probe.epoch) | |
| 498 | 478 | ||
| 499 | - | t.Run("no local binding provisions no producer", func(t *testing.T) { | |
| 500 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |
| 501 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |
| 502 | - | writeSandboxConfig(t, root, sandboxRegistrationDocument("/tmp/vev-local-test/mux.sock")) | |
| 503 | - | layout, err := brokerconfig.ResolveLayout(root, nil) | |
| 504 | - | require.NoError(t, err) | |
| 505 | - | config, err := brokerconfig.Load(layout) | |
| 506 | - | require.NoError(t, err) | |
| 507 | - | _, err = brokerLocalObservation(config, brokerLocalTestEpoch, connector) | |
| 508 | - | require.Error(t, err) | |
| 509 | - | }) | |
| 510 | 479 | } | |
| 511 | 480 | ||
| 512 | - | // TestOfflineRegistryConfigSelectsLocalObservationOnlyWhenProvisioned proves the | |
| 513 | - | // composition's producer selection is decided by the provisioned configuration | |
| 514 | - | // alone, never by the presence of an identity: a provisioned local binding | |
| 515 | - | // always observes and runs with mutable membership (so the broker owns host | |
| 516 | - | // membership for its whole run, including a zero-host membership), while a | |
| 517 | - | // configuration with no local binding is an explicitly configured fixture that | |
| 518 | - | // stays observation-disabled. | |
| 519 | - | func TestOfflineRegistryConfigSelectsLocalObservationOnlyWhenProvisioned(t *testing.T) { | |
| 520 | - | localConfig, binding := testLocalSandboxConfig(t) | |
| 521 | - | selected, err := offlineRegistryConfig(localConfig, brokerLocalTestEpoch, unreachableCarriageConnector(t)) | |
| 481 | + | // Production always composes local observation and mutable membership, even | |
| 482 | + | // when broker.json contains no remote hosts. | |
| 483 | + | func TestProductionRegistryConfigAlwaysSelectsLocalObservation(t *testing.T) { | |
| 484 | + | config, binding := testLocalSandboxConfig(t) | |
| 485 | + | selected, err := offlineRegistryConfig(config, brokerLocalTestEpoch, unreachableCarriageConnector(t)) | |
| 522 | 486 | require.NoError(t, err) | |
| 523 | 487 | require.False(t, selected.ObservationDisabled) | |
| 524 | - | require.Equal(t, broker.MembershipMutable, selected.MembershipMode, "a provisioned local binding always owns mutable membership") | |
| 488 | + | require.Equal(t, broker.MembershipMutable, selected.MembershipMode) | |
| 525 | 489 | require.NotNil(t, selected.Local) | |
| 526 | 490 | require.Equal(t, binding.DisplayOrigin, selected.Local.DisplayOrigin) | |
| 527 | 491 | require.Equal(t, binding.Policy, selected.Local.Policy) | |
| 528 | - | ||
| 529 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |
| 530 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |
| 531 | - | writeSandboxConfig(t, root, sandboxRegistrationDocument("/tmp/vev-local-test/mux.sock")) | |
| 532 | - | layout, err := brokerconfig.ResolveLayout(root, nil) | |
| 533 | - | require.NoError(t, err) | |
| 534 | - | remoteOnly, err := brokerconfig.Load(layout) | |
| 535 | - | require.NoError(t, err) | |
| 536 | - | disabled, err := offlineRegistryConfig(remoteOnly, brokerLocalTestEpoch, unreachableCarriageConnector(t)) | |
| 537 | - | require.NoError(t, err) | |
| 538 | - | require.True(t, disabled.ObservationDisabled, "a fixture with no local binding stays explicitly disabled") | |
| 539 | - | require.Nil(t, disabled.Local) | |
| 540 | 492 | } | |
| 541 | 493 | ||
| 542 | 494 | // productionLocalConfig builds one production-shaped broker configuration whose | |
| @@ -692,38 +644,6 @@ func TestBrokerLocalObservationNeverSpawnsDaemon(t *testing.T) { | |||
| 692 | 644 | } | |
| 693 | 645 | ||
| 694 | 646 | // TestOfflineRegistryConfigExplicitDisabledFixtureStaysDisabled proves an | |
| 695 | - | // explicitly configured fixture that provisions no local binding remains a | |
| 696 | - | // read-only snapshot owner: it issues no probes, arms no timers, and never | |
| 697 | - | // reconciles, so its disabled mode is never inferred from an identity. | |
| 698 | - | func TestOfflineRegistryConfigExplicitDisabledFixtureStaysDisabled(t *testing.T) { | |
| 699 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |
| 700 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |
| 701 | - | writeSandboxConfig(t, root, sandboxEmptyDocument("3s")) | |
| 702 | - | layout, err := brokerconfig.ResolveLayout(root, nil) | |
| 703 | - | require.NoError(t, err) | |
| 704 | - | config, err := brokerconfig.Load(layout) | |
| 705 | - | require.NoError(t, err) | |
| 706 | - | ||
| 707 | - | selected, err := offlineRegistryConfig(config, brokerLocalTestEpoch, unreachableCarriageConnector(t)) | |
| 708 | - | require.NoError(t, err) | |
| 709 | - | require.True(t, selected.ObservationDisabled) | |
| 710 | - | require.Nil(t, selected.Local) | |
| 711 | - | ||
| 712 | - | stateDir := filepath.Join(shortTempDir(t, "vevl"), "state") | |
| 713 | - | store, err := brokerstore.Open(brokerstore.Options{Dir: stateDir}) | |
| 714 | - | require.NoError(t, err) | |
| 715 | - | t.Cleanup(func() { require.NoError(t, store.Close()) }) | |
| 716 | - | registry, err := broker.NewRegistryWithConfig(brokerLocalTestEpoch, store, nil, clock.New(), discardLog(), selected) | |
| 717 | - | require.NoError(t, err) | |
| 718 | - | startLocalRegistry(t, registry) | |
| 719 | - | ||
| 720 | - | // A disabled registry publishes its restored durable state and then stays | |
| 721 | - | // read-only: no probe, no reconcile, and a zero-host mutation is refused. | |
| 722 | - | require.Eventually(t, func() bool { return registry.Snapshot().Epoch != 0 }, brokerTestWait, time.Millisecond) | |
| 723 | - | _, err = registry.AddHost(context.Background(), "user@example.com", localDaemonPolicy()) | |
| 724 | - | require.ErrorIs(t, err, ports.ErrBrokerMembershipImmutable, "an explicitly disabled fixture owns no mutable membership") | |
| 725 | - | } | |
| 726 | - | ||
| 727 | 647 | // TestProductionRegistryOwnsMutableMembershipWithZeroHosts proves the production | |
| 728 | 648 | // composition owns host membership for its whole run even when it starts with | |
| 729 | 649 | // no configured host at all: the broker may precede every registration, so a | |
| @@ -34,34 +34,14 @@ const ( | |||||
| 34 | brokerLocalTestEpoch = ports.BrokerEpoch(11) | 34 | brokerLocalTestEpoch = ports.BrokerEpoch(11) | ||
| 35 | ) | 35 | ) | ||
| 36 | 36 | ||||
| 37 | - | // testLocalSandboxConfig writes and loads one marker-valid sandbox whose only | 37 | + | // testLocalSandboxConfig composes the production local authority with a |
| 38 | - | // provisioned carriage is the broker-owned local binding. | 38 | + | // daemon-owned identity under private XDG directories. |
| 39 | func testLocalSandboxConfig(t *testing.T) (*brokerconfig.Config, brokerconfig.LocalBinding) { | 39 | func testLocalSandboxConfig(t *testing.T) (*brokerconfig.Config, brokerconfig.LocalBinding) { | ||
| 40 | t.Helper() | 40 | t.Helper() | ||
| 41 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | 41 | + | layout := emptyProductionBrokerLayout(t, "") |
| 42 | - | policy := brokerTestPolicy() | 42 | + | identity, err := daemonidentity.LoadOrCreate(filepath.Dir(layout.Root)) |
| 43 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |||
| 44 | - | writeSandboxConfig(t, root, map[string]any{ | |||
| 45 | - | "marker": brokerconfig.Marker, | |||
| 46 | - | "registrations": []any{}, | |||
| 47 | - | "local": map[string]any{ | |||
| 48 | - | "identity": brokerLocalTestIdentity, | |||
| 49 | - | "displayOrigin": "local", | |||
| 50 | - | "route": filepath.Join(root, "local-mux.sock"), | |||
| 51 | - | "policy": map[string]any{ | |||
| 52 | - | "protocolVersion": policy.ProtocolVersion, | |||
| 53 | - | "catalogSchemaVersion": policy.CatalogSchemaVersion, | |||
| 54 | - | "environmentPolicy": "client-owned", | |||
| 55 | - | "transport": policy.Transport, | |||
| 56 | - | "trust": policy.Trust, | |||
| 57 | - | "launch": policy.Launch, | |||
| 58 | - | "isolation": policy.Isolation, | |||
| 59 | - | }, | |||
| 60 | - | }, | |||
| 61 | - | }) | |||
| 62 | - | layout, err := offlineLayout(root) | |||
| 63 | require.NoError(t, err) | 43 | require.NoError(t, err) | ||
| 64 | - | config, err := brokerconfig.Load(layout) | 44 | + | config, err := brokerconfig.LoadProduction(layout, productionBrokerConfigPath(), identity, localDaemonPolicy(), daemonmux.SocketPath(ipc.SocketDir())) |
| 65 | require.NoError(t, err) | 45 | require.NoError(t, err) | ||
| 66 | binding, ok := config.LocalBinding() | 46 | binding, ok := config.LocalBinding() | ||
| 67 | require.True(t, ok) | 47 | require.True(t, ok) | ||
| @@ -496,47 +476,19 @@ func TestBrokerLocalObservationComposition(t *testing.T) { | |||||
| 496 | require.Same(t, connector, probe.connector) | 476 | require.Same(t, connector, probe.connector) | ||
| 497 | require.Equal(t, brokerLocalTestEpoch, probe.epoch) | 477 | require.Equal(t, brokerLocalTestEpoch, probe.epoch) | ||
| 498 | 478 | ||||
| 499 | - | t.Run("no local binding provisions no producer", func(t *testing.T) { | |||
| 500 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |||
| 501 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |||
| 502 | - | writeSandboxConfig(t, root, sandboxRegistrationDocument("/tmp/vev-local-test/mux.sock")) | |||
| 503 | - | layout, err := brokerconfig.ResolveLayout(root, nil) | |||
| 504 | - | require.NoError(t, err) | |||
| 505 | - | config, err := brokerconfig.Load(layout) | |||
| 506 | - | require.NoError(t, err) | |||
| 507 | - | _, err = brokerLocalObservation(config, brokerLocalTestEpoch, connector) | |||
| 508 | - | require.Error(t, err) | |||
| 509 | - | }) | |||
| 510 | } | 479 | } | ||
| 511 | 480 | ||||
| 512 | - | // TestOfflineRegistryConfigSelectsLocalObservationOnlyWhenProvisioned proves the | 481 | + | // Production always composes local observation and mutable membership, even |
| 513 | - | // composition's producer selection is decided by the provisioned configuration | 482 | + | // when broker.json contains no remote hosts. |
| 514 | - | // alone, never by the presence of an identity: a provisioned local binding | 483 | + | func TestProductionRegistryConfigAlwaysSelectsLocalObservation(t *testing.T) { |
| 515 | - | // always observes and runs with mutable membership (so the broker owns host | 484 | + | config, binding := testLocalSandboxConfig(t) |
| 516 | - | // membership for its whole run, including a zero-host membership), while a | 485 | + | selected, err := offlineRegistryConfig(config, brokerLocalTestEpoch, unreachableCarriageConnector(t)) |
| 517 | - | // configuration with no local binding is an explicitly configured fixture that | |||
| 518 | - | // stays observation-disabled. | |||
| 519 | - | func TestOfflineRegistryConfigSelectsLocalObservationOnlyWhenProvisioned(t *testing.T) { | |||
| 520 | - | localConfig, binding := testLocalSandboxConfig(t) | |||
| 521 | - | selected, err := offlineRegistryConfig(localConfig, brokerLocalTestEpoch, unreachableCarriageConnector(t)) | |||
| 522 | require.NoError(t, err) | 486 | require.NoError(t, err) | ||
| 523 | require.False(t, selected.ObservationDisabled) | 487 | require.False(t, selected.ObservationDisabled) | ||
| 524 | - | require.Equal(t, broker.MembershipMutable, selected.MembershipMode, "a provisioned local binding always owns mutable membership") | 488 | + | require.Equal(t, broker.MembershipMutable, selected.MembershipMode) |
| 525 | require.NotNil(t, selected.Local) | 489 | require.NotNil(t, selected.Local) | ||
| 526 | require.Equal(t, binding.DisplayOrigin, selected.Local.DisplayOrigin) | 490 | require.Equal(t, binding.DisplayOrigin, selected.Local.DisplayOrigin) | ||
| 527 | require.Equal(t, binding.Policy, selected.Local.Policy) | 491 | require.Equal(t, binding.Policy, selected.Local.Policy) | ||
| 528 | - | ||||
| 529 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |||
| 530 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |||
| 531 | - | writeSandboxConfig(t, root, sandboxRegistrationDocument("/tmp/vev-local-test/mux.sock")) | |||
| 532 | - | layout, err := brokerconfig.ResolveLayout(root, nil) | |||
| 533 | - | require.NoError(t, err) | |||
| 534 | - | remoteOnly, err := brokerconfig.Load(layout) | |||
| 535 | - | require.NoError(t, err) | |||
| 536 | - | disabled, err := offlineRegistryConfig(remoteOnly, brokerLocalTestEpoch, unreachableCarriageConnector(t)) | |||
| 537 | - | require.NoError(t, err) | |||
| 538 | - | require.True(t, disabled.ObservationDisabled, "a fixture with no local binding stays explicitly disabled") | |||
| 539 | - | require.Nil(t, disabled.Local) | |||
| 540 | } | 492 | } | ||
| 541 | 493 | ||||
| 542 | // productionLocalConfig builds one production-shaped broker configuration whose | 494 | // productionLocalConfig builds one production-shaped broker configuration whose | ||
| @@ -692,38 +644,6 @@ func TestBrokerLocalObservationNeverSpawnsDaemon(t *testing.T) { | |||||
| 692 | } | 644 | } | ||
| 693 | 645 | ||||
| 694 | // TestOfflineRegistryConfigExplicitDisabledFixtureStaysDisabled proves an | 646 | // TestOfflineRegistryConfigExplicitDisabledFixtureStaysDisabled proves an | ||
| 695 | - | // explicitly configured fixture that provisions no local binding remains a | |||
| 696 | - | // read-only snapshot owner: it issues no probes, arms no timers, and never | |||
| 697 | - | // reconciles, so its disabled mode is never inferred from an identity. | |||
| 698 | - | func TestOfflineRegistryConfigExplicitDisabledFixtureStaysDisabled(t *testing.T) { | |||
| 699 | - | root := filepath.Join(shortTempDir(t, "vevl"), "sandbox") | |||
| 700 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |||
| 701 | - | writeSandboxConfig(t, root, sandboxEmptyDocument("3s")) | |||
| 702 | - | layout, err := brokerconfig.ResolveLayout(root, nil) | |||
| 703 | - | require.NoError(t, err) | |||
| 704 | - | config, err := brokerconfig.Load(layout) | |||
| 705 | - | require.NoError(t, err) | |||
| 706 | - | ||||
| 707 | - | selected, err := offlineRegistryConfig(config, brokerLocalTestEpoch, unreachableCarriageConnector(t)) | |||
| 708 | - | require.NoError(t, err) | |||
| 709 | - | require.True(t, selected.ObservationDisabled) | |||
| 710 | - | require.Nil(t, selected.Local) | |||
| 711 | - | ||||
| 712 | - | stateDir := filepath.Join(shortTempDir(t, "vevl"), "state") | |||
| 713 | - | store, err := brokerstore.Open(brokerstore.Options{Dir: stateDir}) | |||
| 714 | - | require.NoError(t, err) | |||
| 715 | - | t.Cleanup(func() { require.NoError(t, store.Close()) }) | |||
| 716 | - | registry, err := broker.NewRegistryWithConfig(brokerLocalTestEpoch, store, nil, clock.New(), discardLog(), selected) | |||
| 717 | - | require.NoError(t, err) | |||
| 718 | - | startLocalRegistry(t, registry) | |||
| 719 | - | ||||
| 720 | - | // A disabled registry publishes its restored durable state and then stays | |||
| 721 | - | // read-only: no probe, no reconcile, and a zero-host mutation is refused. | |||
| 722 | - | require.Eventually(t, func() bool { return registry.Snapshot().Epoch != 0 }, brokerTestWait, time.Millisecond) | |||
| 723 | - | _, err = registry.AddHost(context.Background(), "user@example.com", localDaemonPolicy()) | |||
| 724 | - | require.ErrorIs(t, err, ports.ErrBrokerMembershipImmutable, "an explicitly disabled fixture owns no mutable membership") | |||
| 725 | - | } | |||
| 726 | - | ||||
| 727 | // TestProductionRegistryOwnsMutableMembershipWithZeroHosts proves the production | 647 | // TestProductionRegistryOwnsMutableMembershipWithZeroHosts proves the production | ||
| 728 | // composition owns host membership for its whole run even when it starts with | 648 | // composition owns host membership for its whole run even when it starts with | ||
| 729 | // no configured host at all: the broker may precede every registration, so a | 649 | // no configured host at all: the broker may precede every registration, so a | ||