detached ece7682

broker_observation.go RawHistoryFolder

1package ports
2
3import (
4	"errors"
5	"fmt"
6	"time"
7
8	"github.com/bnema/vev/internal/domain"
9	"github.com/bnema/vev/internal/protocol/catalogue"
10)
11
12// Broker-native daemon observation.
13//
14// BrokerDaemonObservation is the broker's own projection of one daemon, local
15// or remote, and the only one: no daemon monitors another. It keeps
16// configured authority separate from observed state:
17//
18//   - configured authority: Local flag, Endpoint, Registration, Policy, and
19//     the presentation hints DisplayOrigin and Rank. The registry stamps
20//     them from its own host records (or the local composition) and never
21//     trusts them from a probe.
22//   - observed identity: Identity and Incarnation are zero until a probe
23//     actually authenticated the daemon. Unknown identity is never invented.
24//   - observed capability: ProtocolVersion and Capabilities mirror what the
25//     daemon reported. ProtocolVersion is the daemon's own version and may
26//     differ from Policy.ProtocolVersion; that difference is exactly how an
27//     incompatible daemon is observable. Zero means unobserved.
28//   - observation state: Availability, Checking, timestamps, failure
29//     counters, and the exact session catalogue. Availability zero is
30//     invalid: an unobserved daemon carries RemoteAvailabilityUnknown.
31//
32// Observing a daemon never attaches to it and never starts a stopped one.
33
34const (
35	// BrokerMaxDaemonsPerSnapshot bounds one snapshot publication: the local
36	// daemon plus every configured remote host.
37	BrokerMaxDaemonsPerSnapshot = BrokerMaxHosts + 1
38	// BrokerMaxDisplayOriginBytes bounds the presentation origin hint.
39	BrokerMaxDisplayOriginBytes = 128
40)
41
42// BrokerDaemonObservation is one immutable daemon projection in one broker
43// snapshot. Nested slices are never mutated after publication.
44type BrokerDaemonObservation struct {
45	// Local marks the broker's own machine daemon. A local observation has
46	// no endpoint and no registration: the local daemon is not a configured
47	// host. At most one local observation exists per snapshot.
48	Local bool
49
50	// Configured authority (registry-stamped, never probe-supplied).
51	Endpoint      string // remote only; matches Registration.Endpoint
52	DisplayOrigin string
53	Rank          int
54	Registration  domain.RemoteRegistration
55	Policy        BrokerPolicy
56
57	// Observed identity/incarnation (zero until observed).
58	Identity    BrokerDaemonIdentity
59	Incarnation BrokerDaemonIncarnation
60
61	// Observed version/capability (zero until observed).
62	ProtocolVersion uint16
63	Capabilities    uint32
64
65	// Availability, checking, failure, and freshness state.
66	Availability        domain.RemoteAvailability
67	Checking            bool
68	LastAttempt         time.Time
69	LastSuccess         time.Time
70	NextDue             time.Time
71	ConsecutiveFailures uint
72	FailureEpisode      uint64
73	LastFailure         domain.RemoteFailure
74	InventoryKnown      bool
75
76	// Exact session catalogue observed from the daemon.
77	Sessions []catalogue.RemoteCatalogSession
78}
79
80// Validate enforces the closed projection rules.
81func (o BrokerDaemonObservation) Validate() error {
82	// Policy is required authority for every daemon, observed or not.
83	if err := o.Policy.Validate(); err != nil {
84		return fmt.Errorf("ports: broker daemon observation: %w", err)
85	}
86	if err := validateBrokerDisplayText(o.DisplayOrigin, BrokerMaxDisplayOriginBytes, "display origin"); err != nil {
87		return fmt.Errorf("ports: broker daemon observation: %w", err)
88	}
89	if o.DisplayOrigin == "" {
90		return errors.New("ports: broker daemon observation has no display origin")
91	}
92	if o.Local {
93		if o.Endpoint != "" || o.Registration != (domain.RemoteRegistration{}) {
94			return errors.New("ports: local daemon observation carries remote authority")
95		}
96	} else {
97		if err := domain.ValidateRemoteHostTarget(o.Endpoint); err != nil {
98			return fmt.Errorf("ports: broker daemon observation: %w", err)
99		}
100		if err := o.Registration.Validate(); err != nil {
101			return fmt.Errorf("ports: broker daemon observation: %w", err)
102		}
103		if o.Endpoint != o.Registration.Endpoint {
104			return errors.New("ports: broker daemon observation endpoint does not match registration")
105		}
106	}
107	if err := validateObservedDaemonState(o); err != nil {
108		return err
109	}
110	if o.Availability < domain.RemoteAvailabilityUnknown || o.Availability > domain.RemoteAvailabilityNoDaemon {
111		return errors.New("ports: broker daemon observation availability is out of range")
112	}
113	if o.LastFailure.Kind > domain.RemoteFailureInvalidResponse {
114		return errors.New("ports: broker daemon observation failure kind is out of range")
115	}
116	if len(o.Sessions) > BrokerMaxSessionsPerHost {
117		return errors.New("ports: broker daemon observation has too many sessions")
118	}
119	if len(o.Sessions) > 0 && !o.InventoryKnown {
120		return errors.New("ports: broker daemon observation carries sessions without known inventory")
121	}
122	return nil
123}
124
125// validateObservedDaemonState applies the observed-state rules shared by a live
126// publication and a durable projection. Observed identity is all-or-nothing and
127// never invented: identity and incarnation appear together, both validate, and
128// an observed daemon always carries the protocol version its identity was
129// authenticated with. The offline store, a restored durable record, and the
130// registry all apply this one rule, so a projection either layer accepts is
131// never published as an observation the wire refuses.
132func validateObservedDaemonState(observation BrokerDaemonObservation) error {
133	observed := observation.Identity != ""
134	if observed != !observation.Incarnation.IsZero() {
135		return errors.New("ports: broker daemon observation has partial identity")
136	}
137	if !observed {
138		return nil
139	}
140	if err := observation.Identity.Validate(); err != nil {
141		return err
142	}
143	if err := observation.Incarnation.Validate(); err != nil {
144		return err
145	}
146	if observation.ProtocolVersion == 0 {
147		return errors.New("ports: observed daemon identity carries no protocol version")
148	}
149	return nil
150}
151
152// Clone returns a defensive copy with an independent session slice. Each
153// session keeps the exact nil-ness of its tab list: an empty but present list
154// is catalogue-valid and must not become absent through a copy.
155func (o BrokerDaemonObservation) Clone() BrokerDaemonObservation {
156	out := o
157	out.Sessions = append([]catalogue.RemoteCatalogSession(nil), o.Sessions...)
158	for i := range out.Sessions {
159		if o.Sessions[i].Tabs == nil {
160			continue
161		}
162		out.Sessions[i].Tabs = append(make([]catalogue.RemoteCatalogTab, 0, len(o.Sessions[i].Tabs)), o.Sessions[i].Tabs...)
163	}
164	return out
165}

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