morphtree 2.1.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. morphtree/__init__.py +28 -0
  2. morphtree/__main__.py +3 -0
  3. morphtree/_experimental/__init__.py +1 -0
  4. morphtree/_experimental/admission.py +364 -0
  5. morphtree/_experimental/base.py +215 -0
  6. morphtree/_experimental/component_cache_v2.py +263 -0
  7. morphtree/_experimental/evolution_report.py +307 -0
  8. morphtree/_experimental/execution_v2.py +697 -0
  9. morphtree/_experimental/facade.py +289 -0
  10. morphtree/_experimental/integrated.py +748 -0
  11. morphtree/_experimental/learning.py +418 -0
  12. morphtree/_experimental/manifest.json +204 -0
  13. morphtree/_experimental/numeric_fixture.py +21 -0
  14. morphtree/_experimental/parameter_contract.py +38 -0
  15. morphtree/_experimental/postfit.py +51 -0
  16. morphtree/_experimental/preprocessing.py +133 -0
  17. morphtree/_experimental/processes.py +191 -0
  18. morphtree/_experimental/producer.py +542 -0
  19. morphtree/_experimental/product.py +156 -0
  20. morphtree/_experimental/product_cli.py +43 -0
  21. morphtree/_experimental/roundtrip_v3.py +311 -0
  22. morphtree/_experimental/runtime_io.py +65 -0
  23. morphtree/_experimental/sampling.py +26 -0
  24. morphtree/_experimental/support.py +281 -0
  25. morphtree/_experimental/target.py +59 -0
  26. morphtree/_experimental/training.py +53 -0
  27. morphtree/_experimental/worker.py +682 -0
  28. morphtree/_installed.py +17 -0
  29. morphtree/_native/__init__.py +1 -0
  30. morphtree/_native/bin/win_amd64/morphtree_kernels.dll +0 -0
  31. morphtree/_native/buffers.py +82 -0
  32. morphtree/_native/loader.py +39 -0
  33. morphtree/_native/manifest.json +29 -0
  34. morphtree/_native/runtime.py +48 -0
  35. morphtree/_native/src/histogram.c +61 -0
  36. morphtree/_native/src/traversal.c +51 -0
  37. morphtree/api.py +388 -0
  38. morphtree/api_compat.py +65 -0
  39. morphtree/backend.py +162 -0
  40. morphtree/booster.py +252 -0
  41. morphtree/cli.py +52 -0
  42. morphtree/config.py +262 -0
  43. morphtree/control.py +20 -0
  44. morphtree/data.py +300 -0
  45. morphtree/diagnostics.py +158 -0
  46. morphtree/estimators.py +156 -0
  47. morphtree/examples.py +25 -0
  48. morphtree/exporting.py +224 -0
  49. morphtree/legacy_artifacts.py +32 -0
  50. morphtree/metrics.py +156 -0
  51. morphtree/model.py +1010 -0
  52. morphtree/mutations.py +205 -0
  53. morphtree/native_lab_backend.py +79 -0
  54. morphtree/numpy_backend.py +180 -0
  55. morphtree/optuna_tuner.py +154 -0
  56. morphtree/persistence.py +414 -0
  57. morphtree/prediction_output.py +35 -0
  58. morphtree/prepared_bins.py +82 -0
  59. morphtree/public.py +106 -0
  60. morphtree/resource_plan.py +170 -0
  61. morphtree/scalable.py +781 -0
  62. morphtree/scalable_backend.py +74 -0
  63. morphtree/scalable_checkpoint.py +129 -0
  64. morphtree/scalable_data.py +255 -0
  65. morphtree/scalable_receipts.py +92 -0
  66. morphtree/scalable_target.py +68 -0
  67. morphtree/scalable_variants.py +133 -0
  68. morphtree/sklearn_view.py +79 -0
  69. morphtree/source_markers.py +25 -0
  70. morphtree/species.py +47 -0
  71. morphtree/temporary.py +65 -0
  72. morphtree/training_fragment.py +130 -0
  73. morphtree/tree.py +231 -0
  74. morphtree/tuning.py +150 -0
  75. morphtree-2.1.0.dist-info/METADATA +212 -0
  76. morphtree-2.1.0.dist-info/RECORD +79 -0
  77. morphtree-2.1.0.dist-info/WHEEL +4 -0
  78. morphtree-2.1.0.dist-info/entry_points.txt +2 -0
  79. morphtree-2.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,697 @@
1
+ """Thin installed v2 adapter: existing admission/store/native executor only.
2
+
3
+ Software fixture operations reserve cache intents but never call native fit.
4
+ LIVE is intentionally closed until the complete finite worker/owner activation
5
+ profile is separately qualified; no API call, cache or old permit opens it.
6
+ """
7
+ import copy
8
+ import json
9
+ import time
10
+ from contextvars import ContextVar
11
+ from contextlib import contextmanager
12
+ from dataclasses import asdict, replace
13
+ from pathlib import Path
14
+ from morphtree.config import AlgorithmGenome, HyperParams
15
+ from morphtree.persistence import read_json, LoadLimits
16
+ from .admission import AdmissionError, digest, sha, installed_identity
17
+ from .runtime_io import configure, artifact_path, WORKDIR, reference
18
+ from .target import _NamedRows, _split, detect_target
19
+ from .preprocessing import _m26_rows
20
+ from .training import NativeExecutor, NATIVE
21
+ from . import component_cache_v2 as cache
22
+
23
+ ACTIVE=ContextVar('comp002-owned-v2-session',default=None)
24
+ PUBLIC_SCOPE=ContextVar('product004-owned-public-qualification',default=None)
25
+
26
+
27
+ class PreFitBoundary(RuntimeError):
28
+ pass
29
+
30
+
31
+ class SearchBudgetExhausted(TimeoutError):
32
+ """Search cutoff inside the unchanged hard call budget;not a new permit."""
33
+ pass
34
+
35
+
36
+ def require_session():
37
+ session=ACTIVE.get()
38
+ if type(session) is not Session:
39
+ from .producer import Session as MultiphaseSession, PIPE_CURRENT
40
+ if type(session) is not MultiphaseSession or PIPE_CURRENT.get()!=session.policy_pin:
41
+ raise AdmissionError({'status':'BLOCKED','reason':'EXACT_INSTALLED_V2_SESSION_REQUIRED'})
42
+ session.check()
43
+ return session
44
+
45
+
46
+ def validate_model(path, value):
47
+ """M026B0 task checks, with exact original producer seed before safe load.
48
+
49
+ Seed stays in the request/provenance, not restored from arbitrary report.
50
+ Loaded normalized reports are not mistaken for fresh producer witnesses.
51
+ """
52
+ task=value['task']; data=read_json(path, LoadLimits(max_bytes=16*1024**2))
53
+ expected={'binary':'MorphTreeModel','regression':'MorphTreeRegressionModel','multiclass':'MorphTreeMulticlassModel'}[task]
54
+ if task!='binary' and value['structure']!=AlgorithmGenome().to_dict():
55
+ from morphtree.config import TASK_GROWTH_FORMATS,validate_task_genome
56
+ validate_task_genome(task,AlgorithmGenome(**value['structure']))
57
+ expected=TASK_GROWTH_FORMATS[task]
58
+ if data.get('format')!=expected:
59
+ raise ValueError('Native task mismatch')
60
+ params=data['algorithm']['parameters'] if task=='binary' else data['parameters']
61
+ if params!=value['params'] or data['n_features']!=value['features']:
62
+ raise ValueError('Native actual parameters/features differ')
63
+ if task=='binary' and data['algorithm']['genome']!=value['structure']:
64
+ raise ValueError('Native genome differs')
65
+ if task!='binary' and data.get('genome',AlgorithmGenome().to_dict())!=value['structure']:
66
+ raise ValueError('Native nonbinary payload and actual requested genome differ')
67
+ if task=='multiclass' and (data['strategy']!='ovr' or data['classes']!=value['preprocessing']['encoded_classes']):
68
+ raise ValueError('OvR columns differ')
69
+ # Exact producer payload is persisted before the safe loader projects report.
70
+ if data['report'].get('backend')!='python-stdlib' or data['report'].get('config',{}).get('seed')!=value['seed']:
71
+ raise ValueError('Missing/mismatched original producer seed/backend witness')
72
+ model=NATIVE[task]._from_payload(data, limits=LoadLimits(max_bytes=16*1024**2), backend='pure')
73
+ k=len(value['preprocessing']['encoded_classes']) if task=='multiclass' else 1
74
+ if len(model.model_.trees)!=value['params']['n_estimators']*k:
75
+ raise ValueError('Actual scalar trees differ')
76
+ return model
77
+
78
+
79
+ class Session:
80
+ """Adapter to the qualified finite primitives, not a second scheduler.
81
+
82
+ One explicit AdmissionEngine and immutable source/plan. Canonical per-key
83
+ leases/INTENT/COMPLETE/STOP come from ComponentStore. No implicit retraining.
84
+ """
85
+ def __init__(self, engine, *, root, plan, fixture=None):
86
+ if ACTIVE.get() is not None:raise RuntimeError('No nested session construction')
87
+ configure(root); self.root=artifact_path(root); self.engine=engine
88
+ if self.root!=engine.workdir or self.root!=Path(engine.q['input_root']).resolve():
89
+ raise ValueError('Exact admitted input/output scope required')
90
+ self.plan=copy.deepcopy(plan); self.pin=digest(plan)
91
+ public=plan.get('mode')=='PUBLIC_USER'
92
+ required={'schema','mode','source','protocol','seed','params','structural','max_requests','seconds'}|({'user_limits'} if public else set())
93
+ if set(plan)!=required or plan['schema']!='comp002-v2-session-v1':
94
+ raise ValueError('Exact finite versioned profile required')
95
+ if plan['protocol']!=engine.q['protocol']:
96
+ raise ValueError('Session/AdmissionEngine protocol mismatch')
97
+ self.mode=plan['mode']; self.structural=plan['structural']
98
+ if self.mode not in ('FIXTURE','PREFIT','LIVE','PUBLIC_USER') or type(self.structural) is not bool:
99
+ raise ValueError('Explicit mode/structural opt-in required')
100
+ if self.mode=='LIVE':
101
+ raise AdmissionError({'status':'BLOCKED','reason':'COMP002_LIVE_WORKER_OWNER_PROFILE_NOT_YET_QUALIFIED'})
102
+ if self.mode=='FIXTURE' and not callable(fixture) or self.mode!='FIXTURE' and fixture is not None:
103
+ raise ValueError('Explicit fixture provider only in software mode')
104
+ if public and engine.q.get('usage')!='ORDINARY_USER_NOT_SCIENTIFIC':
105
+ raise ValueError('Public Session cannot use a scientific qualification')
106
+ if not public and engine.q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC':
107
+ raise ValueError('User qualification cannot enter scientific/fixture mode')
108
+ if type(plan['max_requests']) is not int or not 1<=plan['max_requests']<=(128 if public else 32) or not 0<plan['seconds']<=(300 if public else 180):
109
+ raise ValueError('Frozen software scope at most32 requests/180s')
110
+ HyperParams(**plan['params'])
111
+ if plan['params']['n_estimators']>8 or type(plan['seed']) is not int:
112
+ raise ValueError('Existing finite adapter bound')
113
+ self.fixture=fixture; self.started=time.monotonic(); self.prepared=None
114
+ self.physical=0; self.prefit=0; self.fixture_calls=0
115
+ self.component=None; self.logical=0; self.root.mkdir(exist_ok=True)
116
+ self._oof_capture=None; self._diagnostic_proofs={}
117
+ if public:
118
+ limits=plan['user_limits']
119
+ if set(limits)!={'calls','scalar_trees','trials','artifact_bytes'} or limits['calls']!=128 or limits['scalar_trees']!=8192 or type(limits['trials']) is not int or not 0<=limits['trials']<=4 or limits['artifact_bytes']!=32*1024**2:
120
+ raise ValueError('Exact bounded ordinary-user resource profile required')
121
+ if engine.q['limits']['fits']!=limits['calls'] or engine.q['limits']['trials']!=limits['trials']:
122
+ raise ValueError('User admission and execution budgets must match')
123
+ self._user_limits=copy.deepcopy(limits);self.phase='FAMILY';self.frozen=None;self._user_trials=0
124
+ self._user_started=0;self._user_completed=0;self._user_trees=0;self._closed=False
125
+ self._search_deadline=None;self._search_closed=False
126
+ self._component_costs=[]
127
+ policy=artifact_path('SESSION.json')
128
+ if policy.exists(): raise ValueError('New software session; never renew/replay a partial run')
129
+ cache.configure(self.root,engine.package)
130
+ cache._write(policy,{'plan':self.plan,'runtime':engine.identity,'deadline':engine.deadline})
131
+ self.policy_sha=sha(policy)
132
+ self.store=cache.MultitaskStore(artifact_path('store'),max_compute_jobs=plan['max_requests'],seconds=plan['seconds'])
133
+ self.public_scope=PUBLIC_SCOPE.get() if public else None
134
+ if self.public_scope is not None:self.public_scope.bind_session(self)
135
+ self.check()
136
+
137
+ def check(self):
138
+ if self.mode=='PUBLIC_USER':
139
+ if self.public_scope is not None:
140
+ if PUBLIC_SCOPE.get() is not self.public_scope:raise PermissionError('Lost public campaign binding')
141
+ self.public_scope.check()
142
+ # Hot cooperative cancellation checks do not hash the complete
143
+ # dataset/runtime on every split. Full integrity remains mandatory
144
+ # at activation, component entry and before component commitment.
145
+ if self._closed or self.engine.stop.exists() or (self.root/'STOP').exists() or (self.store.root/'STOP').exists():
146
+ raise RuntimeError('STOP permanent for this user call')
147
+ if time.time()>=self.engine.deadline or time.monotonic()-self.started>=self.plan['seconds']:
148
+ raise TimeoutError('Finite ordinary-user deadline')
149
+ if getattr(self,'_component_deadline',None) is not None and time.monotonic()>=self._component_deadline:
150
+ raise TimeoutError('Ordinary-user cooperative component deadline')
151
+ if (not self._search_closed and self._search_deadline is not None
152
+ and time.monotonic()>=self._search_deadline):
153
+ raise SearchBudgetExhausted('Search window ended;hard call budget is unchanged')
154
+ return
155
+ configure(self.root);cache.configure(self.root,self.engine.package)
156
+ if digest(self.plan)!=self.pin or sha(self.root/'SESSION.json')!=self.policy_sha:
157
+ raise ValueError('Changed immutable session')
158
+ if digest(self.engine.q)!=self.engine._q_pin:
159
+ raise ValueError('Mutated qualification dossier')
160
+ if installed_identity(self.engine.package,self.engine.q)!=self.engine.identity:
161
+ raise ValueError('Changed installed runtime')
162
+ if self.engine.stop.exists() or (self.root/'STOP').exists() or (self.store.root/'STOP').exists():
163
+ raise RuntimeError('STOP permanent including cache hits')
164
+ if time.time()>=self.engine.deadline or time.monotonic()-self.started>=self.plan['seconds']:
165
+ raise TimeoutError('Original finite deadline')
166
+
167
+ def begin_search(self):
168
+ if self.mode!='PUBLIC_USER':return
169
+ self.check()
170
+ if self._search_deadline is not None or self._search_closed:raise PermissionError('Search cannot renew')
171
+ reserve=min(10.,self.plan['seconds']*.1)
172
+ self._search_deadline=self.started+self.plan['seconds']-reserve
173
+ self.event('USER_SEARCH_WINDOW',finalization_reserve_seconds=reserve,hard_seconds=self.plan['seconds'])
174
+
175
+ def finish_search(self,reason):
176
+ if self.mode!='PUBLIC_USER' or ACTIVE.get() is not self:
177
+ raise PermissionError('Only the active ordinary user call may close search')
178
+ # Irreversible closing can suppress only the SOFT search cutoff. Hard
179
+ # deadline,STOP,campaign binding and component deadline remain checked.
180
+ self._search_closed=True
181
+ self.check()
182
+ self.event('USER_SEARCH_CLOSED',reason=reason,new_learning_allowed=False)
183
+
184
+ def _learning_open(self):
185
+ self.check()
186
+ if self.mode=='PUBLIC_USER' and self._search_closed:
187
+ raise PermissionError('Search closed;only retained-state finalization remains')
188
+
189
+ def admit_batch(self,task,family,params,*,folds=1,kind='FAMILY'):
190
+ """Conservative preflight,not a reservation or a timing guarantee.
191
+
192
+ Actual physical/global reservations remain at the native boundary.
193
+ A batch that cannot fit the remaining quota is never partially started.
194
+ Observed inclusive component costs only screen likely overruns;hard
195
+ cancellation still applies and uncommitted children are never adopted.
196
+ """
197
+ if self.mode!='PUBLIC_USER':return
198
+ self._learning_open()
199
+ if task!=self.prepared['task'] or family not in ('boost','bag') or type(folds) is not int or not 1<=folds<=2:
200
+ raise ValueError('Exact task/family and at most two TRAIN folds required')
201
+ p=HyperParams(**params);calls=folds*(1 if family=='boost' else p.n_estimators)
202
+ scalar=folds*p.n_estimators*(len(self.prepared['classes']) if task=='multiclass' else 1)
203
+ reason=None
204
+ if self._user_started+calls>self._user_limits['calls'] or self._user_trees+scalar>self._user_limits['scalar_trees']:
205
+ reason='WHOLE_BATCH_EXCEEDS_REMAINING_CALL_QUOTA'
206
+ if self.public_scope is not None:
207
+ used=self.public_scope.used();profile=self.public_scope.policy['profile']
208
+ if used['calls']+calls>profile['calls'] or used['scalar_trees']+scalar>profile['scalar_trees']:
209
+ reason='WHOLE_BATCH_EXCEEDS_REMAINING_GLOBAL_QUOTA'
210
+ observed=max(self._component_costs,default=0.)
211
+ estimate=1.5*observed*calls if observed else None
212
+ remaining=None if self._search_deadline is None else self._search_deadline-time.monotonic()
213
+ if estimate is not None and remaining is not None and estimate>=remaining:
214
+ reason='OBSERVED_COST_SCREEN_NO_ROOM_FOR_WHOLE_BATCH'
215
+ self.event('USER_BATCH_PREFLIGHT',batch_kind=kind,task=task,family=family,calls=calls,
216
+ scalar_trees=scalar,estimated_seconds=estimate,remaining_search_seconds=remaining,
217
+ timing_scope='OPERATIONAL_HEURISTIC_NOT_GUARANTEED_OR_BENCHMARK',
218
+ decision='STOP_BEFORE_BATCH' if reason else 'ALLOW_WITH_EXISTING_PER_COMPONENT_GUARDS',reason=reason)
219
+ if reason:raise SearchBudgetExhausted(reason)
220
+
221
+ @contextmanager
222
+ def activate(self):
223
+ if self.mode=='PUBLIC_USER':self._user_integrity()
224
+ self.check()
225
+ if ACTIVE.get() is not None: raise RuntimeError('No nested concurrent session')
226
+ with cache.exclusive_lock(artifact_path('session-writer')):
227
+ token=ACTIVE.set(self)
228
+ try: yield self
229
+ finally: ACTIVE.reset(token)
230
+
231
+ def event(self, kind, **values):
232
+ if self.mode=='PUBLIC_USER':
233
+ op=values.get('value',{}).get('operation')
234
+ if kind=='ORCHESTRATION' and op=='evolution_intent':
235
+ if self.frozen is not None:raise ValueError('No mutation after public freeze')
236
+ self.phase='STRUCTURE'
237
+ if kind=='ORCHESTRATION' and op=='structure_frozen' and 'packet' in values['value']:
238
+ if self.frozen is not None and self.frozen!=values['value']['packet']:
239
+ raise ValueError('Frozen user program changed')
240
+ self.frozen=copy.deepcopy(values['value']['packet']);self.phase='NUMERIC'
241
+ self.store.event(kind,phase=self.phase,**values);return
242
+ self.store.event(kind, **values)
243
+
244
+ def counters(self):
245
+ p=self.store.root/'EVENTS.jsonl'
246
+ rows=[json.loads(line) for line in p.read_text('utf8').splitlines()] if p.exists() else []
247
+ result={'physical_fits':self.physical,'fixture_operations':self.fixture_calls,
248
+ 'prefit_entries':self.prefit,'trials':0,
249
+ 'OOF':sum(r['event']=='NATIVE_FIT_COMPLETED' and r.get('phase')=='OOF' for r in rows),
250
+ 'OOF_count_semantics':'COMPLETED_PHYSICAL_NATIVE_FIT_COMPONENTS_NOT_FIXTURE_OR_CACHE',
251
+ 'OOF_fixture_components':sum(r.get('components',0) for r in rows
252
+ if r['event']=='OOF_CONTEXT_PRODUCED' and r.get('physical_fits_scope')=='FIXTURE'),
253
+ 'calibrators':0,
254
+ 'store':self.store.counts(),'elapsed_operational_seconds':time.monotonic()-self.started,
255
+ 'CPU':'UNKNOWN','peak_RSS':'UNKNOWN','performance_qualified':False}
256
+ if self.mode=='PUBLIC_USER':
257
+ created=[r for r in rows if r['event']=='NUMERIC_TRIAL_ASKED']
258
+ result.update(physical_fits_started=self._user_started,physical_fits_completed=self._user_completed,
259
+ scalar_trees_started=self._user_trees,Optuna_trials=sum(r['real'] for r in created),trials=sum(r['real'] for r in created),
260
+ trial_reservations=self._user_trials,simulated_trials=sum(not r['real'] for r in created),
261
+ scope='ORDINARY_USER_OPERATIONAL_NOT_SCIENTIFIC',closed=self._closed)
262
+ return result
263
+
264
+ def evolution_context(self, selected, program):
265
+ """Use only the exact Session's admitted fixed-parent fold operation.
266
+
267
+ Base software sessions stay fixture-only. A versioned owned worker may
268
+ implement oof_operation, with separate owner/role/phase/cumulative caps.
269
+ Constructing a context never creates that authorization.
270
+ """
271
+ self.check()
272
+ # A repeated unchanged parent may reuse a revalidated context; it does
273
+ # not consume another inspect token or replay physical fitting.
274
+ for proof in getattr(self,'_diagnostic_proofs',{}).values():
275
+ if proof['parent']==selected and proof['context']['program_sha256']==digest(program):
276
+ if self.verify_evolution_context(proof['context'],selected):
277
+ self.event('OOF_CONTEXT_REUSED',context=digest(proof['context']),physical_fits=0)
278
+ return copy.deepcopy(proof['context'])
279
+ with self.oof_operation(selected,program) as evidence:
280
+ if evidence is None:return None
281
+ return self._produce_oof_context(selected,program,evidence)
282
+
283
+ @contextmanager
284
+ def oof_operation(self, selected, program):
285
+ if self.mode=='PUBLIC_USER':
286
+ if not self.structural or self.frozen is not None:
287
+ yield None;return
288
+ self._user_integrity();previous=self.phase;self.phase='OOF'
289
+ try:yield 'AUTHENTIC_USER_TRAIN_FOLDS_NOT_INDEPENDENT_SCIENTIFIC_VALIDATION'
290
+ finally:self.phase=previous
291
+ return
292
+ yield ('SOFTWARE_FOLD_FIXTURE_NOT_TRAINED_OOF'
293
+ if type(self) is Session and self.mode=='FIXTURE' and self.structural else None)
294
+
295
+ def _produce_oof_context(self, selected, program, evidence):
296
+ self.check();start=time.monotonic()
297
+ from .integrated import learning_program
298
+ from .sampling import oof_folds
299
+ from .preprocessing import TrainEncoder
300
+ from morphtree.diagnostics import ErrorMemory, ErrorProfiler
301
+ import math
302
+ a=self.prepared
303
+ if a is None:return None
304
+ task=a['task']
305
+ if task!='binary' and not callable(getattr(ErrorProfiler,'profile_task',None)):
306
+ return None # Older native runtimes keep their binary-only boundary.
307
+ if program!=learning_program(selected) or selected['params']!=self.plan['params']:
308
+ raise ValueError('OOF requires exact current fixed program and numeric parameters')
309
+ raw=[a['rows'][i] for i in a['ti']];y=[a['y'][i] for i in a['ti']]
310
+ ids=['ROW:'+str(i) for i in a['ti']]
311
+ parent_encoder=TrainEncoder.from_dict(selected['recipe']['encoder'])
312
+ encoded=parent_encoder.transform(_NamedRows(raw,a['names']))
313
+ if digest({'X':encoded,'y':y,'ids':a['ti']})!=selected['learning_identity']['TRAIN']:
314
+ raise ValueError('OOF parent differs from actual fixed TRAIN')
315
+ try:held_folds=oof_folds(y,task,self.plan['seed'])
316
+ except ValueError:
317
+ self.event('OOF_ABSTAIN',reason='INSUFFICIENT_TRAIN_CLASS_OR_ROW_SUPPORT',task=task)
318
+ return None
319
+ self.admit_batch(task,selected['family'],selected['params'],folds=len(held_folds),kind='OOF')
320
+ pred=[None]*len(y);proofs=[]
321
+ # Write the plan before component intents. No SEARCH labels/predictions
322
+ # enter folds, feature fitting or the profiler.
323
+ key=digest({'parent':selected,'program':program})
324
+ plan={'parent_sha256':digest(selected),'program_sha256':digest(program),
325
+ 'TRAIN_ids':ids,'held_folds':held_folds,'mode':self.mode,
326
+ 'evidence':evidence,'fold_local_preprocessing':True}
327
+ path=artifact_path('OOF-'+key+'.json')
328
+ if path.exists():
329
+ if cache._read(path)!=plan:raise ValueError('Changed fixed-parent OOF plan')
330
+ else:cache._write(path,plan)
331
+ for held in held_folds:
332
+ self.check();fit=[i for i in range(len(y)) if i not in set(held)]
333
+ if not fit or not held or (task!='regression' and set(y[i] for i in fit)!=set(y)):
334
+ return None # Do not repair labels or leak held rows into fit.
335
+ e=TrainEncoder().fit([raw[i] for i in fit]);e.state_['names']=a['names']
336
+ fx=e.transform(_NamedRows([raw[i] for i in fit],a['names']))
337
+ hx=e.transform(_NamedRows([raw[i] for i in held],a['names']))
338
+ fy=[y[i] for i in fit];fold_ids=[ids[i] for i in fit]
339
+ identity={'preprocessing':e.to_dict(),'seed':self.plan['seed'],'task':task,
340
+ 'family':selected['family'],'TRAIN_ids':fold_ids,
341
+ 'actual_TRAIN_X':fx,'actual_TRAIN_y':fy}
342
+ # The owned worker validates this exact raw complement/encoder and
343
+ # sample again before a native entry. It is not caller admission.
344
+ self._fold_scope={'fit':fit,'held':held,'TRAIN_ids':ids,'encoder':e.to_dict(),
345
+ 'X':fx,'y':fy,'ids':fold_ids,'parent':copy.deepcopy(selected),
346
+ 'program':copy.deepcopy(program),'family':selected['family']}
347
+ self._oof_capture=[]
348
+ try:
349
+ model=self.executor(None).fit_family(task,selected['family'],fx,fy,
350
+ seed=self.plan['seed'],params=HyperParams(**selected['params']),
351
+ genome=AlgorithmGenome(**program['genome']),check=self.check,identity=identity)
352
+ receipts=copy.deepcopy(self._oof_capture)
353
+ finally:self._oof_capture=None;self._fold_scope=None
354
+ values=model.probabilities_or_values(hx)
355
+ for i,v in zip(held,values):pred[i]=v
356
+ proofs.append({'fit':fit,'held':held,'encoder':e.to_dict(),'receipts':receipts,
357
+ 'held_X':hx,'prediction':values})
358
+ from .support import prediction_contract
359
+ prediction_contract(task,y,pred)
360
+ px=[[float('nan') if v is None else v for v in row] for row in encoded]
361
+ if task=='binary':
362
+ memory=ErrorMemory(len(y));memory.update(y,pred)
363
+ profile=ErrorProfiler().profile(px,y,memory,px,y,pred)
364
+ else:profile=ErrorProfiler().profile_task(px,y,pred,task)
365
+ profile['interpretation']='Current-program TRAIN-fold associations;not causal or unbiased evidence for the complete adaptive selection procedure'
366
+ profile['acquisition_evidence']=evidence
367
+ allowed=[]
368
+ if program['genome']==AlgorithmGenome().to_dict():
369
+ allowed=[digest(learning_program({'task':task,'family':selected['family'],
370
+ 'recipe':{'genome':AlgorithmGenome(growth_policy='depthwise').to_dict()}}))]
371
+ context={'role':'TRAIN_OOF','parent_packet_sha256':digest(selected),
372
+ 'program_sha256':digest(program),'learning_identity':copy.deepcopy(selected['learning_identity']),
373
+ 'profile':profile,'allowed_programs':allowed,
374
+ 'evidence':evidence}
375
+ proof={'context':copy.deepcopy(context),'parent':copy.deepcopy(selected),
376
+ 'plan':path.name,'plan_sha256':sha(path),'folds':proofs,'mode':self.mode,
377
+ 'acquisition_seconds_operational':time.monotonic()-start,
378
+ 'cost_scope':'INCLUSIVE_OPERATIONAL_NOT_PERFORMANCE','CPU':'UNKNOWN','peak_RSS':'UNKNOWN'}
379
+ durable=artifact_path('OOF-PROOF-'+digest(context)+'.json');cache._write(durable,proof)
380
+ self._diagnostic_proofs[digest(context)]={'context':copy.deepcopy(context),
381
+ 'parent':copy.deepcopy(selected),'artifact':durable.name,'sha256':sha(durable)}
382
+ self.event('OOF_CONTEXT_PRODUCED',parent=digest(selected),context=digest(context),
383
+ evidence=evidence,components=sum(len(f['receipts']) for f in proofs),
384
+ physical_fits_scope=self.mode)
385
+ return context
386
+
387
+ def verify_evolution_context(self, context, selected):
388
+ """Reopen committed components and reproduce every held prediction.
389
+
390
+ No caller-provided boolean or metadata hash establishes authenticity.
391
+ The proof is private to this exact software session; trained LIVE OOF
392
+ remains blocked rather than inheriting this fixture qualification.
393
+ """
394
+ self.check()
395
+ if self.mode=='FIXTURE':
396
+ if context.get('evidence')!='SOFTWARE_FOLD_FIXTURE_NOT_TRAINED_OOF':return False
397
+ elif self.mode=='PUBLIC_USER':
398
+ if type(self) is not Session or context.get('evidence')!='AUTHENTIC_USER_TRAIN_FOLDS_NOT_INDEPENDENT_SCIENTIFIC_VALIDATION':return False
399
+ elif self.mode=='LIVE':
400
+ from .producer import Session as Owned, evolution_profile
401
+ if type(self) is not Owned or not evolution_profile():return False
402
+ self.permit()
403
+ if context.get('evidence')!='AUTHENTIC_FIXED_PROGRAM_TRAIN_FOLDS_ADAPTIVE_UTILITY_UNPROVED':return False
404
+ else:return False
405
+ proof=self._diagnostic_proofs.get(digest(context))
406
+ if proof is None or proof['context']!=context or proof['parent']!=selected:return False
407
+ durable=artifact_path(proof['artifact'])
408
+ if sha(durable)!=proof['sha256']:raise ValueError('Changed durable diagnostic proof')
409
+ proof=cache._read(durable)
410
+ if proof['mode']!=self.mode or proof['context']!=context or proof['parent']!=selected:return False
411
+ if sha(artifact_path(proof['plan']))!=proof['plan_sha256']:raise ValueError('Changed OOF plan')
412
+ from .support import FamilyModel
413
+ from .preprocessing import TrainEncoder
414
+ a=self.prepared;raw=[a['rows'][i] for i in a['ti']];y=[a['y'][i] for i in a['ti']]
415
+ covered=[]
416
+ for fold in proof['folds']:
417
+ self.check();models=[]
418
+ fit,held=fold['fit'],fold['held'];covered+=held
419
+ if set(fit)&set(held) or sorted(fit+held)!=list(range(len(raw))):raise ValueError('Not a TRAIN-only complement')
420
+ e=TrainEncoder().fit([raw[i] for i in fit]);e.state_['names']=a['names']
421
+ if e.to_dict()!=fold['encoder'] or e.transform(_NamedRows([raw[i] for i in held],a['names']))!=fold['held_X']:
422
+ raise ValueError('Fold-local preprocessing or held observations changed')
423
+ for row in fold['receipts']:
424
+ request=cache.FitRequest(cache.canonical(row['request']))
425
+ v=request.value()
426
+ expected=cache.learning_request(
427
+ {'role':'TRAIN','ids':['ROW:'+str(a['ti'][i]) for i in fit],
428
+ 'X':e.transform(_NamedRows([raw[i] for i in fit],a['names'])),
429
+ 'y':[y[i] for i in fit]},v['indices'],
430
+ **{k:v[k] for k in ('protocol','split_sha256','preprocessing','task','family',
431
+ 'structure','params','config','objective','seed','backend','dependencies','sources')})
432
+ if request!=expected or v['structure']!=selected['recipe']['genome'] or v['family']!=selected['family']:
433
+ raise ValueError('Fold component does not bind the actual TRAIN complement/current program')
434
+ receipt=self.store._result(request,artifact_path(row['folder']))
435
+ if digest(receipt)!=row['receipt_sha256']:raise ValueError('Changed OOF committed receipt')
436
+ if request.value()['dependencies']['mode']!=self.mode:raise ValueError('Cross-mode OOF cache provenance')
437
+ if self.mode in ('LIVE','PUBLIC_USER') and not any(r['event']=='NATIVE_FIT_COMPLETED' and r.get('phase')=='OOF' and r.get('key')==request.key for r in self.store.events()):
438
+ raise ValueError('No authenticated completed OOF native call')
439
+ models.append(validate_model(artifact_path(receipt['artifact']['path']),request.value()))
440
+ if not models:raise ValueError('Missing actual fold components')
441
+ model=FamilyModel(selected['task'],models,[1/len(models)]*len(models))
442
+ if model.probabilities_or_values(fold['held_X'])!=fold['prediction']:
443
+ raise ValueError('OOF prediction differs from committed native component')
444
+ if sorted(covered)!=list(range(len(raw))):raise ValueError('Held coverage differs')
445
+ return True
446
+
447
+ def _capture_oof_component(self, request, lease):
448
+ if getattr(self,'_oof_capture',None) is not None:
449
+ self._oof_capture.append({'request':request.value(),
450
+ 'folder':lease.folder.relative_to(self.root).as_posix(),'receipt_sha256':digest(lease.result)})
451
+
452
+ def bind_input(self, estimator, X, y):
453
+ self.check(); rows,names=_m26_rows(X); task,targets,classes=detect_target(y,estimator.task)
454
+ if estimator.random_state!=self.plan['seed'] or self.mode!='PUBLIC_USER' and estimator.capacity is not None:
455
+ raise ValueError('Exact seed/profile capacity required')
456
+ if self.mode=='PUBLIC_USER':
457
+ expected=asdict(HyperParams(n_estimators=2,max_leaves=8,max_depth=4,max_bins=15,min_samples_leaf=2,row_fraction=1.))
458
+ if estimator.capacity:expected.update(estimator.capacity)
459
+ if expected!=self.plan['params']:raise ValueError('Public requested capacity differs from immutable session')
460
+ source=self.plan['source']
461
+ if len(rows)!=len(source['rows']) or any(r!={'id':'ROW:'+str(i),'X':rows[i],'y':y[i]} for i,r in enumerate(source['rows'])):
462
+ raise ValueError('Actual supplied data differ from admitted source')
463
+ ti,si=_split(targets,task,self.plan['seed'])
464
+ if source['partitions']!={'TRAIN':['ROW:'+str(i) for i in ti],
465
+ 'SEARCH':['ROW:'+str(i) for i in si],'GUARD_FINAL':[]}:
466
+ raise ValueError('Actual ordered split differs')
467
+ if source['OOF']: raise ValueError('No OOF producer admitted in COMP002')
468
+ self.prepared={'rows':rows,'names':names,'y':targets,'classes':classes,'task':task,'ti':ti,'si':si}
469
+
470
+ def parameters(self, unused):
471
+ return HyperParams(**self.plan['params'])
472
+
473
+ @property
474
+ def source(self):return self.plan['source']
475
+
476
+ @contextmanager
477
+ def orchestration_operation(self, estimator):
478
+ a=self.prepared; p=self.plan['params']; k=len(a['classes']) or 1
479
+ families=['boost','bag'] if estimator.families=='auto' else [estimator.families]
480
+ forms=[{'family':f,'members':1 if f=='boost' else p['n_estimators'],
481
+ 'stages':p['n_estimators'] if f=='boost' else 1,'classes':k,
482
+ 'genome_sha256':digest(AlgorithmGenome().to_dict())} for f in families]
483
+ form=forms[0] if len(forms)==1 else {'candidates':forms}
484
+ if hasattr(self,'admission_recipe'):
485
+ form=self.admission_recipe(estimator,form)
486
+ request=self.engine.request(operation='inspect',task=a['task'],recipe=form,
487
+ source=self.source,role='TRAIN',protocol=self.engine.q['protocol'],
488
+ authorization=self.engine.q['authorization']['id'],components=[])
489
+ # One exact software operation contains the canonical per-component
490
+ # intents. This inspect capability never itself authorizes training.
491
+ with self.engine.operation(request): yield
492
+
493
+ def optional_optuna(self):
494
+ if self.mode!='PUBLIC_USER':return False
495
+ import importlib.util
496
+ return importlib.util.find_spec('optuna') is not None
497
+ def numeric_admitted(self, executor):
498
+ return self.mode=='PUBLIC_USER' and self._user_limits['trials']>0
499
+ def load_optuna(self):
500
+ if self.mode=='PUBLIC_USER':
501
+ if not self.numeric_admitted(None):raise PermissionError('No numerical user budget')
502
+ import importlib
503
+ from .producer import NumericBinding
504
+ module=importlib.import_module('optuna')
505
+ from importlib.metadata import distribution
506
+ dist=distribution('optuna')
507
+ if module.__version__!='5.0.0' or dist.version!='5.0.0' or not Path(module.__file__).resolve().is_relative_to(Path(dist.locate_file('optuna')).resolve()):
508
+ raise ValueError('Exact supported Optuna5.0.0 distribution required;no shadowed driver')
509
+ return NumericBinding(self,module)
510
+ raise AdmissionError({'status':'BLOCKED','reason':'OPTIONAL_DEPENDENCY_AND_LIVE_TRIAL_PROFILE_REQUIRED'})
511
+
512
+ def reserve_trial(self, *, real_driver=True):
513
+ self._learning_open()
514
+ if self.mode!='PUBLIC_USER' or self.frozen is None or self.phase!='NUMERIC':
515
+ raise PermissionError('User trial requires an already frozen program')
516
+ if type(real_driver) is not bool:raise ValueError('Explicit driver evidence required')
517
+ self.check()
518
+ if self._user_trials>=self._user_limits['trials']:raise RuntimeError('Numerical user budget exhausted')
519
+ if self.public_scope is not None:self.public_scope.reserve(self,operation='trial',trees=0,
520
+ key=digest({'frozen':self.frozen,'ordinal':self._user_trials,'source':digest(self.source)}))
521
+ r=self._user_learning_request('trial',self.frozen['task'],self.frozen['family'],
522
+ HyperParams(**self.frozen['params']),len(self.frozen['recipe']['classes']),self.frozen['recipe']['genome'])
523
+ with self.engine.user_learning(r):pass # Reservation precedes Optuna.ask, not a completed numerical trial.
524
+ self._user_trials+=1
525
+ self.event('NUMERIC_TRIAL_INTENT',real=real_driver,frozen_sha256=digest(self.frozen))
526
+
527
+ def _user_learning_request(self,operation,task,family,params,classes,genome):
528
+ recipe={'family':family,'members':params.n_estimators if family=='bag' and operation=='trial' else 1,'stages':params.n_estimators if family=='boost' else 1,
529
+ 'classes':classes if task!='regression' else 1,'genome_sha256':digest(genome)}
530
+ r=self.engine.request(operation=operation,task=task,recipe=recipe,source=self.source,role='TRAIN',
531
+ protocol=self.engine.q['protocol'],authorization=self.engine.q['authorization']['id'],components=[],
532
+ requested_cost={'fits':int(operation=='fit'),'trials':int(operation=='trial')})
533
+ r['component_key']=digest({'operation':operation,'ordinal':self._user_started if operation=='fit' else self._user_trials,'recipe':recipe})
534
+ r['id']=digest({k:v for k,v in r.items() if k!='id'})
535
+ return r
536
+
537
+ def packet_lineage(self,genome):
538
+ if self.mode=='PUBLIC_USER' and self.frozen is not None:
539
+ if genome.to_dict()!=self.frozen['recipe']['genome']:raise ValueError('Changed frozen numerical genome')
540
+ return copy.deepcopy(self.frozen['recipe'].get('lineage',{}))
541
+ return {}
542
+
543
+ def _user_integrity(self):
544
+ if self.mode!='PUBLIC_USER':raise PermissionError('Ordinary-user boundary only')
545
+ self.check()
546
+ configure(self.root);cache.configure(self.root,self.engine.package)
547
+ if digest(self.plan)!=self.pin or sha(self.root/'SESSION.json')!=self.policy_sha or digest(self.engine.q)!=self.engine._q_pin:
548
+ raise ValueError('Mutated ordinary-user plan/qualification')
549
+ if installed_identity(self.engine.package,self.engine.q)!=self.engine.identity:
550
+ raise ValueError('Changed installed ordinary-user runtime')
551
+ if self._user_limits!=self.plan['user_limits']:raise ValueError('Changed ordinary-user resource limits')
552
+
553
+ def close_user_run(self):
554
+ if self.mode!='PUBLIC_USER' or self._closed:return
555
+ self.event('USER_RUN_CLOSED',counts=self.counters(),authority_residual=0)
556
+ self._closed=True
557
+ self.engine.stop_permanently('Ordinary user call finished;never a scientific training permit')
558
+
559
+ def executor(self, estimator):
560
+ session=self
561
+ class Executor(NativeExecutor):
562
+ evidence_level={'PUBLIC_USER':'USER_API_NATIVE_EXECUTION_NOT_SCIENTIFIC_CONFIRMATION','FIXTURE':'COMP003_SOFTWARE_FIXTURE_NOT_TRAINING',
563
+ 'PREFIT':'COMP003_PREFIT_NOT_TRAINING','LIVE':'COMP003_EXACT_OWNER_ADMITTED_NATIVE_TRAINING'}[session.mode]
564
+ def execute_native(self, model, X, y, *, identity, cancel):
565
+ return session.execute_native(model,X,y,identity=identity,cancel=cancel)
566
+ return Executor()
567
+
568
+ def execute_native(self, model, X, y, *, identity, cancel):
569
+ start=time.monotonic();before=getattr(self,'_user_completed',0)
570
+ try:return self._execute_native(model,X,y,identity=identity,cancel=cancel)
571
+ finally:
572
+ if self.mode=='PUBLIC_USER' and self._user_completed>before:
573
+ elapsed=time.monotonic()-start
574
+ self._component_costs.append(elapsed)
575
+ self.event('USER_COMPONENT_OPERATION_COST',seconds=elapsed,
576
+ scope='INCLUSIVE_COMPONENT_OPERATION_ONLY_NOT_TOTAL_FIT_OR_BENCHMARK')
577
+
578
+ def _execute_native(self, model, X, y, *, identity, cancel):
579
+ self._learning_open()
580
+ if self.mode=='PUBLIC_USER':
581
+ self._user_integrity()
582
+ if self.phase=='NUMERIC':
583
+ if self.frozen is None or identity['family']!=self.frozen['family'] or model.base_genome.to_dict()!=self.frozen['recipe']['genome']:
584
+ raise ValueError('Numerical tuning cannot change the frozen program')
585
+ base=HyperParams(**self.frozen['params'])
586
+ if identity['family']=='bag':base=replace(base,n_estimators=1,learning_rate=1.,row_fraction=1.)
587
+ allowed={'l2','min_samples_leaf'}|({'learning_rate'} if identity['family']=='boost' else set())
588
+ if any(asdict(model.base_params)[k]!=v for k,v in asdict(base).items() if k not in allowed):
589
+ raise ValueError('Numerical tuning changed a fixed parameter')
590
+ self.check(); a=self.prepared; task=a['task']; family=identity['family']
591
+ if type(model) is not NATIVE[task] or model.backend!='pure' or model.model_ is not None:
592
+ raise ValueError('Fresh exact native estimator required')
593
+ p=asdict(model.base_params); c=asdict(model.config)
594
+ genome=model.base_genome.to_dict()
595
+ train={'role':'TRAIN','ids':identity['TRAIN_ids'],
596
+ 'X':identity['actual_TRAIN_X'],'y':identity['actual_TRAIN_y']}
597
+ indices=identity['ordered_sample']
598
+ if X!=[train['X'][i] for i in indices] or y!=[train['y'][i] for i in indices] or p!=identity['params']:
599
+ raise ValueError('Actual sampled data/parameters differ')
600
+ prep={**identity['preprocessing'],'fit_role':'TRAIN','encoded_classes':list(range(len(a['classes'])))}
601
+ component_protocol={k:self.plan['protocol'][k] for k in ('path','sha256')}
602
+ context=dict(protocol=component_protocol,split_sha256=digest(self.source['partitions']),
603
+ preprocessing=prep,task=task,family=family,structure=genome,params=p,config=c,
604
+ objective={'binary':'binary_logloss','multiclass':'ovr','regression':'squared_error'}[task],
605
+ seed=c['seed'],backend='pure',dependencies={'runtime':self.engine.identity,'mode':self.mode},
606
+ sources=cache.native_sources())
607
+ request=cache.learning_request(train,indices,**context)
608
+ form={'family':family,'members':1,'stages':p['n_estimators'],'classes':len(a['classes']) or 1,'genome_sha256':digest(genome)}
609
+ from .admission import CURRENT
610
+ current=CURRENT.get()
611
+ if current is None or current[0] is not self.engine or current[1]['operation']!='inspect':
612
+ raise ValueError('Missing exact parent software admission')
613
+ if True:
614
+ self.logical+=1
615
+ if self.logical>self.plan['max_requests']: raise RuntimeError('Logical opportunity budget exhausted')
616
+ with self.store.claim(request,logical_id='component:'+str(self.logical)) as lease:
617
+ if lease.hit:
618
+ self._capture_oof_component(request,lease)
619
+ return validate_model(artifact_path(lease.result['artifact']['path']),request.value())
620
+ if self.mode in ('PREFIT','LIVE','PUBLIC_USER'):
621
+ self.component=(model,copy.deepcopy(X),copy.deepcopy(y),request)
622
+ try:
623
+ native=model.fit(X,y,cancel=lambda:(cancel() or False))
624
+ path=artifact_path(lease.folder/'model.json');native.save_model(path)
625
+ validated=validate_model(path,request.value())
626
+ if self.mode=='PUBLIC_USER':
627
+ self._user_integrity()
628
+ if sum(p.stat().st_size for p in self.root.rglob('*') if p.is_file())>self._user_limits['artifact_bytes']:
629
+ raise RuntimeError('Ordinary-user artifact budget exhausted')
630
+ lease.commit(path,expected_sha256=sha(path),proof=[component_protocol])
631
+ self._capture_oof_component(request,lease)
632
+ return validated
633
+ finally:self.component=None
634
+ self.fixture_calls+=1
635
+ payload=self.fixture(copy.deepcopy(request.value()),copy.deepcopy(X),copy.deepcopy(y))
636
+ path=artifact_path(lease.folder/'model.json')
637
+ cache._write(path,payload)
638
+ native=validate_model(path,request.value())
639
+ lease.commit(path,expected_sha256=sha(path),proof=[component_protocol])
640
+ self._capture_oof_component(request,lease)
641
+ self.event('FIXTURE_COMPLETED',key=request.key,physical_fits=0)
642
+ return native
643
+
644
+ def native_boundary(self, original, model, X, y, kwargs):
645
+ self._learning_open()
646
+ self.check()
647
+ if self.mode=='PUBLIC_USER':
648
+ self._user_integrity()
649
+ if self.component is None:raise PermissionError('No active user component lease')
650
+ want,xx,yy,request=self.component;v=request.value()
651
+ if model is not want or X!=xx or y!=yy or set(kwargs)!={'cancel'} or not callable(kwargs['cancel']):
652
+ raise ValueError('Actual user native invocation differs')
653
+ if asdict(model.base_params)!=v['params'] or asdict(model.config)!=v['config'] or model.base_genome.to_dict()!=v['structure']:
654
+ raise ValueError('Actual user native recipe changed')
655
+ scalar=v['params']['n_estimators']*(len(v['preprocessing']['encoded_classes']) if v['task']=='multiclass' else 1)
656
+ if self._user_started+1>self._user_limits['calls'] or self._user_trees+scalar>self._user_limits['scalar_trees']:
657
+ raise RuntimeError('Ordinary-user cumulative native budget exhausted')
658
+ r=self._user_learning_request('fit',v['task'],v['family'],model.base_params,
659
+ len(v['preprocessing']['encoded_classes']),v['structure'])
660
+ # Repeated equal parameters still need distinct physical component
661
+ # identities. Cache hits never reach this reservation boundary.
662
+ r['component_key']=request.key
663
+ r['id']=digest({k:x for k,x in r.items() if k!='id'})
664
+ if self.public_scope is not None:self.public_scope.reserve(self,operation='fit',trees=scalar,key=request.key)
665
+ with self.engine.user_learning(r):
666
+ self._user_started+=1;self._user_trees+=scalar;self.physical+=1
667
+ self.event('NATIVE_FIT_STARTED',key=request.key,scalar_trees=scalar,physical_fits=1)
668
+ old=getattr(self,'_component_deadline',None)
669
+ self._component_deadline=min(time.monotonic()+60,self.started+self.plan['seconds'])
670
+ try:
671
+ fitted=original(model,X,y,**kwargs)
672
+ self._user_integrity();self._user_completed+=1
673
+ self.event('NATIVE_FIT_COMPLETED',key=request.key,scalar_trees=scalar,physical_fits=1)
674
+ return fitted
675
+ except BaseException as e:
676
+ self.event('NATIVE_FIT_INCOMPLETE',key=request.key,error=type(e).__name__)
677
+ raise
678
+ finally:self._component_deadline=old
679
+ if self.mode!='PREFIT' or self.component is None:
680
+ raise AdmissionError({'status':'BLOCKED','reason':'NO_CURRENT_LIVE_NATIVE_AUTHORITY'})
681
+ want,xx,yy,request=self.component
682
+ v=request.value()
683
+ if model is not want or X!=xx or y!=yy or set(kwargs)!={'cancel'} or not callable(kwargs['cancel']):
684
+ raise ValueError('Actual pre-fit invocation differs')
685
+ if asdict(model.base_params)!=v['params'] or asdict(model.config)!=v['config']:
686
+ raise ValueError('Changed actual pre-fit parameters/config')
687
+ self.prefit+=1;self.event('NATIVE_PREFIT_BOUNDARY',key=request.key,physical_fits=0)
688
+ raise PreFitBoundary('STOP_BEFORE_ORIGINAL_NATIVE_FIT')
689
+
690
+
691
+ def native_fit_entry(original, cls):
692
+ """M031 cooperative pre-fit wrapper, with exact current v2 session type."""
693
+ def guarded(model,X,y,**kwargs):
694
+ session=require_session()
695
+ if type(model) is not cls: raise ValueError('Changed native class')
696
+ return session.native_boundary(original,model,X,y,kwargs)
697
+ return guarded