readiness.go RawHistoryFolder

1package sessionlock
2
3import "github.com/bnema/neferwl/internal/ports"
4
5// Readiness is pure readiness bookkeeping owned by one goroutine. Its zero
6// value is an empty, disengaged ledger. It must not be copied after first use;
7// it owns its inventory and never exposes mutable state.
8//
9// Readiness is not authentication or protocol policy. All acknowledgements
10// must be affirmative evidence from the respective owner, never an inference
11// from a failed operation.
12type Readiness struct {
13	generation ports.LockGeneration // high-water mark, retained after End
14	active     bool
15	backend    bool
16	capture    bool
17	outputs    map[ports.OutputInstance]lockOutputReadiness
18}
19
20type lockOutputReadiness struct {
21	registered bool
22	proof      ports.ProtectionKind
23}
24
25// Add registers a new output lifetime before or during a lock. Zero, duplicate
26// and retired IDs are rejected without changing state. A new output has no
27// proof, so adding it while engaged revokes Ready until it is protected. IDs
28// remain as tombstones after Remove to prevent accepting a replaced lifetime.
29func (r *Readiness) Add(instance ports.OutputInstance) bool {
30	if instance == 0 {
31		return false
32	}
33	if _, seen := r.outputs[instance]; seen {
34		return false
35	}
36	if r.outputs == nil {
37		r.outputs = make(map[ports.OutputInstance]lockOutputReadiness)
38	}
39	r.outputs[instance] = lockOutputReadiness{registered: true}
40	return true
41}
42
43// Remove retires a lifetime only when the caller has confirmed it is actually
44// disconnected or inactive. An error (including "gone") is not confirmation.
45// An inactive lifetime that may resume should instead remain registered with
46// an inactive-output proof. Unknown or already retired instances are rejected.
47func (r *Readiness) Remove(instance ports.OutputInstance) bool {
48	output, ok := r.outputs[instance]
49	if !ok || !output.registered {
50		return false
51	}
52	r.outputs[instance] = lockOutputReadiness{}
53	return true
54}
55
56// Begin snapshots the registered inventory into a new readiness round by
57// clearing every proof and both barriers. Registrations may subsequently
58// change. Generations must increase strictly, even across End; an invalid
59// Begin leaves the current round untouched. Exhaustion must not wrap to zero.
60func (r *Readiness) Begin(generation ports.LockGeneration) bool {
61	if generation == 0 || generation <= r.generation {
62		return false
63	}
64	r.generation, r.active = generation, true
65	r.backend, r.capture = false, false
66	for instance, output := range r.outputs {
67		output.proof = ""
68		r.outputs[instance] = output
69	}
70	return true
71}
72
73// End clears only the exact active round, retaining inventory and the
74// generation high-water mark. Late End calls cannot cancel a newer round, and
75// late proofs or barriers cannot reactivate an ended round.
76func (r *Readiness) End(generation ports.LockGeneration) bool {
77	if !r.accepts(generation) {
78		return false
79	}
80	r.active, r.backend, r.capture = false, false, false
81	for instance, output := range r.outputs {
82		output.proof = ""
83		r.outputs[instance] = output
84	}
85	return true
86}
87
88// Record accepts only affirmative protection evidence for a registered
89// lifetime in the exact active generation. Repeated valid proofs are harmless;
90// invalid evidence never overwrites an existing proof.
91func (r *Readiness) Record(proof ports.OutputProtection) bool {
92	if !r.accepts(proof.Generation) {
93		return false
94	}
95	switch proof.Kind {
96	case ports.ProtectionProtectedFrame, ports.ProtectionInactiveOutput:
97	default:
98		return false
99	}
100	output, ok := r.outputs[proof.Instance]
101	if !ok || !output.registered {
102		return false
103	}
104	output.proof = proof.Kind
105	r.outputs[proof.Instance] = output
106	return true
107}
108
109// Invalidate revokes a prior proof within the active epoch. A recovered or
110// resumed lifetime must provide fresh affirmative protection, never reuse its
111// earlier presentation. Unknown, retired and stale epochs have no effect.
112func (r *Readiness) Invalidate(generation ports.LockGeneration, instance ports.OutputInstance) bool {
113	if !r.accepts(generation) {
114		return false
115	}
116	output, ok := r.outputs[instance]
117	if !ok || !output.registered {
118		return false
119	}
120	output.proof = ""
121	r.outputs[instance] = output
122	return true
123}
124
125// BackendBarrier acknowledges authoritative output inventory, drainage of all
126// backend leases, and completed security handoff for the active generation.
127// This is required even with zero outputs; an empty local inventory is not an
128// acknowledgement. It does not acknowledge capture memory-copy completion.
129func (r *Readiness) BackendBarrier(generation ports.LockGeneration) bool {
130	if !r.accepts(generation) {
131		return false
132	}
133	r.backend = true
134	return true
135}
136
137// CaptureBarrier acknowledges that all admitted prelock memory-copy work,
138// including writes to capture FDs, has completed for the active generation.
139// Output protection and backend drainage cannot substitute for this barrier.
140func (r *Readiness) CaptureBarrier(generation ports.LockGeneration) bool {
141	if !r.accepts(generation) {
142		return false
143	}
144	r.capture = true
145	return true
146}
147
148// Ready requires an active round, both independent barriers, and affirmative
149// protection evidence for every currently registered output lifetime.
150func (r *Readiness) Ready() bool {
151	if !r.active || !r.backend || !r.capture {
152		return false
153	}
154	for _, output := range r.outputs {
155		if output.registered && output.proof == "" {
156			return false
157		}
158	}
159	return true
160}
161
162func (r *Readiness) accepts(generation ports.LockGeneration) bool {
163	return r.active && generation != 0 && generation == r.generation
164}

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 25

Tags 10

↑↓ moveenter openesc close