Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .scratch/bridge_handle_journey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from dataclasses import dataclass
from objectstate import ObjectState, ObjectStateRegistry, Inherit, LazyDataclassFactory, set_base_config_type, set_global_config
from formbridge.bridge.dto import UiObjectStateFieldMutationRequest, UiObjectStateScopeListRequest
from formbridge.bridge.object_state import ObjectStateFieldMutationService, ObjectStateScopeProjectionService

@dataclass
class Settings:
text: str | None = None
set_base_config_type(Settings)
set_global_config(Settings, Settings(text='global text'))
Lazy = LazyDataclassFactory.make_lazy_simple(Settings)
state = ObjectState(Lazy(), scope_id='/declared-bridge-journey')
ObjectStateRegistry.register(state)
field = state.fields.text
service = ObjectStateFieldMutationService()
try:
for value in ('None', '', None):
result = service.mutate(UiObjectStateFieldMutationRequest.from_fields(
object_state_scope_id=state.scope_id, field_path='text', value=value,
))
assert result.mutated and not result.errors, result
assert field.value == value
assert result.after.raw_value_is_none == (value is None)
assert not result.after.inherited_value
result = service.mutate(UiObjectStateFieldMutationRequest.from_fields(
object_state_scope_id=state.scope_id, field_path='text', reset=True,
))
assert result.mutated and not result.errors, result
assert isinstance(field.raw, Inherit) and field.value == 'global text'
assert result.after.inherited_value
unknown = service.mutate(UiObjectStateFieldMutationRequest.from_fields(
object_state_scope_id=state.scope_id, field_path='typo', value='bad',
))
assert not unknown.mutated and unknown.errors[0].code == 'ui_object_state_field_not_found'
summary = ObjectStateScopeProjectionService().summary(state, UiObjectStateScopeListRequest(include_fields=True, include_field_values=True))
assert summary.fields[0].address.field_path == 'text'
assert summary.fields[0].inherited_value
print('real bridge mutation service preserves None/empty/string None; reset inherits; unknown address is rejected; catalog projects handles',flush=True)
assert ObjectStateRegistry.time_travel_back()
assert field.value is None
print('registered history undo retains the original handle and restores literal None',flush=True)
finally:
ObjectStateRegistry.unregister(state)
52 changes: 52 additions & 0 deletions .scratch/bridge_handles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import ast
from pathlib import Path
from nominal_refactor_advisor.ast_tools import SourceModule,module_syntax_index
P=Path('/home/ts/wt/formbridge-declared-fields/src/formbridge/bridge/object_state.py');s=P.read_text();s=s.replace('from objectstate.inspection import FieldInspection, type_name, type_qualname','from objectstate.inspection import FieldInspection, type_name, type_qualname\nfrom objectstate.field_access import DottedFieldPath, FieldAccessError')
s=s.replace('parameter_count=len(state.parameters)','parameter_count=len(state.fields)')
idx=module_syntax_index(SourceModule.from_source_path(P,s).parse().module);lines=s.splitlines(keepends=True);edits=[]
for _,c in idx.indexed_nodes_of_type(ast.ClassDef):
for n in c.body:
if not isinstance(n,ast.FunctionDef):continue
old=''.join(lines[n.lineno-1:n.end_lineno]);new=None
if c.name=='ObjectStateFieldMutationService':
if n.name=='_field_summary':new=''
if n.name=='_converted_value':new=''' @staticmethod
def _converted_value(field, value):
return ParameterFormService().convert_value_to_type(
value, field.annotation, field.name, field.declaring_type,
)
'''
if n.name=='mutate':
new=old.replace(''' if request.field_path not in state.parameters:
''',''' try:
field = DottedFieldPath(request.field_path).bind(state)
except FieldAccessError:
''')
new=new.replace('before = self._field_summary(state, request)','before = ObjectStateFieldSemanticProjection.of(field).to_field_summary(include_values=request.include_field_values)')
new=new.replace('state.reset_parameter(request.field_path)','field.reset()')
new=new.replace('''state.update_parameter(
request.field_path,
self._converted_value(state, request),
)''','''field.write(self._converted_value(field, request.value))''')
new=new.replace('after = self._field_summary(state, request)','after = ObjectStateFieldSemanticProjection.of(field).to_field_summary(include_values=request.include_field_values)')
elif n.name=='_field_page':
a=old.index(' if request.field_paths:');b=old.index(' if request.field_filter',a)
new=old[:a]+''' available = tuple(state.fields)
if request.field_paths:
all_field_paths = tuple(item for item in available if item.address.value in request.field_paths)
else:
all_field_paths = tuple(sorted(available, key=lambda item: item.address.value))
'''+old[b:]
new=new.replace(''' state,
field_path,''',''' field_path,''').replace(''' state,
field_path,''',''' field_path,''')
new=new.replace('''self._field_summary(
field_path,''','''ObjectStateFieldSemanticProjection.of(field_path).to_field_summary(''').replace('''self._field_summary(
field_path,''','''ObjectStateFieldSemanticProjection.of(field_path).to_field_summary(''')
elif n.name=='_field_summary':
new=''
if new is not None:
start=min([n.lineno]+[d.lineno for d in n.decorator_list])-1
edits.append((start,n.end_lineno,new+'\n'if new else''))
for a,b,t in sorted(edits,reverse=True):lines[a:b]=[t]
s=''.join(lines);compile(s,str(P),'exec');P.write_text(s)
28 changes: 28 additions & 0 deletions .scratch/census.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Owned source evidence for ObjectState #32/#46, using NRA's syntax/family owners."""
import ast,json
from pathlib import Path
from nominal_refactor_advisor.ast_tools import SourceModule,module_syntax_index
from nominal_refactor_advisor.class_index import build_class_family_index
ROOTS=[Path('/home/ts/wt/objectstate-declared-fields/src/objectstate'),Path('/home/ts/wt/openhcs-declared-fields/openhcs'),Path('/home/ts/wt/core-doctrine-segment/src/agent_comms'),Path('/home/ts/wt/toad-declared-fields/src/toad'),Path('/home/ts/wt/formbridge-declared-fields/src/formbridge'),Path('/home/ts/wt/pyqt-declared-fields/src/pyqt_reactive'),Path('/home/ts/wt/python-introspect-field-values/src/python_introspect'),Path('/home/ts/code/projects/openhcs/external/pycodify/src/pycodify')]
modules=[];original=[];sites=[];errors=[]
for root in ROOTS:
for path in root.rglob('*.py'):
try: parsed=SourceModule.from_source_path(path,path.read_text()).parse()
except Exception as e:errors.append((str(path),str(e)));continue
modules.append(parsed);syntax=module_syntax_index(parsed.module)
for _,node in syntax.indexed_nodes_of_type(ast.ClassDef):
original.append((str(path),node.lineno,node.name,[ast.unparse(b)for b in node.bases]))
for _,node in syntax.indexed_nodes_of_type(ast.Call):
name=ast.unparse(node.func)
if any(s in name for s in ('update_parameter','reset_parameter','get_resolved_value','on_state_changed','on_resolved_changed','patch_lazy_constructors','global_pipeline_config','rebuild_with_none','_reconstruct_from','reconstruct_top_level_parameters','type_for_path','FieldProxy','FieldInspection','DottedFieldPath','set_placeholder','setSpecialValueText','_write_concrete_value','_write_blank_value')):
sites.append((str(path),node.lineno,'call',ast.unparse(node)))
relevant={'parameters','_parameters','_parameter_structure','declarations','direct_paths','owner_paths','dirty_fields','signature_diff_fields','raw_value','raw_value_is_none','resolved_value_is_none','raw_value_preview','resolved_value_preview','resolved_value','resolved_value_available','resolved','_draft','_write_concrete_value','_write_blank_value','setSpecialValueText','has_value','is_none','_on_state_changed_callbacks','_on_resolved_changed_callbacks'}
for _,node in syntax.indexed_nodes_of_type(ast.Attribute):
if node.attr in relevant:sites.append((str(path),node.lineno,type(node.ctx).__name__,ast.unparse(node)))
for _,node in syntax.indexed_nodes_of_type(ast.ImportFrom):
if (node.module or '').startswith(('objectstate','pycodify','python_introspect')):sites.append((str(path),node.lineno,'import',ast.unparse(node)))
if root.name=='objectstate' or path.name=='widget_adapters.py':
for _,node in syntax.indexed_nodes_of_type(ast.Compare):
if any(isinstance(n,ast.Constant)and n.value is None for n in node.comparators):sites.append((str(path),node.lineno,'None decision',ast.unparse(node)))
index=build_class_family_index(modules)
print(json.dumps({'roots':list(map(str,ROOTS)),'module_count':len(modules),'original_classes':original,'sites':sites,'parse_errors':errors,'index_type':type(index).__name__,'index_members':[n for n in dir(index)if n in ('classes_by_symbol','classes','symbols_by_simple_name')]},indent=2))
24 changes: 24 additions & 0 deletions .scratch/consumer_state_cutover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import ast
from pathlib import Path
from nominal_refactor_advisor.ast_tools import SourceModule,module_syntax_index
changes={
'/home/ts/wt/pyqt-declared-fields/src/pyqt_reactive/services/widget_service.py':{
'_apply_context_behavior': [('if value is None:', 'if FieldValue.of(value).inherited:'),('elif value is not None:', 'else:')]},
'/home/ts/wt/pyqt-declared-fields/src/pyqt_reactive/services/parameter_ops_service.py':{
'_reset_value_widget_parameter':[('if reset_value is None:', 'if FieldValue.of(reset_value).inherited:')],
'refresh_single_placeholder':[('if current_value is not None:', 'if not FieldValue.of(current_value).inherited:')]},
'/home/ts/wt/formbridge-declared-fields/src/formbridge/forms/parameter_form_service.py':{
'get_reset_value_for_parameter':None,'_get_actual_dataclass_field_default':None},
}
for name,methods in changes.items():
p=Path(name);s=p.read_text();idx=module_syntax_index(SourceModule.from_source_path(p,s).parse().module);lines=s.splitlines(keepends=True);edits=[]
for _,n in idx.indexed_nodes_of_type(ast.FunctionDef):
if n.name not in methods:continue
pairs=methods[n.name];text=''.join(lines[n.lineno-1:n.end_lineno])if pairs is not None else''
for old,new in pairs or []:
assert old in text,(p,n.name,old);text=text.replace(old,new)
edits.append((n.lineno-1,n.end_lineno,text))
for a,b,text in sorted(edits,reverse=True):lines[a:b]=[text]
s=''.join(lines)
if any(v is not None for v in methods.values()):s='from objectstate.field_value import FieldValue\n'+s
ast.parse(s,filename=str(p));p.write_text(s)
32 changes: 32 additions & 0 deletions .scratch/core_handle_consumers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import ast
from pathlib import Path
from nominal_refactor_advisor.ast_tools import SourceModule,module_syntax_index
ROOT=Path('/home/ts/wt/objectstate-declared-fields/src/objectstate')
for p in ROOT.glob('*.py'):
s=p.read_text();idx=module_syntax_index(SourceModule.from_source_path(p,s).parse().module);lines=s.splitlines(keepends=True);offsets=[];at=0
for line in lines:offsets.append(at);at+=len(line)
edits=[]
for _,n in idx.indexed_nodes_of_type(ast.Attribute):
if n.attr=='parameters'and isinstance(n.value,ast.Name)and n.value.id in {'state','schema'}:
a=offsets[n.lineno-1]+len(lines[n.lineno-1].encode()[:n.col_offset].decode());b=offsets[n.end_lineno-1]+len(lines[n.end_lineno-1].encode()[:n.end_col_offset].decode());edits.append((a,b,ast.unparse(n.value)+'._parameters'))
for a,b,t in sorted(edits,reverse=True):s=s[:a]+t+s[b:]
compile(s,str(p),'exec');p.write_text(s)
p=ROOT/'object_state_registry.py';s=p.read_text();s=s.replace('pipeline_state.update_parameter("step_scope_ids", new_ids)','pipeline_state.fields.step_scope_ids.write(new_ids)');compile(s,str(p),'exec');p.write_text(s)
p=ROOT/'inspection.py';s=p.read_text();s=s.replace(''' def of(cls, state: ObjectState, field_path: str):
"""Inspect ``field_path``; a value that fails to resolve is reported unavailable."""
''',''' def of(cls, field):
"""Capture one admitted field, never look up a caller-spelled path."""
state = field.state
field_path = field._path
''').replace('state.get_resolved_value(field_path)','field.value').replace('raw_value=state._parameters[field_path]','raw_value=field.raw');compile(s,str(p),'exec');p.write_text(s)
p=ROOT/'edit_session.py';s=p.read_text();idx=module_syntax_index(SourceModule.from_source_path(p,s).parse().module);lines=s.splitlines(keepends=True);edits=[]
for _,n in idx.indexed_nodes_of_type(ast.FunctionDef):
if n.name in {'has_parameter','parameter_value','update_parameter'}:edits.append((n.lineno-1,n.end_lineno,''))
if n.name=='require_state':edits.append((n.lineno-1,n.end_lineno,''' def require_state(self) -> ObjectState:
state = self.state
if state is None:
raise RuntimeError("ObjectStateEditSession has no editing state")
return state
'''))
for a,b,t in sorted(edits,reverse=True):lines[a:b]=[t]
s=''.join(lines).rstrip()+'\n';compile(s,str(p),'exec');p.write_text(s)
30 changes: 30 additions & 0 deletions .scratch/declaration-checkpoint-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Declaration-owned checkpoint (source-paired, not installed)

Pushed heads: ObjectState 93eae56; FormBridge b5d3af8; PyQt 8690060; OpenHCS f0c07c411. Dependency python-introspect ffcffaa (PR #25).

ValueDeclaration compiles annotations once. ParameterOwnerDeclaration owns parameters, field_types and init_field_names (one definition each); constructor and configuration owners share that implementation. FieldValue.of only preserves meaning members or boxes data; it does not classify runtime payloads. Named configuration access is absent on scalar leaves. Declared collection element types own traversal and serialization. DeclaredValue shares metadata/copy/serialization lifecycle between configuration and collection values.

InheritedDefault is the single frozen class-keyed default record; ValueDefault/FactoryDefault materialize it. own_default and inherited_default now index declared fields and raise KeyError for unknown names. Their direct resolver callers validate against the requested declaration; configuration_mro(field_name) filters ancestors by their declared fields before reading defaults. All touched production roots have zero `_inherited_default`, `_resolve_type` and `__objectstate_rebuild__` references. UIParameterVisibilityPolicy owns field visibility metadata, keyed by the existing policy class. CompactFieldProjection is also its own metadata key; no string-key compatibility reader remains.

Text-line counts (published baseline -> checkpoint):

| production root | is_container | is_config_type | literal `| None` | getattr | get_base_type_for_lazy |
| --- | --- | --- | --- | --- | --- |
| ObjectState 1479884 | 27 -> 0 | 2 -> 0 | 94 -> 91 | 37 -> 16 | 46 -> 2 |
| FormBridge bdafb52 | 3 -> 0 | 6 -> 0 | 480 -> 468 | 18 -> 16 | 8 -> 0 |

The two remaining lazy callback-name lines are its definition and register_type_resolver registration; zero callers, imports or exports remain in the touched production family. The normalization wrapper/cache and per-caller fallback expressions are removed. Archived dirty-before normalization count was 45, now 2.

FormBridge FieldValue.of: published 3 -> 2; archived dirty-before 15 -> 2. get_base_type_for_lazy: 8 -> 0. metadata.get: 4 -> 2. Combined requested regex: published 15 -> 4; archived dirty-before 27 -> 4. The remaining FieldValue.of sites preserve DTO meaning after mapping JSON data, not payload kind. The remaining metadata.get sites read canonical runtime code-document tokens, not declaration metadata.

widget_creation_type consumers: WidgetCreationPipeline.__init__ selected an enum by the string; it now calls param_info.creation_type(WidgetCreationType). ParameterFormManager._create_parameter_widget only logged it; it now logs the actual info class. All four string ClassVars are deleted.

NRA syntax/family evidence: 1,590 production/dependency modules, 9,953 classes, no parse errors. AST calls: ObjectState getattr 28 -> 13, FormBridge 17 -> 15; is_container/is_config_type calls zero, is_dataclass exactly one annotation compiler call. Dynamic resolution is not established by these syntax counts; the actual source-paired journey below checks the affected behavior.

Exact deleted Python type-name DTO declarations remain: source_type (1), object_state_path_type (2), raw_value_type (2), resolved_value_type (2), object_type (3), help_target_type (1). Genuine mime_type is retained. Deletion gates cover the exact declarations, not all *_type fields.

Verification: python-introspect public resolver/analyzer 64 passed; producer declaration/handle/inspection/scopeless/codegen/deletion cases 51 passed; bridge DTO/deletion cases 29 passed; Qt declared/numeric cases 66 passed. Actual source-paired native Qt input, main-thread dispatch, authenticated localhost ZMQ, DTO decode, reset, undo and disposal passed with null, empty string, text None, inheritance and absence. No provider and no installation.

Still unfinished: OpenHCS config startup reaches SourceBindingsConfig.__post_init__ but attempts to iterate Inherit in its source-binding normalization. Earlier removed has_value reads in the shared normalization owner are repaired; the whole remaining post-init normalizer needs a directive-aware owner pass. The old test_subfield_semantics.py still imports deleted MISSING/string APIs and fails collection; this failure is retained, not skipped or xfailed. Full OpenHCS/app acceptance and #32/#46 cutover are not complete. doctrine-segment remains the implementation owner.

Dependency: python-introspect ffcffaa, draft PR #25. No shared runtime publication/restart performed.
Loading