返回 DeepSeek-Reasonix
session_write_authority.go
根目录 / internal / control / session_write_authority.go
1 package control
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "reflect"
8
9 "reasonix/internal/agent"
10 "reasonix/internal/event"
11 "reasonix/internal/session"
12 )
13
14 // BindSessionWriteAuthority issues a generation-bound write authority from
15 // the lease's SessionWriter onto the executor session. A nil lease clears
16 // the binding so later saves fail closed instead of forking recovery.
17 func (c *Controller) BindSessionWriteAuthority(lease *agent.SessionLease) error {
18 if c == nil {
19 return nil
20 }
21 gen := agent.NextSessionWriteGeneration()
22 if c.executor == nil {
23 return nil
24 }
25 sess := c.executor.Session()
26 if sess == nil {
27 return nil
28 }
29 sess.RequireWriteAuthority()
30 if lease == nil {
31 sess.ClearWriteAuthority()
32 return nil
33 }
34 // Mint through the lease writer so saves serialize and update its baseline.
35 // Recovery rebinds the lease before sessionPath updates; saves still
36 // enforce auth.Covers(targetPath).
37 if err := lease.Writer().Bind(sess, gen); err != nil {
38 sess.ClearWriteAuthority()
39 return err
40 }
41 if c.managedSessionEvents.Load() {
42 if err := c.activateManagedSessionEvents(sess); err != nil {
43 sess.ClearWriteAuthority()
44 return err
45 }
46 }
47 return nil
48 }
49
50 // activateManagedSessionEvents publishes the replacement runtime's exact
51 // projection only after the final lease handoff succeeds. This keeps a failed
52 // settings/model rebuild from changing the still-active controller through the
53 // shared in-process v3 store.
54 func (c *Controller) activateManagedSessionEvents(sess *agent.Session) error {
55 if c == nil || sess == nil {
56 return nil
57 }
58 if prompt := c.basePrompt(); prompt != "" {
59 sess.SetLeadingSystemPromptWithReason(prompt, "managed-runtime-activation")
60 }
61 // Write-authority binding can run before a service-backed Runtime is
62 // published. Its publication path seeds the projection; this preparation
63 // path must not manufacture a path-derived sidecar.
64 if service, runtime, exclusive := c.v3Binding(); exclusive && service != nil && runtime == nil {
65 return nil
66 }
67 messages := sess.Snapshot()
68 snapshot, ok := c.sessionEventSnapshot()
69 if !ok || snapshot.EventSequence == 0 {
70 if err := c.seedSessionEventsFromExecutor("managed-runtime-activation"); err != nil {
71 return err
72 }
73 } else if !reflect.DeepEqual(snapshot.Projection.ModelMessages, messages) {
74 if err := c.replaceSessionEventProjection(context.Background(), "managed-runtime-activation", messages); err != nil {
75 return err
76 }
77 }
78 planPayload, _ := json.Marshal(map[string]any{"enabled": c.PlanMode()})
79 return c.appendDomainState("plan/state", planPayload, "managed-runtime-activation")
80 }
81
82 // WriteAuthorityGeneration reports the generation currently bound on this
83 // controller. Tests use it to prove old generations become stale after rebind.
84 func (c *Controller) WriteAuthorityGeneration() uint64 {
85 if c == nil || c.executor == nil || c.executor.Session() == nil {
86 return 0
87 }
88 return c.executor.Session().WriteAuthority().Generation()
89 }
90
91 func (c *Controller) submitCommandOrTurn(trimmed, input, display string, scopedRefsOnly bool, editedOriginal, format string, admission turnAdmission) {
92 if err := c.ensureWriteAuthorityReady(); err != nil {
93 c.sink.Emit(event.Event{Kind: event.Notice, Level: event.LevelWarn, Text: "input was not accepted: this session is no longer writable — reopen it and try again"})
94 return
95 }
96 c.submitCommandOrTurnReady(trimmed, input, display, scopedRefsOnly, editedOriginal, format, admission)
97 }
98
99 // Run verifies the live write generation before synchronous headless turns.
100 func (c *Controller) Run(ctx context.Context, input string) error {
101 prepared, failures := c.prepareSubmissionImagesContext(ctx, SubmissionRequest{Input: input})
102 if len(failures) > 0 {
103 return ImageReferenceFailures(failures)
104 }
105 ctx = contextWithPreparedImageReferences(ctx, prepared)
106 err := c.runSynchronousTurn(ctx, nil, func(runCtx context.Context) error {
107 return c.runReady(runCtx, input)
108 })
109 if err != nil {
110 return err
111 }
112 return c.waitForGoalTerminal(ctx)
113 }
114
115 // RebindSessionWriteAuthority is a convenience for keepers that already hold a
116 // lease: it issues a fresh generation so any previous controller authority for
117 // the same lease object is immediately stale.
118 func (c *Controller) RebindSessionWriteAuthority(lease *agent.SessionLease) error {
119 return c.BindSessionWriteAuthority(lease)
120 }
121
122 // ensureWriteAuthorityReady refuses turn admission when the session path is
123 // set but the bound authority is missing or stale. Empty session paths (no
124 // persistence yet) are allowed.
125 func (c *Controller) ensureWriteAuthorityReady() error {
126 if c == nil || c.executor == nil {
127 return nil
128 }
129 if service, runtime, exclusive := c.v3Binding(); exclusive {
130 if runtime == nil {
131 if service == nil {
132 return session.ErrSessionNotRunning
133 }
134 if _, err := c.BindFreshSession(context.Background(), ""); err != nil {
135 return err
136 }
137 _, runtime, _ = c.v3Binding()
138 if runtime == nil {
139 return session.ErrSessionNotRunning
140 }
141 }
142 phase := runtime.StateSnapshot().Phase
143 if phase == session.RuntimeRecoveryRequired {
144 return session.ErrRecoveryRequired
145 }
146 if phase == session.RuntimeClosed {
147 return session.ErrSessionNotRunning
148 }
149 return nil
150 }
151 path := c.SessionPath()
152 if path == "" {
153 return nil
154 }
155 sess := c.executor.Session()
156 if sess == nil {
157 return nil
158 }
159 auth := sess.WriteAuthority()
160 if auth == nil {
161 if sess.WriteAuthorityRequired() {
162 return agent.ErrSessionWriteAuthorityMissing
163 }
164 return nil
165 }
166 if auth.Covers(path) {
167 return nil
168 }
169 return agent.ErrSessionWriteAuthorityStale
170 }
171
172 // IssueAndBindWriteAuthority is used by SessionLeaseKeeper and desktop tabs
173 // after a successful lease acquire/rebind.
174 func IssueAndBindWriteAuthority(c *Controller, lease *agent.SessionLease) error {
175 if c == nil {
176 return nil
177 }
178 return c.BindSessionWriteAuthority(lease)
179 }
180
181 // authoritySaveError classifies authority failures so recovery does not fire.
182 func authoritySaveError(err error) bool {
183 return errors.Is(err, agent.ErrSessionWriteAuthorityMissing) ||
184 errors.Is(err, agent.ErrSessionWriteAuthorityStale)
185 }
186
186 lines GO