返回 DeepSeek-Reasonix
runtime_owner.go
根目录 / internal / extension / runtime_owner.go
1 package extension
2
3 import (
4 "context"
5 "fmt"
6 "sync/atomic"
7 )
8
9 // RuntimeOwner owns generation-scoped lifecycle state for one logical
10 // controller/session lineage. Rebuilds reuse the owner; independent sessions
11 // receive independent owners so publishing one runtime never drains another.
12 type RuntimeOwner struct {
13 Gate *PublishGate
14 Receipts *ReceiptStore
15 FilePriors *FilePriorStore
16 Messages *MessageSendGuard
17 HostStreams *HostStreamRegistry
18 receiptSeq atomic.Uint64
19 }
20
21 // NewRuntimeOwner returns an isolated runtime lifecycle owner.
22 func NewRuntimeOwner() *RuntimeOwner {
23 priors := NewFilePriorStore()
24 messages := NewMessageSendGuard()
25 receipts := newReceiptStore(defaultReceiptGenerationLimit, defaultReceiptPerGenerationLimit, func(r EffectReceipt) {
26 if r.Class == Compensatable {
27 priors.Forget(r.ID)
28 }
29 messages.ForgetReceipt(r)
30 })
31 gate := newPublishGate(receipts)
32 owner := &RuntimeOwner{
33 Gate: gate,
34 Receipts: receipts,
35 FilePriors: priors,
36 Messages: messages,
37 }
38 owner.HostStreams = NewHostStreamRegistry(gate)
39 return owner
40 }
41
42 // DefaultRuntimeOwner preserves package-level compatibility for callers that
43 // have not yet supplied an explicit owner. Product boot paths use isolated
44 // owners instead.
45 var DefaultRuntimeOwner = NewRuntimeOwner()
46
47 var defaultRuntimeOwnerFallbacks atomic.Uint64
48
49 // RuntimeOwnerOrDefault returns owner when it is explicitly bound and records
50 // compatibility fallbacks when a caller has not supplied one. Product boot
51 // paths bind an owner before constructing a runtime; the counter makes missed
52 // wiring observable in doctor diagnostics instead of silently sharing state.
53 func RuntimeOwnerOrDefault(owner *RuntimeOwner) *RuntimeOwner {
54 if owner != nil {
55 return owner
56 }
57 defaultRuntimeOwnerFallbacks.Add(1)
58 return DefaultRuntimeOwner
59 }
60
61 // RuntimeOwnerFallbackCount returns the number of process-local compatibility
62 // owner fallbacks observed since startup.
63 func RuntimeOwnerFallbackCount() uint64 {
64 return defaultRuntimeOwnerFallbacks.Load()
65 }
66
67 // ContextWithRuntimeOwner binds owner to provider/agent work derived from ctx.
68 func ContextWithRuntimeOwner(ctx context.Context, owner *RuntimeOwner) context.Context {
69 if ctx == nil {
70 ctx = context.Background()
71 }
72 if owner == nil {
73 return ctx
74 }
75 return context.WithValue(ctx, runtimeOwnerContextKey{}, owner)
76 }
77
78 // RuntimeOwnerFromContext returns the bound owner, falling back to the package
79 // compatibility owner for callers outside the product boot path. The fallback
80 // is counted for doctor diagnostics.
81 func RuntimeOwnerFromContext(ctx context.Context) *RuntimeOwner {
82 if ctx != nil {
83 if owner, ok := ctx.Value(runtimeOwnerContextKey{}).(*RuntimeOwner); ok && owner != nil {
84 return owner
85 }
86 }
87 return RuntimeOwnerOrDefault(nil)
88 }
89
90 type runtimeOwnerContextKey struct{}
91
92 // RecordProviderSubmit records one irreversible provider request.
93 func (o *RuntimeOwner) RecordProviderSubmit(generation uint64, streamID, owner string) {
94 o = RuntimeOwnerOrDefault(o)
95 o.Receipts.Record(EffectReceipt{
96 ID: "provider-submit:" + streamID,
97 Owner: owner,
98 Generation: generation,
99 Class: Irreversible,
100 CompensationStatus: "not_applicable",
101 })
102 }
103
104 // RecordMessageSentOnce records a user-visible send exactly once per
105 // generation/message pair in this runtime lineage.
106 func (o *RuntimeOwner) RecordMessageSentOnce(generation uint64, messageID, owner string) bool {
107 o = RuntimeOwnerOrDefault(o)
108 if !o.Messages.TryRecord(generation, messageID) {
109 return false
110 }
111 o.Receipts.Record(EffectReceipt{
112 ID: "message-sent:" + messageID,
113 Owner: owner,
114 Component: messageID,
115 Generation: generation,
116 Class: Irreversible,
117 CompensationStatus: "not_applicable",
118 })
119 return true
120 }
121
122 // RecordMessageSent records a user-visible send without applying deduplication.
123 func (o *RuntimeOwner) RecordMessageSent(generation uint64, messageID, owner string) {
124 o = RuntimeOwnerOrDefault(o)
125 o.Receipts.Record(EffectReceipt{
126 ID: "message-sent:" + messageID,
127 Owner: owner,
128 Generation: generation,
129 Class: Irreversible,
130 CompensationStatus: "not_applicable",
131 })
132 }
133
134 // RecordFileWrite captures prior state under a unique receipt ID. Repeated
135 // writes to the same path never overwrite an earlier generation's evidence.
136 func (o *RuntimeOwner) RecordFileWrite(path string, hadPrior bool, prior []byte) string {
137 o = RuntimeOwnerOrDefault(o)
138 gen := o.Gate.Published()
139 id := fmt.Sprintf("file-write:%d:%d", gen, o.receiptSeq.Add(1))
140 retained := o.FilePriors.Capture(id, path, prior, hadPrior)
141 status := "prior_captured"
142 if !retained {
143 status = "prior_truncated"
144 }
145 o.Receipts.Record(EffectReceipt{
146 ID: id,
147 Owner: "write_file",
148 Generation: gen,
149 Class: Compensatable,
150 CompensationStatus: status,
151 Error: fmt.Sprintf("prior_bytes=%d retained=%t", len(prior), retained),
152 })
153 return id
154 }
155
156 // ApplyFileWriteCompensation restores prior file state and updates this
157 // lineage's receipt without touching another runtime owner.
158 func (o *RuntimeOwner) ApplyFileWriteCompensation(receiptID string) error {
159 o = RuntimeOwnerOrDefault(o)
160 if err := o.FilePriors.Compensate(receiptID); err != nil {
161 o.Receipts.Record(EffectReceipt{
162 ID: receiptID,
163 Class: Compensatable,
164 CompensationStatus: "failed",
165 Error: err.Error(),
166 })
167 return err
168 }
169 o.FilePriors.Forget(receiptID)
170 o.Receipts.Record(EffectReceipt{
171 ID: receiptID,
172 Class: Compensatable,
173 CompensationStatus: "applied",
174 })
175 return nil
176 }
177
178 // DecideResume evaluates recovery evidence owned by this runtime lineage.
179 func (o *RuntimeOwner) DecideResume(generation uint64) ResumeDecision {
180 o = RuntimeOwnerOrDefault(o)
181 return DecideResume(o.Receipts, generation)
182 }
183
184 // AssessRecoverability evaluates recovery evidence owned by this lineage.
185 func (o *RuntimeOwner) AssessRecoverability(generation uint64) Recoverability {
186 o = RuntimeOwnerOrDefault(o)
187 return o.Receipts.AssessRecoverability(generation)
188 }
189
189 lines GO