detached d84053d

test(adapters,broker): move same-package-only test seams into export_test.go

listener.inFlight/liveSessions/refusal, brokeripc.DefaultSocketPath,
brokerwire.WithMaxStagedBytes, daemonmux ServerBinding.Accepts/Policy/Origin,
NewStreamEngine, NewListener, NewScheduler, ServerSupervisor.Children,
DecodePreambleRequestBytes, ipc.SocketPath, quic.WithRuntimeObserver, and
broker.NewRegistry/setHosts are all legitimate test conveniences over live
production constructors/accessors, with every caller confirmed to be a
same-package test. They move into a per-package export_test.go so they no
longer count as unreachable production code, without changing what any test
calls or asserts.

ipc.SocketPath has no current test caller (brokeripc.SocketPath, a distinct
function, is what every call site actually uses); it moves anyway per the
documented seam list, flagged here for the owner to reconsider deleting
outright.

daemonmux.NewServerBinding, brokeripc.SocketDir, brokerwire.MutationKindOf,
and snapshot.NewRepository are NOT touched: each is called from a different
package's tests (internal/app, internal/app, brokeripc, internal/app
respectively), and Go's build model cannot export a _test.go symbol across
package boundaries, so they must stay production code. They remain flagged
by deadcode and are documented in the phase report as accepted exceptions.
commit
d84053d662c6cad7e55d90ce4302491717198045
author
brice · · 2026-09-23 01:56 UTC
parent
e312f75
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
18 files · +220 -200
A internal/adapters/brokeripc/export_test.go +34 -0
@@ -0,0 +1,34 @@
1+package brokeripc
2+
3+// Test-only accessors (export_test.go pattern): these observe listener
4+// internals that only this package's own tests need. They are kept out of
5+// the production build so a live server's diagnostics or capacity never
6+// depend on a test-only accessor.
7+
8+// refusal reports the most recent accept refusal, or nil if the listener has
9+// refused nothing yet.
10+func (l *listener) refusal() error {
11+ l.mu.Lock()
12+ defer l.mu.Unlock()
13+ return l.lastRefuse
14+}
15+
16+// inFlight reports the count of accepted carriages still completing their
17+// handshake.
18+func (l *listener) inFlight() int {
19+ l.mu.Lock()
20+ defer l.mu.Unlock()
21+ return len(l.pending)
22+}
23+
24+// liveSessions reports the count of admitted, still-open sessions.
25+func (l *listener) liveSessions() int {
26+ l.mu.Lock()
27+ defer l.mu.Unlock()
28+ return len(l.sessions)
29+}
30+
31+// DefaultSocketPath returns the broker endpoint path under SocketFileName's
32+// convenience default runtime directory, for tests that need it without
33+// composing SocketPath(SocketDir()) themselves.
34+func DefaultSocketPath() string { return SocketPath(SocketDir()) }
@@ -0,0 +1,34 @@
1+package brokeripc
2+
3+// Test-only accessors (export_test.go pattern): these observe listener
4+// internals that only this package's own tests need. They are kept out of
5+// the production build so a live server's diagnostics or capacity never
6+// depend on a test-only accessor.
7+
8+// refusal reports the most recent accept refusal, or nil if the listener has
9+// refused nothing yet.
10+func (l *listener) refusal() error {
11+ l.mu.Lock()
12+ defer l.mu.Unlock()
13+ return l.lastRefuse
14+}
15+
16+// inFlight reports the count of accepted carriages still completing their
17+// handshake.
18+func (l *listener) inFlight() int {
19+ l.mu.Lock()
20+ defer l.mu.Unlock()
21+ return len(l.pending)
22+}
23+
24+// liveSessions reports the count of admitted, still-open sessions.
25+func (l *listener) liveSessions() int {
26+ l.mu.Lock()
27+ defer l.mu.Unlock()
28+ return len(l.sessions)
29+}
30+
31+// DefaultSocketPath returns the broker endpoint path under SocketFileName's
32+// convenience default runtime directory, for tests that need it without
33+// composing SocketPath(SocketDir()) themselves.
34+func DefaultSocketPath() string { return SocketPath(SocketDir()) }
M internal/adapters/brokeripc/server.go +0 -22
@@ -210,13 +210,6 @@ func (l *listener) confirmRefusal(err error) {
210210 l.mu.Unlock()
211211 }
212212
213-// refusal reports the most recent refused or failed admission.
214-func (l *listener) refusal() error {
215- l.mu.Lock()
216- defer l.mu.Unlock()
217- return l.lastRefuse
218-}
219-
220213 // admit completes the preamble and broker admission for one accepted carriage
221214 // and publishes the running session. Slot ownership transfers to the session on
222215 // success.
@@ -266,14 +259,6 @@ func (l *listener) closePending() {
266259 }
267260 }
268261
269-// inFlight reports how many accepted carriages are still completing preamble or
270-// admission.
271-func (l *listener) inFlight() int {
272- l.mu.Lock()
273- defer l.mu.Unlock()
274- return len(l.pending)
275-}
276-
277262 // classifyAdmissionFailure maps one preamble or authority failure. Once the
278263 // listener is closing, an accepted client that never reached admission was
279264 // dropped by the close, so the failure is reported as ErrListenerClosed. A
@@ -446,13 +431,6 @@ func (l *listener) forgetSession(session *serverSession) {
446431 l.mu.Unlock()
447432 }
448433
449-// liveSessions reports how many admitted sessions are still live.
450-func (l *listener) liveSessions() int {
451- l.mu.Lock()
452- defer l.mu.Unlock()
453- return len(l.sessions)
454-}
455-
456434 func (l *listener) isClosed() bool {
457435 if l == nil {
458436 return true
@@ -210,13 +210,6 @@ func (l *listener) confirmRefusal(err error) {
210 l.mu.Unlock()210 l.mu.Unlock()
211 }211 }
212 212
213-// refusal reports the most recent refused or failed admission.
214-func (l *listener) refusal() error {
215- l.mu.Lock()
216- defer l.mu.Unlock()
217- return l.lastRefuse
218-}
219-
220 // admit completes the preamble and broker admission for one accepted carriage213 // admit completes the preamble and broker admission for one accepted carriage
221 // and publishes the running session. Slot ownership transfers to the session on214 // and publishes the running session. Slot ownership transfers to the session on
222 // success.215 // success.
@@ -266,14 +259,6 @@ func (l *listener) closePending() {
266 }259 }
267 }260 }
268 261
269-// inFlight reports how many accepted carriages are still completing preamble or
270-// admission.
271-func (l *listener) inFlight() int {
272- l.mu.Lock()
273- defer l.mu.Unlock()
274- return len(l.pending)
275-}
276-
277 // classifyAdmissionFailure maps one preamble or authority failure. Once the262 // classifyAdmissionFailure maps one preamble or authority failure. Once the
278 // listener is closing, an accepted client that never reached admission was263 // listener is closing, an accepted client that never reached admission was
279 // dropped by the close, so the failure is reported as ErrListenerClosed. A264 // dropped by the close, so the failure is reported as ErrListenerClosed. A
@@ -446,13 +431,6 @@ func (l *listener) forgetSession(session *serverSession) {
446 l.mu.Unlock()431 l.mu.Unlock()
447 }432 }
448 433
449-// liveSessions reports how many admitted sessions are still live.
450-func (l *listener) liveSessions() int {
451- l.mu.Lock()
452- defer l.mu.Unlock()
453- return len(l.sessions)
454-}
455-
456 func (l *listener) isClosed() bool {434 func (l *listener) isClosed() bool {
457 if l == nil {435 if l == nil {
458 return true436 return true
M internal/adapters/brokeripc/socket.go +0 -5
@@ -23,8 +23,3 @@ func SocketPath(runtimeDir string) string {
2323 // well, so both endpoints share one owner-only directory while keeping
2424 // distinct socket names.
2525 func SocketDir() string { return ipc.SocketDir() }
26-
27-// DefaultSocketPath returns the broker endpoint path in the per-user runtime
28-// directory. Callers that need a private temporary endpoint supply their own
29-// parent directory to SocketPath instead.
30-func DefaultSocketPath() string { return SocketPath(SocketDir()) }
@@ -23,8 +23,3 @@ func SocketPath(runtimeDir string) string {
23 // well, so both endpoints share one owner-only directory while keeping23 // well, so both endpoints share one owner-only directory while keeping
24 // distinct socket names.24 // distinct socket names.
25 func SocketDir() string { return ipc.SocketDir() }25 func SocketDir() string { return ipc.SocketDir() }
26-
27-// DefaultSocketPath returns the broker endpoint path in the per-user runtime
28-// directory. Callers that need a private temporary endpoint supply their own
29-// parent directory to SocketPath instead.
30-func DefaultSocketPath() string { return SocketPath(SocketDir()) }
A internal/adapters/brokerwire/export_test.go +10 -0
@@ -0,0 +1,10 @@
1+package brokerwire
2+
3+// WithMaxStagedBytes overrides the staged estimate ceiling. It exists only for
4+// tests that prove the bound without allocating the production-sized default;
5+// production never needs a non-default ceiling.
6+func WithMaxStagedBytes(n uint64) SnapshotOption {
7+ return func(a *SnapshotAssembler) {
8+ a.maxStagedBytes = n
9+ }
10+}
@@ -0,0 +1,10 @@
1+package brokerwire
2+
3+// WithMaxStagedBytes overrides the staged estimate ceiling. It exists only for
4+// tests that prove the bound without allocating the production-sized default;
5+// production never needs a non-default ceiling.
6+func WithMaxStagedBytes(n uint64) SnapshotOption {
7+ return func(a *SnapshotAssembler) {
8+ a.maxStagedBytes = n
9+ }
10+}
M internal/adapters/brokerwire/snapshot.go +0 -7
@@ -90,13 +90,6 @@ var (
9090 // to prove the bound without allocating 80 MiB; production uses defaults.
9191 type SnapshotOption func(*SnapshotAssembler)
9292
93-// WithMaxStagedBytes overrides the staged estimate ceiling.
94-func WithMaxStagedBytes(n uint64) SnapshotOption {
95- return func(a *SnapshotAssembler) {
96- a.maxStagedBytes = n
97- }
98-}
99-
10093 // WithGeneration pins the subscription generation this assembler belongs to,
10194 // so a part of any other generation is fenced from the first one. The zero
10295 // default adopts the generation from the first Begin.
@@ -90,13 +90,6 @@ var (
90 // to prove the bound without allocating 80 MiB; production uses defaults.90 // to prove the bound without allocating 80 MiB; production uses defaults.
91 type SnapshotOption func(*SnapshotAssembler)91 type SnapshotOption func(*SnapshotAssembler)
92 92
93-// WithMaxStagedBytes overrides the staged estimate ceiling.
94-func WithMaxStagedBytes(n uint64) SnapshotOption {
95- return func(a *SnapshotAssembler) {
96- a.maxStagedBytes = n
97- }
98-}
99-
100 // WithGeneration pins the subscription generation this assembler belongs to,93 // WithGeneration pins the subscription generation this assembler belongs to,
101 // so a part of any other generation is fenced from the first one. The zero94 // so a part of any other generation is fenced from the first one. The zero
102 // default adopts the generation from the first Begin.95 // default adopts the generation from the first Begin.
M internal/adapters/daemonmux/binding.go +0 -23
@@ -67,24 +67,6 @@ func NewServerBinding(identity ports.BrokerDaemonIdentity, incarnation ports.Bro
6767 func (b ServerBinding) Identity() ports.BrokerDaemonIdentity { return b.identity }
6868 func (b ServerBinding) Incarnation() ports.BrokerDaemonIncarnation { return b.incarnation }
6969
70-// Policy returns the sole entry's policy for legacy single-policy callers. It
71-// returns the zero value for a multi-entry authority; admission never uses it.
72-func (b ServerBinding) Policy() ports.BrokerPolicy {
73- if len(b.entries) == 1 {
74- return b.entries[0].Policy
75- }
76- return ports.BrokerPolicy{}
77-}
78-
79-// Origin returns the sole entry's locality for legacy single-policy callers. It
80-// returns the unknown origin for a multi-entry authority.
81-func (b ServerBinding) Origin() ports.SessionConnectionOrigin {
82- if len(b.entries) == 1 {
83- return b.entries[0].Origin
84- }
85- return ports.SessionOriginUnknown
86-}
87-
8870 func (b ServerBinding) Validate() error {
8971 if b.identity.Validate() != nil || b.incarnation.Validate() != nil || len(b.entries) == 0 {
9072 return ErrInvalidBinding
@@ -116,8 +98,3 @@ func (b ServerBinding) Accepted(policy ports.BrokerPolicy) (ServerPolicyAdmissio
11698 }
11799 return ServerPolicyAdmission{}, false
118100 }
119-
120-func (b ServerBinding) Accepts(policy ports.BrokerPolicy) bool {
121- _, ok := b.Accepted(policy)
122- return ok
123-}
@@ -67,24 +67,6 @@ func NewServerBinding(identity ports.BrokerDaemonIdentity, incarnation ports.Bro
67 func (b ServerBinding) Identity() ports.BrokerDaemonIdentity { return b.identity }67 func (b ServerBinding) Identity() ports.BrokerDaemonIdentity { return b.identity }
68 func (b ServerBinding) Incarnation() ports.BrokerDaemonIncarnation { return b.incarnation }68 func (b ServerBinding) Incarnation() ports.BrokerDaemonIncarnation { return b.incarnation }
69 69
70-// Policy returns the sole entry's policy for legacy single-policy callers. It
71-// returns the zero value for a multi-entry authority; admission never uses it.
72-func (b ServerBinding) Policy() ports.BrokerPolicy {
73- if len(b.entries) == 1 {
74- return b.entries[0].Policy
75- }
76- return ports.BrokerPolicy{}
77-}
78-
79-// Origin returns the sole entry's locality for legacy single-policy callers. It
80-// returns the unknown origin for a multi-entry authority.
81-func (b ServerBinding) Origin() ports.SessionConnectionOrigin {
82- if len(b.entries) == 1 {
83- return b.entries[0].Origin
84- }
85- return ports.SessionOriginUnknown
86-}
87-
88 func (b ServerBinding) Validate() error {70 func (b ServerBinding) Validate() error {
89 if b.identity.Validate() != nil || b.incarnation.Validate() != nil || len(b.entries) == 0 {71 if b.identity.Validate() != nil || b.incarnation.Validate() != nil || len(b.entries) == 0 {
90 return ErrInvalidBinding72 return ErrInvalidBinding
@@ -116,8 +98,3 @@ func (b ServerBinding) Accepted(policy ports.BrokerPolicy) (ServerPolicyAdmissio
116 }98 }
117 return ServerPolicyAdmission{}, false99 return ServerPolicyAdmission{}, false
118 }100 }
119-
120-func (b ServerBinding) Accepts(policy ports.BrokerPolicy) bool {
121- _, ok := b.Accepted(policy)
122- return ok
123-}
M internal/adapters/daemonmux/engine_state.go +0 -8
@@ -234,14 +234,6 @@ type StreamEngine struct {
234234 policy ports.BrokerPolicy
235235 }
236236
237-// NewStreamEngine returns an open engine bound to one physical connection
238-// under the default MuxCeilings: the full envelope ceiling, the maximum
239-// chunk, the maximum stream count (pending-plus-live), and the maximum
240-// aggregate buffered bytes.
241-func NewStreamEngine() *StreamEngine {
242- return newStreamEngine(DefaultMuxCeilings())
243-}
244-
245237 // NewStreamEngineWithCeilings returns an open engine bound to one physical
246238 // connection whose admission, chunk, and aggregate budgets enforce the given
247239 // negotiated MuxCeilings. The value is validated and copied, so the ceilings
@@ -234,14 +234,6 @@ type StreamEngine struct {
234 policy ports.BrokerPolicy234 policy ports.BrokerPolicy
235 }235 }
236 236
237-// NewStreamEngine returns an open engine bound to one physical connection
238-// under the default MuxCeilings: the full envelope ceiling, the maximum
239-// chunk, the maximum stream count (pending-plus-live), and the maximum
240-// aggregate buffered bytes.
241-func NewStreamEngine() *StreamEngine {
242- return newStreamEngine(DefaultMuxCeilings())
243-}
244-
245 // NewStreamEngineWithCeilings returns an open engine bound to one physical237 // NewStreamEngineWithCeilings returns an open engine bound to one physical
246 // connection whose admission, chunk, and aggregate budgets enforce the given238 // connection whose admission, chunk, and aggregate budgets enforce the given
247 // negotiated MuxCeilings. The value is validated and copied, so the ceilings239 // negotiated MuxCeilings. The value is validated and copied, so the ceilings
A internal/adapters/daemonmux/export_test.go +87 -0
@@ -0,0 +1,87 @@
1+package daemonmux
2+
3+import (
4+ "google.golang.org/protobuf/proto"
5+
6+ "github.com/bnema/vev/internal/adapters/clock"
7+ "github.com/bnema/vev/internal/ports"
8+ "github.com/bnema/vev/internal/protocol"
9+ "github.com/bnema/vev/internal/protocol/wire"
10+)
11+
12+// Test-only convenience constructors and accessors (export_test.go pattern):
13+// production always supplies its own explicit ceilings, clock, or budget
14+// (see pump.go, supervisor.go), so these package-default shorthands only
15+// exist for tests that don't care about a non-default value.
16+
17+// Accepts reports whether policy matches an admitted entry, for tests over the
18+// legacy single-policy binding shape.
19+func (b ServerBinding) Accepts(policy ports.BrokerPolicy) bool {
20+ _, ok := b.Accepted(policy)
21+ return ok
22+}
23+
24+// Policy returns the sole entry's policy for legacy single-policy test
25+// fixtures. It returns the zero value for a multi-entry authority.
26+func (b ServerBinding) Policy() ports.BrokerPolicy {
27+ if len(b.entries) == 1 {
28+ return b.entries[0].Policy
29+ }
30+ return ports.BrokerPolicy{}
31+}
32+
33+// Origin returns the sole entry's locality for legacy single-policy test
34+// fixtures. It returns the unknown origin for a multi-entry authority.
35+func (b ServerBinding) Origin() ports.SessionConnectionOrigin {
36+ if len(b.entries) == 1 {
37+ return b.entries[0].Origin
38+ }
39+ return ports.SessionOriginUnknown
40+}
41+
42+// NewStreamEngine returns an open engine bound to one physical connection
43+// under the package default MuxCeilings.
44+func NewStreamEngine() *StreamEngine {
45+ return newStreamEngine(DefaultMuxCeilings())
46+}
47+
48+// NewListener returns a listener under the package default handshake budget,
49+// accept-queue bound, and wall clock.
50+func NewListener(pump *Pump, accepted ServerPolicyAdmission) (*Listener, error) {
51+ return newListener(pump, protocol.HandshakeTimeout, MaxAcceptQueue, clock.New(), accepted)
52+}
53+
54+// NewScheduler returns an empty outbound scheduler under the package default
55+// MuxCeilings.
56+func NewScheduler(engine *StreamEngine) *Scheduler {
57+ return newScheduler(engine, DefaultMuxCeilings())
58+}
59+
60+// Children reports the count of currently owned physical children.
61+func (s *ServerSupervisor) Children() int {
62+ if s == nil {
63+ return 0
64+ }
65+ s.mu.Lock()
66+ defer s.mu.Unlock()
67+ return len(s.children)
68+}
69+
70+// DecodePreambleRequestBytes strictly decodes one serialized daemonmux client
71+// preamble under the 4 KiB bound, the strict scan, the generated unmarshal,
72+// and DecodePreambleRequest's semantic checks. Production decodes a preamble
73+// inline through the same steps (see handshake.go); this composed helper
74+// exists for tests and the package fuzz target.
75+func DecodePreambleRequestBytes(payload []byte) (MuxPreambleRequest, error) {
76+ if err := CheckPreambleSize(payload); err != nil {
77+ return MuxPreambleRequest{}, err
78+ }
79+ message := &wire.MuxPreambleRequest{}
80+ if err := wire.ScanEnvelope(message, payload); err != nil {
81+ return MuxPreambleRequest{}, err
82+ }
83+ if err := (proto.UnmarshalOptions{DiscardUnknown: false}.Unmarshal(payload, message)); err != nil {
84+ return MuxPreambleRequest{}, err
85+ }
86+ return DecodePreambleRequest(message)
87+}
@@ -0,0 +1,87 @@
1+package daemonmux
2+
3+import (
4+ "google.golang.org/protobuf/proto"
5+
6+ "github.com/bnema/vev/internal/adapters/clock"
7+ "github.com/bnema/vev/internal/ports"
8+ "github.com/bnema/vev/internal/protocol"
9+ "github.com/bnema/vev/internal/protocol/wire"
10+)
11+
12+// Test-only convenience constructors and accessors (export_test.go pattern):
13+// production always supplies its own explicit ceilings, clock, or budget
14+// (see pump.go, supervisor.go), so these package-default shorthands only
15+// exist for tests that don't care about a non-default value.
16+
17+// Accepts reports whether policy matches an admitted entry, for tests over the
18+// legacy single-policy binding shape.
19+func (b ServerBinding) Accepts(policy ports.BrokerPolicy) bool {
20+ _, ok := b.Accepted(policy)
21+ return ok
22+}
23+
24+// Policy returns the sole entry's policy for legacy single-policy test
25+// fixtures. It returns the zero value for a multi-entry authority.
26+func (b ServerBinding) Policy() ports.BrokerPolicy {
27+ if len(b.entries) == 1 {
28+ return b.entries[0].Policy
29+ }
30+ return ports.BrokerPolicy{}
31+}
32+
33+// Origin returns the sole entry's locality for legacy single-policy test
34+// fixtures. It returns the unknown origin for a multi-entry authority.
35+func (b ServerBinding) Origin() ports.SessionConnectionOrigin {
36+ if len(b.entries) == 1 {
37+ return b.entries[0].Origin
38+ }
39+ return ports.SessionOriginUnknown
40+}
41+
42+// NewStreamEngine returns an open engine bound to one physical connection
43+// under the package default MuxCeilings.
44+func NewStreamEngine() *StreamEngine {
45+ return newStreamEngine(DefaultMuxCeilings())
46+}
47+
48+// NewListener returns a listener under the package default handshake budget,
49+// accept-queue bound, and wall clock.
50+func NewListener(pump *Pump, accepted ServerPolicyAdmission) (*Listener, error) {
51+ return newListener(pump, protocol.HandshakeTimeout, MaxAcceptQueue, clock.New(), accepted)
52+}
53+
54+// NewScheduler returns an empty outbound scheduler under the package default
55+// MuxCeilings.
56+func NewScheduler(engine *StreamEngine) *Scheduler {
57+ return newScheduler(engine, DefaultMuxCeilings())
58+}
59+
60+// Children reports the count of currently owned physical children.
61+func (s *ServerSupervisor) Children() int {
62+ if s == nil {
63+ return 0
64+ }
65+ s.mu.Lock()
66+ defer s.mu.Unlock()
67+ return len(s.children)
68+}
69+
70+// DecodePreambleRequestBytes strictly decodes one serialized daemonmux client
71+// preamble under the 4 KiB bound, the strict scan, the generated unmarshal,
72+// and DecodePreambleRequest's semantic checks. Production decodes a preamble
73+// inline through the same steps (see handshake.go); this composed helper
74+// exists for tests and the package fuzz target.
75+func DecodePreambleRequestBytes(payload []byte) (MuxPreambleRequest, error) {
76+ if err := CheckPreambleSize(payload); err != nil {
77+ return MuxPreambleRequest{}, err
78+ }
79+ message := &wire.MuxPreambleRequest{}
80+ if err := wire.ScanEnvelope(message, payload); err != nil {
81+ return MuxPreambleRequest{}, err
82+ }
83+ if err := (proto.UnmarshalOptions{DiscardUnknown: false}.Unmarshal(payload, message)); err != nil {
84+ return MuxPreambleRequest{}, err
85+ }
86+ return DecodePreambleRequest(message)
87+}
M internal/adapters/daemonmux/listener.go +0 -17
@@ -185,23 +185,6 @@ type admittedStream struct {
185185 settled bool
186186 }
187187
188-// NewListener returns a listener over one daemon-side pump under the package
189-// policy: the accepted absolute handshake deadline budget is
190-// protocol.HandshakeTimeout, and the accept queue is bounded by
191-// MaxAcceptQueue. accepted is the provisioned carriage shape the physical
192-// handshake admitted: its exact policy and its locality origin. Every admitted
193-// session connection is stamped with it. The pump must decode broker-to-daemon
194-// frames (Inbound == DirectionClient) and emit daemon-to-broker frames, as the
195-// daemon side of a pooled physical connection does; a nil, engine-less, or
196-// broker-side pump, or an invalid accepted entry, is refused. The pump must not
197-// have started: the listener registers the pump's admission observer, and a
198-// pump that already started refuses the registration with
199-// ErrAdmissionObserverLate (wrapped in ErrListenerConfig), because an observer
200-// registered after Start could miss an already-admitted Open.
201-func NewListener(pump *Pump, accepted ServerPolicyAdmission) (*Listener, error) {
202- return newListener(pump, protocol.HandshakeTimeout, MaxAcceptQueue, clock.New(), accepted)
203-}
204-
205188 // newListener builds a listener from explicit policy. budget is the handshake
206189 // deadline budget each stream receives at admission, limit is the accept-queue
207190 // bound, clock supplies the admission time and the deadline timer, and accepted
@@ -185,23 +185,6 @@ type admittedStream struct {
185 settled bool185 settled bool
186 }186 }
187 187
188-// NewListener returns a listener over one daemon-side pump under the package
189-// policy: the accepted absolute handshake deadline budget is
190-// protocol.HandshakeTimeout, and the accept queue is bounded by
191-// MaxAcceptQueue. accepted is the provisioned carriage shape the physical
192-// handshake admitted: its exact policy and its locality origin. Every admitted
193-// session connection is stamped with it. The pump must decode broker-to-daemon
194-// frames (Inbound == DirectionClient) and emit daemon-to-broker frames, as the
195-// daemon side of a pooled physical connection does; a nil, engine-less, or
196-// broker-side pump, or an invalid accepted entry, is refused. The pump must not
197-// have started: the listener registers the pump's admission observer, and a
198-// pump that already started refuses the registration with
199-// ErrAdmissionObserverLate (wrapped in ErrListenerConfig), because an observer
200-// registered after Start could miss an already-admitted Open.
201-func NewListener(pump *Pump, accepted ServerPolicyAdmission) (*Listener, error) {
202- return newListener(pump, protocol.HandshakeTimeout, MaxAcceptQueue, clock.New(), accepted)
203-}
204-
205 // newListener builds a listener from explicit policy. budget is the handshake188 // newListener builds a listener from explicit policy. budget is the handshake
206 // deadline budget each stream receives at admission, limit is the accept-queue189 // deadline budget each stream receives at admission, limit is the accept-queue
207 // bound, clock supplies the admission time and the deadline timer, and accepted190 // bound, clock supplies the admission time and the deadline timer, and accepted
M internal/adapters/daemonmux/preamble.go +0 -23
@@ -328,29 +328,6 @@ func ValidatePreambleResponseAgainstOffer(response MuxPreambleResponse, offered
328328 return nil
329329 }
330330
331-// DecodePreambleRequestBytes strictly decodes one serialized daemonmux client
332-// preamble. The 4 KiB bound is enforced before allocation, wire.ScanEnvelope
333-// is the shared strict scanner that rejects unknown fields, duplicate singular
334-// or oneof fields, trailing bytes, truncation, and empty payloads before
335-// generated unmarshal, proto.UnmarshalOptions{DiscardUnknown: false} runs next,
336-// and DecodePreambleRequest applies the semantic checks. A carriage failure
337-// returns the precise wire sentinel (wire.ErrScanUnknown, wire.ErrScanDuplicate,
338-// wire.ErrScanTrailing, wire.ErrScanTruncated, ...); an oversize payload or a
339-// semantic failure returns ErrPreambleRejected.
340-func DecodePreambleRequestBytes(payload []byte) (MuxPreambleRequest, error) {
341- if err := CheckPreambleSize(payload); err != nil {
342- return MuxPreambleRequest{}, err
343- }
344- message := &wire.MuxPreambleRequest{}
345- if err := wire.ScanEnvelope(message, payload); err != nil {
346- return MuxPreambleRequest{}, err
347- }
348- if err := (proto.UnmarshalOptions{DiscardUnknown: false}.Unmarshal(payload, message)); err != nil {
349- return MuxPreambleRequest{}, err
350- }
351- return DecodePreambleRequest(message)
352-}
353-
354331 // DecodePreambleResponseBytes strictly decodes one serialized daemonmux server
355332 // preamble under the same 4 KiB bound, strict scan, generated unmarshal, and
356333 // semantic decode as DecodePreambleRequestBytes. Carriage failures return the
@@ -328,29 +328,6 @@ func ValidatePreambleResponseAgainstOffer(response MuxPreambleResponse, offered
328 return nil328 return nil
329 }329 }
330 330
331-// DecodePreambleRequestBytes strictly decodes one serialized daemonmux client
332-// preamble. The 4 KiB bound is enforced before allocation, wire.ScanEnvelope
333-// is the shared strict scanner that rejects unknown fields, duplicate singular
334-// or oneof fields, trailing bytes, truncation, and empty payloads before
335-// generated unmarshal, proto.UnmarshalOptions{DiscardUnknown: false} runs next,
336-// and DecodePreambleRequest applies the semantic checks. A carriage failure
337-// returns the precise wire sentinel (wire.ErrScanUnknown, wire.ErrScanDuplicate,
338-// wire.ErrScanTrailing, wire.ErrScanTruncated, ...); an oversize payload or a
339-// semantic failure returns ErrPreambleRejected.
340-func DecodePreambleRequestBytes(payload []byte) (MuxPreambleRequest, error) {
341- if err := CheckPreambleSize(payload); err != nil {
342- return MuxPreambleRequest{}, err
343- }
344- message := &wire.MuxPreambleRequest{}
345- if err := wire.ScanEnvelope(message, payload); err != nil {
346- return MuxPreambleRequest{}, err
347- }
348- if err := (proto.UnmarshalOptions{DiscardUnknown: false}.Unmarshal(payload, message)); err != nil {
349- return MuxPreambleRequest{}, err
350- }
351- return DecodePreambleRequest(message)
352-}
353-
354 // DecodePreambleResponseBytes strictly decodes one serialized daemonmux server331 // DecodePreambleResponseBytes strictly decodes one serialized daemonmux server
355 // preamble under the same 4 KiB bound, strict scan, generated unmarshal, and332 // preamble under the same 4 KiB bound, strict scan, generated unmarshal, and
356 // semantic decode as DecodePreambleRequestBytes. Carriage failures return the333 // semantic decode as DecodePreambleRequestBytes. Carriage failures return the
M internal/adapters/daemonmux/scheduler.go +0 -10
@@ -258,16 +258,6 @@ type Scheduler struct {
258258 quantum int64
259259 }
260260
261-// NewScheduler returns an empty outbound scheduler under the default
262-// MuxCeilings. When engine is non-nil the scheduler enqueues a frame only for
263-// an opening or open stream the engine has admitted; when it is nil, the
264-// caller owns admission and any nonzero physical identity is accepted. The
265-// engine does not need to share the scheduler's ceilings: the scheduler's
266-// envelope, chunk, and aggregate budgets come from its own copy.
267-func NewScheduler(engine *StreamEngine) *Scheduler {
268- return newScheduler(engine, DefaultMuxCeilings())
269-}
270-
271261 // NewSchedulerWithCeilings returns an empty outbound scheduler whose envelope,
272262 // chunk, and aggregate budgets enforce the given negotiated MuxCeilings. The
273263 // value is validated and copied, so the ceilings a scheduler enforces are
@@ -258,16 +258,6 @@ type Scheduler struct {
258 quantum int64258 quantum int64
259 }259 }
260 260
261-// NewScheduler returns an empty outbound scheduler under the default
262-// MuxCeilings. When engine is non-nil the scheduler enqueues a frame only for
263-// an opening or open stream the engine has admitted; when it is nil, the
264-// caller owns admission and any nonzero physical identity is accepted. The
265-// engine does not need to share the scheduler's ceilings: the scheduler's
266-// envelope, chunk, and aggregate budgets come from its own copy.
267-func NewScheduler(engine *StreamEngine) *Scheduler {
268- return newScheduler(engine, DefaultMuxCeilings())
269-}
270-
271 // NewSchedulerWithCeilings returns an empty outbound scheduler whose envelope,261 // NewSchedulerWithCeilings returns an empty outbound scheduler whose envelope,
272 // chunk, and aggregate budgets enforce the given negotiated MuxCeilings. The262 // chunk, and aggregate budgets enforce the given negotiated MuxCeilings. The
273 // value is validated and copied, so the ceilings a scheduler enforces are263 // value is validated and copied, so the ceilings a scheduler enforces are
M internal/adapters/daemonmux/supervisor.go +0 -12
@@ -201,18 +201,6 @@ func (s *ServerSupervisor) Adopt(ctx context.Context, raw RawFramedTransport) er
201201 return nil
202202 }
203203
204-// Children reports how many physical connections the supervisor currently
205-// owns. It is a diagnostic accessor; a lost child disappears once its reaper
206-// has closed it.
207-func (s *ServerSupervisor) Children() int {
208- if s == nil {
209- return 0
210- }
211- s.mu.Lock()
212- defer s.mu.Unlock()
213- return len(s.children)
214-}
215-
216204 // Close stops accepting physical carriage and tears every owned child down:
217205 // it deregisters and closes each child - listener, pump, and carrier together -
218206 // and joins every reaper. Close is idempotent and concurrent-safe. It leaves
@@ -201,18 +201,6 @@ func (s *ServerSupervisor) Adopt(ctx context.Context, raw RawFramedTransport) er
201 return nil201 return nil
202 }202 }
203 203
204-// Children reports how many physical connections the supervisor currently
205-// owns. It is a diagnostic accessor; a lost child disappears once its reaper
206-// has closed it.
207-func (s *ServerSupervisor) Children() int {
208- if s == nil {
209- return 0
210- }
211- s.mu.Lock()
212- defer s.mu.Unlock()
213- return len(s.children)
214-}
215-
216 // Close stops accepting physical carriage and tears every owned child down:204 // Close stops accepting physical carriage and tears every owned child down:
217 // it deregisters and closes each child - listener, pump, and carrier together -205 // it deregisters and closes each child - listener, pump, and carrier together -
218 // and joins every reaper. Close is idempotent and concurrent-safe. It leaves206 // and joins every reaper. Close is idempotent and concurrent-safe. It leaves
A internal/adapters/ipc/export_test.go +12 -0
@@ -0,0 +1,12 @@
1+package ipc
2+
3+import "path/filepath"
4+
5+// SocketPath returns the daemon socket path inside runtimeDir. It has no
6+// current caller: brokeripc.SocketPath (a distinct, unrelated function) is
7+// the one every production and test call site actually uses for the daemon's
8+// own socket path. This is kept as a test-only convenience the package name
9+// implies is available.
10+func SocketPath(runtimeDir string) string {
11+ return filepath.Join(runtimeDir, socketFileName)
12+}
@@ -0,0 +1,12 @@
1+package ipc
2+
3+import "path/filepath"
4+
5+// SocketPath returns the daemon socket path inside runtimeDir. It has no
6+// current caller: brokeripc.SocketPath (a distinct, unrelated function) is
7+// the one every production and test call site actually uses for the daemon's
8+// own socket path. This is kept as a test-only convenience the package name
9+// implies is available.
10+func SocketPath(runtimeDir string) string {
11+ return filepath.Join(runtimeDir, socketFileName)
12+}
M internal/adapters/ipc/listener.go +0 -5
@@ -17,11 +17,6 @@ import (
1717 // its socket directory.
1818 const socketFileName = "daemon.sock"
1919
20-// SocketPath returns the daemon socket path inside runtimeDir.
21-func SocketPath(runtimeDir string) string {
22- return filepath.Join(runtimeDir, socketFileName)
23-}
24-
2520 // ErrDaemonRunning is returned by Listen when the socket path is already
2621 // bound by a live daemon (a dial-probe against it succeeded).
2722 var ErrDaemonRunning = errors.New("ipc: a daemon is already listening on this socket")
@@ -17,11 +17,6 @@ import (
17 // its socket directory.17 // its socket directory.
18 const socketFileName = "daemon.sock"18 const socketFileName = "daemon.sock"
19 19
20-// SocketPath returns the daemon socket path inside runtimeDir.
21-func SocketPath(runtimeDir string) string {
22- return filepath.Join(runtimeDir, socketFileName)
23-}
24-
25 // ErrDaemonRunning is returned by Listen when the socket path is already20 // ErrDaemonRunning is returned by Listen when the socket path is already
26 // bound by a live daemon (a dial-probe against it succeeded).21 // bound by a live daemon (a dial-probe against it succeeded).
27 var ErrDaemonRunning = errors.New("ipc: a daemon is already listening on this socket")22 var ErrDaemonRunning = errors.New("ipc: a daemon is already listening on this socket")
M internal/adapters/quic/endpoint.go +0 -6
@@ -47,12 +47,6 @@ type dialOptions struct {
4747 observer ports.SerializedRuntimeObserver
4848 }
4949
50-// WithRuntimeObserver enables process-local adapter marks on the dialed
51-// transport. A nil observer leaves observation disabled.
52-func WithRuntimeObserver(observer ports.SerializedRuntimeObserver) Option {
53- return func(opts *dialOptions) { opts.observer = observer }
54-}
55-
5650 // DialConfig carries the dial parameters. Fingerprint is the exact
5751 // SHA-256 of the server certificate (32 bytes). Timeout bounds the whole
5852 // dial: address resolution, every candidate connection, stream open, and
@@ -47,12 +47,6 @@ type dialOptions struct {
47 observer ports.SerializedRuntimeObserver47 observer ports.SerializedRuntimeObserver
48 }48 }
49 49
50-// WithRuntimeObserver enables process-local adapter marks on the dialed
51-// transport. A nil observer leaves observation disabled.
52-func WithRuntimeObserver(observer ports.SerializedRuntimeObserver) Option {
53- return func(opts *dialOptions) { opts.observer = observer }
54-}
55-
56 // DialConfig carries the dial parameters. Fingerprint is the exact50 // DialConfig carries the dial parameters. Fingerprint is the exact
57 // SHA-256 of the server certificate (32 bytes). Timeout bounds the whole51 // SHA-256 of the server certificate (32 bytes). Timeout bounds the whole
58 // dial: address resolution, every candidate connection, stream open, and52 // dial: address resolution, every candidate connection, stream open, and
A internal/adapters/quic/export_test.go +10 -0
@@ -0,0 +1,10 @@
1+package quic
2+
3+import "github.com/bnema/vev/internal/ports"
4+
5+// WithRuntimeObserver enables process-local adapter marks on the dialed
6+// transport, for tests that assert on those marks. Production never needs
7+// runtime observation on a QUIC dial.
8+func WithRuntimeObserver(observer ports.SerializedRuntimeObserver) Option {
9+ return func(opts *dialOptions) { opts.observer = observer }
10+}
@@ -0,0 +1,10 @@
1+package quic
2+
3+import "github.com/bnema/vev/internal/ports"
4+
5+// WithRuntimeObserver enables process-local adapter marks on the dialed
6+// transport, for tests that assert on those marks. Production never needs
7+// runtime observation on a QUIC dial.
8+func WithRuntimeObserver(observer ports.SerializedRuntimeObserver) Option {
9+ return func(opts *dialOptions) { opts.observer = observer }
10+}
A internal/usecase/broker/export_test.go +67 -0
@@ -0,0 +1,67 @@
1+package broker
2+
3+import (
4+ "errors"
5+ "fmt"
6+ "log/slog"
7+
8+ "github.com/bnema/vev/internal/ports"
9+)
10+
11+// NewRegistry restores a validated durable snapshot under a fresh broker
12+// epoch with the default RegistryConfig, delegating to the same
13+// NewRegistryWithConfig production composition uses. It is a test-only
14+// shorthand: production always selects an explicit membership/observation
15+// mode.
16+func NewRegistry(epoch ports.BrokerEpoch, store ports.BrokerHostStore, probe ports.BrokerHostProbe, clock ports.Clock, log *slog.Logger) (*Registry, error) {
17+ return NewRegistryWithConfig(epoch, store, probe, clock, log, RegistryConfig{})
18+}
19+
20+// setHosts is projection-only; it is private so callers cannot bypass durable
21+// membership and policy. Observation tests use it without claiming durability.
22+// An unchanged membership is a no-op: it neither republishes nor restarts an
23+// in-flight observation. An exhausted revision series is refused with
24+// ports.ErrBrokerRevisionExhausted before the projection changes.
25+// Removed registrations retire bounded tombstones so a stale completion can
26+// never revive them; replaced or re-added endpoints are fenced by the
27+// registry attempt token instead.
28+func (r *Registry) setHosts(records []ports.BrokerHostRecord) error {
29+ if len(records) > ports.BrokerMaxHosts {
30+ return fmt.Errorf("broker: %d host registrations exceed the limit of %d", len(records), ports.BrokerMaxHosts)
31+ }
32+ if len(records) > 0 && !r.observationDisabled && nilDependency(r.probe) {
33+ return errors.New("broker: remote hosts require a host probe")
34+ }
35+ next := make(map[string]ports.BrokerDaemonObservation, len(records))
36+ order := make([]string, 0, len(records))
37+ for rank, record := range records {
38+ if err := record.Registration.Validate(); err != nil {
39+ return err
40+ }
41+ if err := record.Policy.Validate(); err != nil {
42+ return err
43+ }
44+ registration := record.Registration
45+ if _, duplicate := next[registration.Endpoint]; duplicate {
46+ return errors.New("broker: duplicate host registration")
47+ }
48+ observation := stampHostAuthority(ports.BrokerDaemonObservation{}, record, rank)
49+ // Membership the registry cannot publish is a caller error, refused here
50+ // rather than left to freeze every later publication: an endpoint whose
51+ // derived display hint is empty after the display rule drops its runes
52+ // makes every snapshot carrying it invalid.
53+ if err := observation.Validate(); err != nil {
54+ return fmt.Errorf("broker: host record %q is not publishable: %w", registration.Endpoint, err)
55+ }
56+ next[registration.Endpoint] = observation
57+ order = append(order, registration.Endpoint)
58+ }
59+
60+ r.mu.Lock()
61+ defer r.mu.Unlock()
62+ if r.revisionExhaustedLocked() {
63+ return fmt.Errorf("broker: set hosts: %w", ports.ErrBrokerRevisionExhausted)
64+ }
65+ r.setHostsLocked(next, order)
66+ return nil
67+}
@@ -0,0 +1,67 @@
1+package broker
2+
3+import (
4+ "errors"
5+ "fmt"
6+ "log/slog"
7+
8+ "github.com/bnema/vev/internal/ports"
9+)
10+
11+// NewRegistry restores a validated durable snapshot under a fresh broker
12+// epoch with the default RegistryConfig, delegating to the same
13+// NewRegistryWithConfig production composition uses. It is a test-only
14+// shorthand: production always selects an explicit membership/observation
15+// mode.
16+func NewRegistry(epoch ports.BrokerEpoch, store ports.BrokerHostStore, probe ports.BrokerHostProbe, clock ports.Clock, log *slog.Logger) (*Registry, error) {
17+ return NewRegistryWithConfig(epoch, store, probe, clock, log, RegistryConfig{})
18+}
19+
20+// setHosts is projection-only; it is private so callers cannot bypass durable
21+// membership and policy. Observation tests use it without claiming durability.
22+// An unchanged membership is a no-op: it neither republishes nor restarts an
23+// in-flight observation. An exhausted revision series is refused with
24+// ports.ErrBrokerRevisionExhausted before the projection changes.
25+// Removed registrations retire bounded tombstones so a stale completion can
26+// never revive them; replaced or re-added endpoints are fenced by the
27+// registry attempt token instead.
28+func (r *Registry) setHosts(records []ports.BrokerHostRecord) error {
29+ if len(records) > ports.BrokerMaxHosts {
30+ return fmt.Errorf("broker: %d host registrations exceed the limit of %d", len(records), ports.BrokerMaxHosts)
31+ }
32+ if len(records) > 0 && !r.observationDisabled && nilDependency(r.probe) {
33+ return errors.New("broker: remote hosts require a host probe")
34+ }
35+ next := make(map[string]ports.BrokerDaemonObservation, len(records))
36+ order := make([]string, 0, len(records))
37+ for rank, record := range records {
38+ if err := record.Registration.Validate(); err != nil {
39+ return err
40+ }
41+ if err := record.Policy.Validate(); err != nil {
42+ return err
43+ }
44+ registration := record.Registration
45+ if _, duplicate := next[registration.Endpoint]; duplicate {
46+ return errors.New("broker: duplicate host registration")
47+ }
48+ observation := stampHostAuthority(ports.BrokerDaemonObservation{}, record, rank)
49+ // Membership the registry cannot publish is a caller error, refused here
50+ // rather than left to freeze every later publication: an endpoint whose
51+ // derived display hint is empty after the display rule drops its runes
52+ // makes every snapshot carrying it invalid.
53+ if err := observation.Validate(); err != nil {
54+ return fmt.Errorf("broker: host record %q is not publishable: %w", registration.Endpoint, err)
55+ }
56+ next[registration.Endpoint] = observation
57+ order = append(order, registration.Endpoint)
58+ }
59+
60+ r.mu.Lock()
61+ defer r.mu.Unlock()
62+ if r.revisionExhaustedLocked() {
63+ return fmt.Errorf("broker: set hosts: %w", ports.ErrBrokerRevisionExhausted)
64+ }
65+ r.setHostsLocked(next, order)
66+ return nil
67+}
M internal/usecase/broker/registry.go +0 -62
@@ -172,19 +172,6 @@ type Registry struct {
172172 demand int
173173 }
174174
175-// NewRegistry restores a validated durable snapshot under a fresh broker epoch.
176-// The store owns durable membership as well as the advisory snapshot, so
177-// BrokerHostStore is required explicitly: a store that cannot answer
178-// LoadHosts is not a broker host store, and an optional assertion would let a
179-// caller silently run the registry without authoritative membership. A nil
180-// store is refused by the dependency guard, including a typed nil hidden
181-// behind the interface. Until Run returns, callers must not mutate the store
182-// outside Registry.ReplaceHosts. The caller closes the store after the registry
183-// has stopped and flushed its advisory writer.
184-func NewRegistry(epoch ports.BrokerEpoch, store ports.BrokerHostStore, probe ports.BrokerHostProbe, clock ports.Clock, log *slog.Logger) (*Registry, error) {
185- return NewRegistryWithConfig(epoch, store, probe, clock, log, RegistryConfig{})
186-}
187-
188175 // NewRegistryWithConfig restores a validated durable snapshot under a fresh
189176 // broker epoch and selects the run mode from cfg. Observation-disabled mode
190177 // tolerates a nil probe because such a registry never probes, and a registry
@@ -759,55 +746,6 @@ func (r *Registry) ReplaceHosts(records []ports.BrokerHostRecord) error {
759746 return r.commitMembershipLocked(records)
760747 }
761748
762-// setHosts is projection-only; it is private so callers cannot bypass durable
763-// membership and policy. Observation tests use it without claiming durability.
764-// An unchanged membership is a no-op: it neither republishes nor restarts an
765-// in-flight observation. An exhausted revision series is refused with
766-// ports.ErrBrokerRevisionExhausted before the projection changes.
767-// Removed registrations retire bounded tombstones so a stale completion can
768-// never revive them; replaced or re-added endpoints are fenced by the
769-// registry attempt token instead.
770-func (r *Registry) setHosts(records []ports.BrokerHostRecord) error {
771- if len(records) > ports.BrokerMaxHosts {
772- return fmt.Errorf("broker: %d host registrations exceed the limit of %d", len(records), ports.BrokerMaxHosts)
773- }
774- if len(records) > 0 && !r.observationDisabled && nilDependency(r.probe) {
775- return errors.New("broker: remote hosts require a host probe")
776- }
777- next := make(map[string]ports.BrokerDaemonObservation, len(records))
778- order := make([]string, 0, len(records))
779- for rank, record := range records {
780- if err := record.Registration.Validate(); err != nil {
781- return err
782- }
783- if err := record.Policy.Validate(); err != nil {
784- return err
785- }
786- registration := record.Registration
787- if _, duplicate := next[registration.Endpoint]; duplicate {
788- return errors.New("broker: duplicate host registration")
789- }
790- observation := stampHostAuthority(ports.BrokerDaemonObservation{}, record, rank)
791- // Membership the registry cannot publish is a caller error, refused here
792- // rather than left to freeze every later publication: an endpoint whose
793- // derived display hint is empty after the display rule drops its runes
794- // makes every snapshot carrying it invalid.
795- if err := observation.Validate(); err != nil {
796- return fmt.Errorf("broker: host record %q is not publishable: %w", registration.Endpoint, err)
797- }
798- next[registration.Endpoint] = observation
799- order = append(order, registration.Endpoint)
800- }
801-
802- r.mu.Lock()
803- defer r.mu.Unlock()
804- if r.revisionExhaustedLocked() {
805- return fmt.Errorf("broker: set hosts: %w", ports.ErrBrokerRevisionExhausted)
806- }
807- r.setHostsLocked(next, order)
808- return nil
809-}
810-
811749 // setHostsLocked applies only a projection; the public path commits authority
812750 // first. The caller holds mu through both steps, fencing probe completions.
813751 // order is the registration order the projection was built from; it becomes
@@ -172,19 +172,6 @@ type Registry struct {
172 demand int172 demand int
173 }173 }
174 174
175-// NewRegistry restores a validated durable snapshot under a fresh broker epoch.
176-// The store owns durable membership as well as the advisory snapshot, so
177-// BrokerHostStore is required explicitly: a store that cannot answer
178-// LoadHosts is not a broker host store, and an optional assertion would let a
179-// caller silently run the registry without authoritative membership. A nil
180-// store is refused by the dependency guard, including a typed nil hidden
181-// behind the interface. Until Run returns, callers must not mutate the store
182-// outside Registry.ReplaceHosts. The caller closes the store after the registry
183-// has stopped and flushed its advisory writer.
184-func NewRegistry(epoch ports.BrokerEpoch, store ports.BrokerHostStore, probe ports.BrokerHostProbe, clock ports.Clock, log *slog.Logger) (*Registry, error) {
185- return NewRegistryWithConfig(epoch, store, probe, clock, log, RegistryConfig{})
186-}
187-
188 // NewRegistryWithConfig restores a validated durable snapshot under a fresh175 // NewRegistryWithConfig restores a validated durable snapshot under a fresh
189 // broker epoch and selects the run mode from cfg. Observation-disabled mode176 // broker epoch and selects the run mode from cfg. Observation-disabled mode
190 // tolerates a nil probe because such a registry never probes, and a registry177 // tolerates a nil probe because such a registry never probes, and a registry
@@ -759,55 +746,6 @@ func (r *Registry) ReplaceHosts(records []ports.BrokerHostRecord) error {
759 return r.commitMembershipLocked(records)746 return r.commitMembershipLocked(records)
760 }747 }
761 748
762-// setHosts is projection-only; it is private so callers cannot bypass durable
763-// membership and policy. Observation tests use it without claiming durability.
764-// An unchanged membership is a no-op: it neither republishes nor restarts an
765-// in-flight observation. An exhausted revision series is refused with
766-// ports.ErrBrokerRevisionExhausted before the projection changes.
767-// Removed registrations retire bounded tombstones so a stale completion can
768-// never revive them; replaced or re-added endpoints are fenced by the
769-// registry attempt token instead.
770-func (r *Registry) setHosts(records []ports.BrokerHostRecord) error {
771- if len(records) > ports.BrokerMaxHosts {
772- return fmt.Errorf("broker: %d host registrations exceed the limit of %d", len(records), ports.BrokerMaxHosts)
773- }
774- if len(records) > 0 && !r.observationDisabled && nilDependency(r.probe) {
775- return errors.New("broker: remote hosts require a host probe")
776- }
777- next := make(map[string]ports.BrokerDaemonObservation, len(records))
778- order := make([]string, 0, len(records))
779- for rank, record := range records {
780- if err := record.Registration.Validate(); err != nil {
781- return err
782- }
783- if err := record.Policy.Validate(); err != nil {
784- return err
785- }
786- registration := record.Registration
787- if _, duplicate := next[registration.Endpoint]; duplicate {
788- return errors.New("broker: duplicate host registration")
789- }
790- observation := stampHostAuthority(ports.BrokerDaemonObservation{}, record, rank)
791- // Membership the registry cannot publish is a caller error, refused here
792- // rather than left to freeze every later publication: an endpoint whose
793- // derived display hint is empty after the display rule drops its runes
794- // makes every snapshot carrying it invalid.
795- if err := observation.Validate(); err != nil {
796- return fmt.Errorf("broker: host record %q is not publishable: %w", registration.Endpoint, err)
797- }
798- next[registration.Endpoint] = observation
799- order = append(order, registration.Endpoint)
800- }
801-
802- r.mu.Lock()
803- defer r.mu.Unlock()
804- if r.revisionExhaustedLocked() {
805- return fmt.Errorf("broker: set hosts: %w", ports.ErrBrokerRevisionExhausted)
806- }
807- r.setHostsLocked(next, order)
808- return nil
809-}
810-
811 // setHostsLocked applies only a projection; the public path commits authority749 // setHostsLocked applies only a projection; the public path commits authority
812 // first. The caller holds mu through both steps, fencing probe completions.750 // first. The caller holds mu through both steps, fencing probe completions.
813 // order is the registration order the projection was built from; it becomes751 // order is the registration order the projection was built from; it becomes

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