detached ece7682

broker_daemon_start_test.go RawHistoryFolder

1package app
2
3import (
4	"context"
5	"errors"
6	"io"
7	"os"
8	"path/filepath"
9	"sync"
10	"sync/atomic"
11	"testing"
12	"time"
13
14	"github.com/stretchr/testify/require"
15
16	"github.com/bnema/vev/internal/adapters/brokerconfig"
17	"github.com/bnema/vev/internal/adapters/daemonmux"
18	"github.com/bnema/vev/internal/ports"
19	"github.com/bnema/vev/internal/protocol/wire"
20)
21
22// Broker-owned daemon start coverage (Plan 001 P7, UI-driver slice 1).
23//
24// These tests drive the real start decision — the closed start authorization,
25// the launch policy, the shared election, and the carriage dial — through the
26// scripted starter seams. Nothing here re-executes a daemon, binds a socket, or
27// reads a session: every assertion is about whether a spawn was elected and
28// which carriage was dialed, which is exactly the contract the broker owns.
29
30// stubRawCarriage is one inert raw framed carriage: enough to satisfy the dial
31// seam without a socket, a peer, or a goroutine.
32type stubRawCarriage struct{ closed atomic.Bool }
33
34func (c *stubRawCarriage) Send(wire.Envelope) error { return nil }
35
36func (c *stubRawCarriage) RecvBounded(uint64) (wire.Envelope, error) { return wire.Envelope{}, io.EOF }
37
38func (c *stubRawCarriage) Close() error {
39	c.closed.Store(true)
40	return nil
41}
42
43// scriptedDaemonStarter is a goroutine-safe recording starter: it counts dials
44// and spawns, keeps the exact target each dial received, and fails every dial
45// until the scripted spawn (or the test) makes the carriage available.
46type scriptedDaemonStarter struct {
47	mu       sync.Mutex
48	carriage string
49	// published is the carriage the scripted daemon advertises as the endpoint
50	// this process can start. Empty means the same carriage it answers at.
51	published string
52	dialed    []ports.BrokerDialTarget
53	dials     int
54	spawns    int
55	// running reports that the daemon behind the carriage is published, as the
56	// elected spawner's daemon is once it binds its endpoint.
57	running bool
58	// available reports whether the carriage answers. A nil func means "answer
59	// only once a spawn happened".
60	available func() bool
61	// dialErr overrides the failure a dial reports while unavailable.
62	dialErr error
63	// spawnErr fails the elected spawn.
64	spawnErr error
65	// daemonOwner is the lifecycle ownership the spawned daemon holds, exactly
66	// as the real daemon does for its whole run. It is what makes a concurrent
67	// second caller wait instead of electing itself.
68	daemonOwner lifecycleOwnership
69}
70
71func (s *scriptedDaemonStarter) starter(backoff backoffConfig) brokerDaemonStarter {
72	return brokerDaemonStarter{
73		dial:    s.dial,
74		spawn:   s.spawn,
75		backoff: backoff,
76		// The scripted daemon publishes exactly the carriage the test declared,
77		// so the start path sees one reachable daemon endpoint like production.
78		published: func() string { return s.publishedCarriage() },
79	}
80}
81
82func (s *scriptedDaemonStarter) dial(_ context.Context, carriage string) (daemonmux.RawFramedTransport, error) {
83	s.mu.Lock()
84	defer s.mu.Unlock()
85	s.dials++
86	s.dialed = append(s.dialed, ports.BrokerDialTarget{Address: carriage})
87	if s.carriage != carriage {
88		return nil, os.ErrNotExist
89	}
90	if s.available == nil {
91		if s.spawns == 0 && !s.running {
92			return nil, os.ErrNotExist
93		}
94	} else if !s.available() {
95		if s.dialErr != nil {
96			return nil, s.dialErr
97		}
98		return nil, os.ErrNotExist
99	}
100	return &stubRawCarriage{}, nil
101}
102
103func (s *scriptedDaemonStarter) spawn() error {
104	s.mu.Lock()
105	defer s.mu.Unlock()
106	s.spawns++
107	if s.spawnErr != nil {
108		return s.spawnErr
109	}
110	// A real spawned daemon immediately takes lifecycle ownership of the
111	// runtime directory and holds it for its whole run; a concurrent caller
112	// therefore observes ErrBusy and waits for the carriage rather than
113	// electing a second spawner.
114	lockDir := filepath.Dir(s.publishedCarriageLocked())
115	if owner, err := daemonLifecycleProbe.TryAcquire(lockDir); err == nil {
116		s.daemonOwner = owner
117	}
118	return nil
119}
120
121// markRunning publishes the daemon the elected spawner started, so the callers
122// waiting on the held election converge on its carriage.
123func (s *scriptedDaemonStarter) markRunning() {
124	s.mu.Lock()
125	defer s.mu.Unlock()
126	s.running = true
127}
128
129// isRunning reports whether the daemon is published.
130func (s *scriptedDaemonStarter) isRunning() bool {
131	s.mu.Lock()
132	defer s.mu.Unlock()
133	return s.running
134}
135
136// publishedCarriageLocked reports the published carriage while the mutex is
137// already held.
138func (s *scriptedDaemonStarter) publishedCarriageLocked() string {
139	if s.published != "" {
140		return s.published
141	}
142	return s.carriage
143}
144
145// publishedCarriage reports the carriage the scripted daemon publishes as the
146// endpoint this process can start.
147func (s *scriptedDaemonStarter) publishedCarriage() string {
148	s.mu.Lock()
149	defer s.mu.Unlock()
150	return s.publishedCarriageLocked()
151}
152
153func (s *scriptedDaemonStarter) counters() (dials, spawns int) {
154	s.mu.Lock()
155	defer s.mu.Unlock()
156	return s.dials, s.spawns
157}
158
159// fastDaemonStartBackoff bounds the election the way an unresponsive daemon
160// would, without the production five-second budget.
161var fastDaemonStartBackoff = backoffConfig{initial: time.Millisecond, max: 2 * time.Millisecond, total: 150 * time.Millisecond}
162
163// withDaemonStarter swaps the package start seam for one test and restores it.
164// The start seam is process state, so no caller of this helper runs in parallel.
165func withDaemonStarter(t *testing.T, starter brokerDaemonStarter) {
166	t.Helper()
167	restore := localDaemonStarter
168	localDaemonStarter = starter
169	t.Cleanup(func() { localDaemonStarter = restore })
170}
171
172// testDaemonCarriage returns a fresh owner-only daemon runtime directory and the
173// exact private carriage a daemon bound to it publishes. It is the carriage the
174// scripted starter declares, so the start path sees one reachable endpoint.
175func testDaemonCarriage(t *testing.T) (dir, carriage string) {
176	t.Helper()
177	dir = t.TempDir()
178	// The lifecycle lock refuses a directory with group or other access, and
179	// t.TempDir() is 0755; the daemon runtime directory is owner-only.
180	require.NoError(t, os.Chmod(dir, 0o700))
181	return dir, daemonmux.SocketPath(dir)
182}
183
184// startableLocalTarget builds the resolved local target the broker publishes for
185// its own machine daemon under the supplied start authorization. The policy is
186// the production local daemon authority, which explicitly names the launch
187// token; a test that needs a non-launching policy overrides it. The address is
188// the bounded token the resolver would publish for the provisioned carriage.
189func startableLocalTarget(t *testing.T, carriage string, mode ports.BrokerDaemonStartMode) ports.BrokerDialTarget {
190	t.Helper()
191	_, binding := testLocalSandboxConfigForCarriage(t, carriage)
192	target := ports.BrokerDialTarget{
193		Fence:            ports.BrokerEndpointFence{Local: true},
194		Address:          binding.Route.Address(),
195		Policy:           localDaemonPolicy(),
196		StartMode:        mode,
197		ExpectedIdentity: ports.BrokerExpectedIdentity{Identity: brokerLocalTestIdentity, Bound: true},
198	}
199	require.NoError(t, target.Validate())
200	return target
201}
202
203// TestDaemonStartExistingOnlyNeverSpawns proves an existing-only acquisition is
204// exactly one carriage dial: no lifecycle probe, no spawn lock, no process, and
205// no fallback of any kind, even though the carriage is absent.
206func TestDaemonStartExistingOnlyNeverSpawns(t *testing.T) {
207	isolateSandboxEnv(t)
208	dir, carriage := testDaemonCarriage(t)
209	scripted := &scriptedDaemonStarter{carriage: carriage}
210	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
211
212	target := startableLocalTarget(t, carriage, ports.BrokerDaemonExistingOnly)
213	raw, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
214	require.ErrorIs(t, err, os.ErrNotExist)
215	var brokerErr ports.BrokerError
216	require.ErrorAs(t, err, &brokerErr)
217	require.Equal(t, ports.BrokerErrorNoDaemon, brokerErr.Code)
218	require.Nil(t, raw)
219
220	dials, spawns := scripted.counters()
221	require.Equal(t, 1, dials, "existing-only is exactly one carriage dial")
222	require.Zero(t, spawns, "existing-only must never spawn")
223	_, statErr := os.Stat(filepath.Join(dir, spawnLockName))
224	require.ErrorIs(t, statErr, os.ErrNotExist, "existing-only must not take the spawn lock")
225	_, probeErr := os.Stat(filepath.Join(dir, "lifecycle.lock"))
226	require.ErrorIs(t, probeErr, os.ErrNotExist, "existing-only must not probe lifecycle ownership")
227}
228
229// TestDaemonStartExistingOnlyReturnsRunningDaemonWithoutProbing proves a
230// reachable carriage is the whole result: the running daemon is never restarted
231// and no election is entered.
232func TestDaemonStartExistingOnlyReturnsRunningDaemonWithoutProbing(t *testing.T) {
233	isolateSandboxEnv(t)
234	dir, carriage := testDaemonCarriage(t)
235	scripted := &scriptedDaemonStarter{carriage: carriage, available: func() bool { return true }}
236	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
237
238	target := startableLocalTarget(t, carriage, ports.BrokerDaemonExistingOnly)
239	raw, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
240	require.NoError(t, err)
241	require.NotNil(t, raw)
242
243	// A start-if-needed acquisition against the same running daemon also
244	// returns it unchanged: the mode authorizes a start, it never demands one.
245	startIfNeeded := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
246	rawStartIfNeeded, err := dialBrokerLocalDaemon(context.Background(), carriage, startIfNeeded)
247	require.NoError(t, err)
248	require.NotNil(t, rawStartIfNeeded)
249
250	dials, spawns := scripted.counters()
251	require.Equal(t, 2, dials)
252	require.Zero(t, spawns, "a running daemon is never restarted")
253	_, probeErr := os.Stat(filepath.Join(dir, "lifecycle.lock"))
254	require.ErrorIs(t, probeErr, os.ErrNotExist, "a reachable carriage never enters the election")
255}
256
257// TestDaemonStartIfNeededSpawnsExactlyOnce proves the authorized start path
258// elects one spawner, then returns the carriage the spawned daemon published.
259func TestDaemonStartIfNeededSpawnsExactlyOnce(t *testing.T) {
260	isolateSandboxEnv(t)
261	dir, carriage := testDaemonCarriage(t)
262	scripted := &scriptedDaemonStarter{carriage: carriage}
263	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
264
265	target := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
266	raw, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
267	require.NoError(t, err)
268	require.NotNil(t, raw)
269
270	dials, spawns := scripted.counters()
271	require.GreaterOrEqual(t, dials, 2, "the start path dials before and after the spawn")
272	require.Equal(t, 1, spawns)
273	// The election released the spawn lock.
274	require.NoFileExists(t, filepath.Join(dir, spawnLockName))
275}
276
277// TestDaemonStartRefusesPolicyWithoutLaunchAuthority proves the closed start
278// authorization can only narrow the resolved policy: a policy that does not name
279// the broker launch authority refuses the start outright, and nothing spawns.
280func TestDaemonStartRefusesPolicyWithoutLaunchAuthority(t *testing.T) {
281	isolateSandboxEnv(t)
282	dir, carriage := testDaemonCarriage(t)
283	scripted := &scriptedDaemonStarter{carriage: carriage}
284	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
285
286	target := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
287	target.Policy.Launch = "sandbox-private"
288	require.NoError(t, target.Validate())
289
290	raw, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
291	require.Error(t, err)
292	require.Nil(t, raw)
293	var typed ports.BrokerError
294	require.ErrorAs(t, err, &typed)
295	require.Equal(t, ports.BrokerErrorConflictingPolicy, typed.Code)
296
297	dials, spawns := scripted.counters()
298	require.Equal(t, 1, dials, "the carriage is dialed once before the refusal")
299	require.Zero(t, spawns, "a non-launching policy must never spawn")
300	require.NoFileExists(t, filepath.Join(dir, spawnLockName))
301}
302
303// TestDaemonStartRefusesRegisteredEndpoint proves the local start mechanics
304// refuse a registered endpoint even on a local carriage: the broker owns the
305// start authority of its own machine daemon only.
306func TestDaemonStartRefusesRegisteredEndpoint(t *testing.T) {
307	isolateSandboxEnv(t)
308	_, carriage := testDaemonCarriage(t)
309	scripted := &scriptedDaemonStarter{carriage: carriage}
310	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
311
312	target := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
313	target.Fence = ports.BrokerEndpointFence{Registration: brokerTestRegistration()}
314	require.NoError(t, target.Validate())
315
316	_, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
317	require.Error(t, err)
318	dials, spawns := scripted.counters()
319	require.Zero(t, dials, "a registered endpoint is refused before any dial")
320	require.Zero(t, spawns)
321}
322
323// TestDaemonStartRefusesCarriageOutsideTheProvisionedRoute proves a carriage that
324// is not an absolute cleaned private mux path is refused before any dial, so a
325// hand-built target can never make the launcher start a daemon for a foreign
326// endpoint.
327func TestDaemonStartRefusesCarriageOutsideTheProvisionedRoute(t *testing.T) {
328	isolateSandboxEnv(t)
329	_, carriage := testDaemonCarriage(t)
330	scripted := &scriptedDaemonStarter{carriage: carriage}
331	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
332
333	target := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
334	for _, invalid := range []string{"", "relative/daemonmux.sock", carriage + "/.."} {
335		_, err := dialBrokerLocalDaemon(context.Background(), invalid, target)
336		require.Error(t, err)
337		var typed ports.BrokerError
338		require.ErrorAs(t, err, &typed)
339		require.Equal(t, ports.BrokerErrorIncompatible, typed.Code)
340	}
341	dials, spawns := scripted.counters()
342	require.Zero(t, dials)
343	require.Zero(t, spawns)
344}
345
346// TestDaemonStartRefusesForeignCarriageStart proves a start is refused for a
347// carriage this process cannot publish: the launcher re-executes this binary,
348// which binds its own private endpoint, so starting a daemon for a foreign
349// carriage would spawn a process the caller could never reach.
350func TestDaemonStartRefusesForeignCarriageStart(t *testing.T) {
351	isolateSandboxEnv(t)
352	_, own := testDaemonCarriage(t)
353	foreignDir := t.TempDir()
354	require.NoError(t, os.Chmod(foreignDir, 0o700))
355	foreign := filepath.Join(foreignDir, "daemonmux.sock")
356
357	// The scripted daemon publishes and answers at the process' own carriage;
358	// the dial target names a different one.
359	scripted := &scriptedDaemonStarter{carriage: own, published: own}
360	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
361
362	target := startableLocalTarget(t, foreign, ports.BrokerDaemonStartIfNeeded)
363	_, err := dialBrokerDaemonCarriage(context.Background(), foreign, target)
364	require.Error(t, err)
365	var typed ports.BrokerError
366	require.ErrorAs(t, err, &typed)
367	require.Equal(t, ports.BrokerErrorIncompatible, typed.Code)
368
369	dials, spawns := scripted.counters()
370	require.Equal(t, 1, dials, "the carriage is dialed once before the refusal")
371	require.Zero(t, spawns, "a foreign carriage must never be started")
372	require.NoFileExists(t, filepath.Join(filepath.Dir(own), spawnLockName))
373}
374
375// TestDaemonStartForeignCarriageWinsOverPolicyRefusal pins the refusal order:
376// an absent daemon on a foreign carriage is incompatible even when the policy
377// also forbids launching.
378func TestDaemonStartForeignCarriageWinsOverPolicyRefusal(t *testing.T) {
379	isolateSandboxEnv(t)
380	_, own := testDaemonCarriage(t)
381	foreignDir := t.TempDir()
382	require.NoError(t, os.Chmod(foreignDir, 0o700))
383	foreign := filepath.Join(foreignDir, "daemonmux.sock")
384	scripted := &scriptedDaemonStarter{carriage: own, published: own}
385	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
386
387	target := startableLocalTarget(t, foreign, ports.BrokerDaemonStartIfNeeded)
388	target.Policy.Launch = "sandbox-private"
389	_, err := dialBrokerDaemonCarriage(context.Background(), foreign, target)
390	var typed ports.BrokerError
391	require.ErrorAs(t, err, &typed)
392	require.Equal(t, ports.BrokerErrorIncompatible, typed.Code)
393	_, spawns := scripted.counters()
394	require.Zero(t, spawns)
395}
396
397// TestDaemonStartDoesNotSpawnOnNonAbsenceFailure proves only proven absence can
398// authorize a start: a permission, path, peer, or cancellation failure is
399// reported unchanged and never repaired by spawning a second daemon.
400func TestDaemonStartDoesNotSpawnOnNonAbsenceFailure(t *testing.T) {
401	tests := []struct {
402		name string
403		err  error
404	}{
405		{name: "permission", err: os.ErrPermission},
406		{name: "context canceled", err: context.Canceled},
407		{name: "invalid path", err: os.ErrInvalid},
408		{name: "unclassified", err: errors.New("carriage refused")},
409	}
410	for _, tt := range tests {
411		t.Run(tt.name, func(t *testing.T) {
412			isolateSandboxEnv(t)
413			dir, carriage := testDaemonCarriage(t)
414			scripted := &scriptedDaemonStarter{
415				carriage:  carriage,
416				available: func() bool { return false },
417				dialErr:   tt.err,
418			}
419			withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
420
421			target := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
422			_, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
423			require.ErrorIs(t, err, tt.err)
424
425			dials, spawns := scripted.counters()
426			require.Equal(t, 1, dials)
427			require.Zero(t, spawns)
428			require.NoFileExists(t, filepath.Join(dir, spawnLockName))
429		})
430	}
431}
432
433// TestDaemonStartConcurrentRequestsShareOneElection proves N concurrent
434// start-if-needed requests never elect a second spawner while one election is
435// already in progress: the spawn lock is held by the elected spawner, so every
436// other caller waits for the carriage it publishes instead of spawning again.
437//
438// The spawn lock is taken here exactly as another client's elected spawner
439// would take it, and the carriage becomes dialable once that "spawner" finishes.
440// The starter therefore records zero spawns of its own, every caller converges
441// on the published carriage, and no caller ever holds the lock itself.
442func TestDaemonStartConcurrentRequestsShareOneElection(t *testing.T) {
443	isolateSandboxEnv(t)
444	dir, carriage := testDaemonCarriage(t)
445	scripted := &scriptedDaemonStarter{carriage: carriage}
446	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
447
448	// Another client's elected spawner holds the lock; the daemon it starts
449	// publishes the carriage shortly afterwards.
450	release, acquired, err := acquireSpawnLock(dir)
451	require.NoError(t, err)
452	require.True(t, acquired, "the test must hold the spawn lock")
453	t.Cleanup(release)
454	// The daemon being started takes lifecycle ownership at once and keeps it
455	// for its whole run, as the real daemon does. Without it, a caller that
456	// dialed just before the daemon was published could win the spawn lock
457	// once it is released and elect itself.
458	daemonOwner, err := daemonLifecycleProbe.TryAcquire(filepath.Dir(carriage))
459	require.NoError(t, err)
460	t.Cleanup(func() { _ = daemonOwner.Release() })
461
462	// The target is resolved once: require.* is not goroutine-safe, and the
463	// resolved target is immutable and shared by every caller.
464	target := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
465
466	const callers = 8
467	var wg sync.WaitGroup
468	results := make([]daemonmux.RawFramedTransport, callers)
469	errs := make([]error, callers)
470	start := make(chan struct{})
471	for i := range callers {
472		wg.Add(1)
473		go func() {
474			defer wg.Done()
475			<-start
476			results[i], errs[i] = dialBrokerLocalDaemon(context.Background(), carriage, target)
477		}()
478	}
479	// Publish the daemon the held election is starting, as its spawner does.
480	go func() {
481		<-start
482		scripted.markRunning()
483	}()
484	close(start)
485	// The held election ends once the carriage answers.
486	require.Eventually(t, func() bool { return scripted.isRunning() }, brokerTestWait, time.Millisecond)
487	release()
488	wg.Wait()
489
490	for i := range callers {
491		require.NoError(t, errs[i], "caller %d", i)
492		require.NotNil(t, results[i], "caller %d", i)
493	}
494	_, spawns := scripted.counters()
495	require.Zero(t, spawns, "a caller must never spawn while another election holds the lock")
496}
497
498// TestDaemonStartConcurrentExistingOnlyNeverSpawns proves concurrent
499// existing-only acquisitions never share a start: each one dials, fails on the
500// same absent carriage, and spawns nothing.
501func TestDaemonStartConcurrentExistingOnlyNeverSpawns(t *testing.T) {
502	isolateSandboxEnv(t)
503	_, carriage := testDaemonCarriage(t)
504	scripted := &scriptedDaemonStarter{carriage: carriage}
505	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
506
507	target := startableLocalTarget(t, carriage, ports.BrokerDaemonExistingOnly)
508	const callers = 8
509	var wg sync.WaitGroup
510	start := make(chan struct{})
511	for range callers {
512		wg.Add(1)
513		go func() {
514			defer wg.Done()
515			<-start
516			_, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
517			require.ErrorIs(t, err, os.ErrNotExist)
518		}()
519	}
520	close(start)
521	wg.Wait()
522
523	dials, spawns := scripted.counters()
524	require.Equal(t, callers, dials)
525	require.Zero(t, spawns)
526}
527
528// TestDaemonStartPropagatesTheWholeTarget proves the resolved target reaches the
529// carriage owner whole: address, fence, policy, start authorization, and expected
530// identity all travel together, so a dial can never act on an authorization the
531// connector did not validate.
532func TestDaemonStartPropagatesTheWholeTarget(t *testing.T) {
533	isolateSandboxEnv(t)
534	_, carriage := testDaemonCarriage(t)
535
536	var seen []ports.BrokerDialTarget
537	var mu sync.Mutex
538	connectorDial := func(ctx context.Context, target ports.BrokerDialTarget) (daemonmux.RawFramedTransport, error) {
539		mu.Lock()
540		seen = append(seen, target)
541		mu.Unlock()
542		return nil, os.ErrNotExist
543	}
544	connector, err := daemonmux.NewEndpointConnector(daemonmux.RawCarrierDialer(connectorDial), daemonmux.DefaultMuxCeilings())
545	require.NoError(t, err)
546
547	// The pool-side connector already owns the whole target; the local start
548	// helper is what turns it into the carriage decision.
549	scripted := &scriptedDaemonStarter{carriage: carriage}
550	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
551
552	target := startableLocalTarget(t, carriage, ports.BrokerDaemonExistingOnly)
553	raw, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
554	require.Error(t, err)
555	require.Nil(t, raw)
556
557	// The exact same target value is what a connector hands to its dial
558	// function, so the local helper observes the identical authorization.
559	_, err = connector.Connect(context.Background(), target)
560	require.Error(t, err)
561	mu.Lock()
562	require.Equal(t, []ports.BrokerDialTarget{target}, seen, "the connector dials the whole resolved target")
563	mu.Unlock()
564	require.Equal(t, ports.BrokerDaemonExistingOnly, seen[0].StartMode)
565	require.True(t, seen[0].Fence.Local)
566	require.Equal(t, target.Policy, seen[0].Policy)
567	require.Equal(t, target.ExpectedIdentity, seen[0].ExpectedIdentity)
568}
569
570// TestDaemonStartNeverFallsBackToASessionDial proves structurally that the
571// start path owns no session carriage: it never dials the daemon's public
572// session socket and never wraps a session transport.
573func TestDaemonStartNeverFallsBackToASessionDial(t *testing.T) {
574	sources := loadAppSources(t, false)
575	body, ok := sources["broker_daemon_start.go"]
576	require.True(t, ok, "the daemon start owner must exist")
577
578	for _, forbidden := range []string{
579		"ipc.DialContext(",
580		"ipc.SocketPath(",
581		"sessionwire.",
582		"localDaemonDialer",
583		"ensureDaemonWithLifecycle",
584		"ensureDaemon(",
585	} {
586		require.NotContains(t, body, forbidden, "the daemon start owner must not reach a session carriage")
587	}
588	// The only transport it hands out is the daemonmux carriage.
589	require.Contains(t, body, "daemonmux.RawFramedTransport")
590}
591
592// TestDaemonStartRefusesIncompleteStarter proves a start seam that cannot dial
593// or spawn fails closed instead of dialing a session or panicking.
594func TestDaemonStartRefusesIncompleteStarter(t *testing.T) {
595	isolateSandboxEnv(t)
596	_, carriage := testDaemonCarriage(t)
597	target := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
598
599	for _, starter := range []brokerDaemonStarter{{}, {dial: dialLocalDaemonCarriage}, {spawn: realSpawn}} {
600		withDaemonStarter(t, starter)
601		_, err := dialBrokerLocalDaemon(context.Background(), carriage, target)
602		require.Error(t, err)
603		var typed ports.BrokerError
604		require.ErrorAs(t, err, &typed)
605		require.Equal(t, ports.BrokerErrorIncompatible, typed.Code)
606	}
607}
608
609// TestDaemonStartRefusesInvalidTarget proves an unvalidated resolved target is
610// refused before any dial or spawn.
611func TestDaemonStartRefusesInvalidTarget(t *testing.T) {
612	isolateSandboxEnv(t)
613	_, carriage := testDaemonCarriage(t)
614	scripted := &scriptedDaemonStarter{carriage: carriage}
615	withDaemonStarter(t, scripted.starter(fastDaemonStartBackoff))
616
617	invalid := startableLocalTarget(t, carriage, ports.BrokerDaemonStartIfNeeded)
618	invalid.StartMode = 0
619	_, err := dialBrokerLocalDaemon(context.Background(), carriage, invalid)
620	require.Error(t, err)
621	var typed ports.BrokerError
622	require.ErrorAs(t, err, &typed)
623	require.Equal(t, ports.BrokerErrorIncompatible, typed.Code)
624	dials, spawns := scripted.counters()
625	require.Zero(t, dials)
626	require.Zero(t, spawns)
627}
628
629// TestDaemonStartArgvFlagRoundTrips proves the helper argument spelling is one
630// closed value both sides agree on, and that an unknown value is refused in
631// either direction.
632func TestDaemonStartArgvFlagRoundTrips(t *testing.T) {
633	tests := []struct {
634		mode ports.BrokerDaemonStartMode
635		flag string
636	}{
637		{mode: ports.BrokerDaemonExistingOnly, flag: "existing-only"},
638		{mode: ports.BrokerDaemonStartIfNeeded, flag: "if-needed"},
639	}
640	for _, tt := range tests {
641		flag, err := brokerDaemonStartArgvFlag(tt.mode)
642		require.NoError(t, err)
643		require.Equal(t, tt.flag, flag)
644		parsed, err := parseBrokerDaemonStartArgvFlag(flag)
645		require.NoError(t, err)
646		require.Equal(t, tt.mode, parsed)
647	}
648	_, err := brokerDaemonStartArgvFlag(ports.BrokerDaemonStartMode(0))
649	require.Error(t, err)
650	for _, invalid := range []string{"", "existing_only", "if_needed", "always", "Existing-Only"} {
651		_, err := parseBrokerDaemonStartArgvFlag(invalid)
652		require.Error(t, err, "value %q must be refused", invalid)
653	}
654}
655
656// testLocalSandboxConfigForCarriage derives local authority from production state.
657func testLocalSandboxConfigForCarriage(t *testing.T, carriage string) (*brokerconfig.Config, brokerconfig.LocalBinding) {
658	t.Helper()
659	layout := emptyProductionBrokerLayout(t, "")
660	config, err := brokerconfig.LoadProduction(layout, productionBrokerConfigPath(), brokerLocalTestIdentity, brokerTestPolicy(), carriage)
661	require.NoError(t, err)
662	binding, ok := config.LocalBinding()
663	require.True(t, ok)
664	return config, binding
665}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close