1package daemon
2
3import "github.com/bnema/vev/internal/ports"
4
5// attachmentCapability is the exact capability for one registered
6// Attachment Connection incarnation. It is invalid as soon as its Session
7// membership, generation, Transport incarnation, or render lease changes.
8type attachmentCapability struct {
9 sess *session
10 ac *attachedClient
11 generation uint64
12 transport transportSnapshot
13 lease *attachmentLease
14 // rebase marks a cross-session move whose first paint starts a dependency-
15 // free output chain after publication releases architecture locks.
16 rebase bool
17}
18
19func captureAttachmentCapability(entry *session, ac *attachedClient, tr ports.ServerConnection) attachmentCapability {
20 if entry == nil || entry.core() == nil || ac == nil || tr == nil {
21 return attachmentCapability{}
22 }
23 // Capture one concrete link incarnation before taking the session lock. The
24 // same snapshot is revalidated below; do not take a second snapshot and
25 // accidentally bind a replacement link.
26 transport := ac.transportSnapshot()
27 if transport.transport != tr {
28 return attachmentCapability{}
29 }
30 if ac.beforeAttachmentCapabilityValidation != nil {
31 ac.beforeAttachmentCapabilityValidation()
32 }
33 core := entry.core()
34 core.mu.Lock()
35 if !attachmentRegisteredLocked(entry, ac) || !ac.transportSnapshotCurrent(transport) {
36 core.mu.Unlock()
37 return attachmentCapability{}
38 }
39 generation := ac.lifecycle.generation.Load()
40 token := attachmentCapability{
41 sess: entry,
42 ac: ac,
43 generation: generation,
44 transport: transport,
45 }
46 if rc := core.coordinator.Load(); rc != nil {
47 token.lease = rc.attachmentLease(ac)
48 }
49 core.mu.Unlock()
50 if !token.current() {
51 return attachmentCapability{}
52 }
53 ac.lifecycle.installInitialCapability(token)
54 return token
55}
56
57func (s *session) captureAttachmentCapability(ac *attachedClient, tr ports.ServerConnection) attachmentCapability {
58 return captureAttachmentCapability(s, ac, tr)
59}
60
61// admitCurrentAttachmentCapabilityEffect re-admits the attachment's current
62// capability after an exact capability stopped being admissible. It re-reads the
63// attachment's current session and transport, so it never sends through a stale
64// capability or resurrects a detached transport. A detached attachment, or one
65// whose current session no longer registers this link, admits nothing.
66func admitCurrentAttachmentCapabilityEffect(capability attachmentCapability) (attachmentCapability, *attachmentEffect, bool) {
67 ac := capability.ac
68 if ac == nil {
69 return attachmentCapability{}, nil, false
70 }
71 sess := ac.currentAttachmentSession()
72 if sess == nil {
73 return attachmentCapability{}, nil, false
74 }
75 token, effect, admitted := ac.beginCurrentAttachmentEffect(sess, ac.transport())
76 if !admitted {
77 return attachmentCapability{}, nil, false
78 }
79 // The re-admitted link replaces the superseded capability's identity but
80 // inherits its post-publication paint semantics, so a recovered cross-session
81 // first paint still rebases the output chain on the current link.
82 if capability.rebase {
83 token.rebase = true
84 }
85 return token, effect, true
86}
87
88func (t attachmentCapability) sameIdentity(other attachmentCapability) bool {
89 return t.sess != nil && t.ac != nil && t.sess == other.sess && t.ac == other.ac &&
90 t.generation == other.generation && t.transport.transport == other.transport.transport &&
91 t.transport.incarnation == other.transport.incarnation && t.lease == other.lease
92}
93
94func (t attachmentCapability) matchesConnectionSnapshot(sess *session, ac *attachedClient, transport transportSnapshot) bool {
95 return t.sess == sess && t.ac == ac && ac != nil && t.transport == transport &&
96 ac.lifecycle.identityCurrent(t)
97}
98
99func (l *attachmentLifecycle) identityCurrent(capability attachmentCapability) bool {
100 return l != nil && capability.sess != nil && capability.ac != nil &&
101 capability.ac.lifecycle.generation.Load() == capability.generation &&
102 capability.ac.currentAttachmentSession() == capability.sess &&
103 capability.ac.transportSnapshotCurrent(capability.transport)
104}
105
106// current validates every immutable identity captured by the capability.
107// Membership is the sole authority; there is no owner, replacement, or
108// compatibility role.
109func (l *attachmentLifecycle) current(capability attachmentCapability) bool {
110 if !l.identityCurrent(capability) || !attachmentRegistered(capability.sess, capability.ac) {
111 return false
112 }
113 if capability.lease == nil {
114 return true
115 }
116 rc := capability.sess.core().coordinator.Load()
117 return rc != nil && capability.lease.attachment == capability.ac && rc.leaseCurrent(capability.lease, false)
118}
119
120func (t attachmentCapability) current() bool {
121 return t.ac != nil && t.ac.lifecycle.current(t)
122}
123
124func beginAttachmentLeaseEffect(sess *session, ac *attachedClient, lease *attachmentLease) (*attachmentEffect, bool) {
125 if sess == nil || ac == nil || lease == nil {
126 return nil, false
127 }
128 token := captureAttachmentCapability(sess, ac, ac.transport())
129 token.lease = lease
130 return ac.beginAttachmentEffect(token)
131}
132
133func (t attachmentCapability) currentInSessionLocked(sess *session) bool {
134 return t.sess == sess && t.ac != nil && t.ac.lifecycle.currentSessionLocked(t)
135}
136
137func (t attachmentCapability) currentInSessionAndLeaseLocked(sess *session, ac *attachedClient, coordinator *renderCoordinator) bool {
138 return t.sess == sess && t.ac == ac && ac != nil &&
139 ac.lifecycle.currentSessionAndLeaseLocked(t, coordinator)
140}
141
142// currentSessionLocked validates capability authority where sess.core().mu is
143// already held. An admitted effect does not use this comparison: a transition
144// cannot change identity until that effect ends.
145func (l *attachmentLifecycle) currentSessionLocked(capability attachmentCapability) bool {
146 if !l.identityCurrent(capability) || !attachmentRegisteredLocked(capability.sess, capability.ac) {
147 return false
148 }
149 if capability.lease == nil {
150 return true
151 }
152 rc := capability.sess.core().coordinator.Load()
153 return rc != nil && capability.lease.attachment == capability.ac && rc.leaseCurrent(capability.lease, false)
154}
155
156// currentSessionAndLeaseLocked validates the same capability tuple when the
157// caller already holds both the Session and coordinator locks.
158func (l *attachmentLifecycle) currentSessionAndLeaseLocked(capability attachmentCapability, coordinator *renderCoordinator) bool {
159 if !l.identityCurrent(capability) || !attachmentRegisteredLocked(capability.sess, capability.ac) {
160 return false
161 }
162 if capability.lease == nil {
163 return true
164 }
165 return coordinator != nil && capability.lease.attachment == capability.ac &&
166 coordinator.leaseCurrentLocked(capability.lease, false)
167}