Skip to content

paper: match 4.0.0 - #59

Open
trissim wants to merge 1 commit into
mainfrom
paper/match-4.0.0
Open

trissim wants to merge 1 commit into
mainfrom
paper/match-4.0.0

Conversation

@trissim

@trissim trissim commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Brings paper.md in line with the code at v4.0.0 (02393c4). main is one commit ahead of the tag (e3b06bf, a coverage badge update that touches only .github/badges/coverage.svg), so the branch is based on main and every claim was checked against the tag's source, which is identical. No library code changes. Paths below are under src/objectstate/ unless they start with tests/, docs/ or name another repository.

Nothing was executed: no environment was created and neither the library nor its tests were run. Each name in the code examples was checked by reading the source for its existence, its import path and its signature (ObjectState.__init__ object_state.py:145, get_resolved_value :1187, get_provenance :1361, update_parameter :1045, dirty_fields :1984, type_for_path :537; ObjectStateRegistry.register object_state_registry.py:392, atomic :933, time_travel_back :2068; LazyDataclassFactory.make_lazy_simple lazy_factory.py:974, mark_global_config_type :382; set_base_config_type config.py:21; set_global_config global_config.py:51; exports in __init__.py:55-122 and :215-217). The examples were not run. Each follows a test in the repository, with the expected values taken from that test's assertions: the main example from tests/test_resolved_values_follow_registered_ancestors.py:57-75, 137-151, 175-177, 336-348, the function example from tests/test_callable_baseline.py:50-52, 73-77.

Claim ledger

# Old claim Status What it became Source
1 Dual-axis inheritance: values resolve through context hierarchy and class inheritance true kept; the order of the two axes is now stated (see 2) dual_axis_resolver.py:153-205
2 "Values resolve through context stack and class MRO simultaneously"; a step's value "first checks the step config, then the pipeline config, then the plate config, then global defaults" misleading Resolution is class first: the MRO is the outer loop and the scopes the inner one, so a value on a more specific class in an outer scope beats a value on a base class in an inner scope. The paper states the order dual_axis_resolver.py:179-197; tests/test_resolution_precedence_order.py:246-261
3 "Built on Python's standard library with zero dependencies" (Summary, table, Research Application) false "three packages (dill, pycodify, python-introspect)" pyproject.toml:26-30; object_state.py:904; object_state_registry.py:2362; codegen.py:21; context_manager.py:27
4 Lazy dataclass factory uses __getattribute__ interception true kept, with the factory call named lazy_factory.py:624-660, 974
5 "Type-safe lazy evaluation" unverifiable ("type-safe" is not something the code establishes) "generated dataclasses whose None fields resolve on attribute access" lazy_factory.py:631-652
6 "Uses contextvars for thread-safe context management" true for the entered context only "a stack of layers held in a contextvars variable"; the registry and the global config are per process and "the library does no locking" context_manager.py:97-99; global_config.py:22-24, 31; no Lock in the package
7 "Resolution returns not just the value but its source, which scope and type provided it" true kept and made the centre of the Design section: ResolvedField(value, source_scope, source_type) from the one resolver dual_axis_resolver.py:19-32, 114-150; lazy_factory.py:652; object_state.py:2222-2226
8 state.get_parameter_with_provenance("sigma") returns (2.0, "step_config") false: no such method at the tag, and the return shape never matched state.get_provenance(path) returns (scope_id, source_type), or None when the state sets the field itself object_state.py:1361-1395
9 "Every parameter value is tagged with its source" false Only fields the state leaves unset have a source; a field the state sets itself reports None object_state.py:1387-1389, 1835-1837
10 Provenance "tracked via a parallel dictionary structure" true kept in one clause ("stored in a dictionary beside the resolved values and written in the same pass") object_state.py:228, 2222-2226
11 Provenance enables "Dirty Tracking: Distinguish between user-set and inherited values" false Dirty fields are the fields whose live and saved resolved values differ; provenance plays no part object_state.py:2007-2040
12 "empty fields show inherited values in gray, while user-set values appear in bold"; "flash animations"; "They can click to see that the step config has no override..." unverifiable in this repository (widget behaviour lives in pyqt-reactive) removed; what remains is what this library supplies: placeholder text and get_provenance placeholder.py:60-63, 92-110
13 Callables: "extracts parameters from the function signature using Python's inspect module" misattributed Extraction is delegated to python-introspect (UnifiedParameterAnalyzer.analyze) object_state.py:892-907, 2559
14 "ObjectState extracts {sigma: None, preserve_range: None} from signature" for gaussian_filter(image, sigma=None, preserve_range=None) unverifiable (which parameters are extracted is decided in python-introspect, not here) example replaced by the one the test suite asserts tests/test_callable_baseline.py:50-52, 73-77
15 "The None sentinel works identically for function kwargs: unset parameters inherit from the context hierarchy"; "function parameters participate in the same dual-axis inheritance"; "A user can set a global default for sigma, override it per-pipeline, and override it again per-step" false A leaf parameter whose owner is a function is not resolved: "a function parameter left as None stays None. Inheritance applies to fields of lazy dataclasses" object_state.py:2554-2555 and 2610 (the owner is the function), 2208-2211 and 2237-2239 (owners without lazy resolution keep the raw value), 1863-1872
16 Example: with config_context(global_cfg): with config_context(pipeline_cfg): ... state = ObjectState(step, scope_id="/step_0"), implying the entered contexts feed the state false at 4.0.0 A state resolves from the global config and the states registered above it; an entered config_context is set aside. The example now registers a plate state and reads a step state below it context_manager.py:581-640 (the three context variables are reset at :624-629); object_state.py:785-815, 2138-2162; tests/test_resolved_values_follow_registered_ancestors.py:812-824
17 Example: resolve_with_provenance(StepConfig, "batch_size") gives scope="/pipeline" false A layer entered without scope_id is reported with source scope None; the example no longer shows this call, the prose gives the return type context_manager.py:166-171, 294; tests/test_one_field_resolution.py:90-106
18 Example: scope ids "/step_0", "/plate::step_0::func_0" misleading Scopes are segments joined by ::; a leading / is not a hierarchy. Examples use "plate", "plate::step" scope_key.py:9-22, 75-81
19 Example: print(step.batch_size) # 64 (restored) after time_travel_back() misleading update_parameter edits the state's working copy, not the wrapped object, so the object's attribute never changed. The example reads state.get_resolved_value object_state.py:1116-1120
20 Example omits imports, set_base_config_type, and a global config incomplete (would raise RuntimeError: Base config type not set) complete, with imports config.py:40-56; context_manager.py:216, 664-687
21 "Saved/live state separation with automatic dirty tracking" true kept object_state.py:235-236, 1984-1987, 2243, 2389
22 "Git-like history: Undo/redo with branching timelines"; "DAG-based undo/redo history" true (each snapshot has one parent, so the graph is a tree with named branches; there is no merge) "undo/redo over snapshots of all registered states, with branches"; the automatic branch on divergence is described snapshot_model.py:39-51, 118-128; object_state_registry.py:1196-1215, 2068, 2087, 2182
23 "ObjectState uniquely combines" unverifiable "uniquely" removed
24 Feature table: 10 rows by 5 tools two cells false, one row false, the rest not checkable here table removed; one sentence per tool. "Zero dependencies: MobX no" is false (the mobx package declares no dependencies). "Zero dependencies: ObjectState yes" is false (see 3) mobxjs/mobx packages/mobx/package.json; pyproject.toml:26-30
25 "Sacred tracks experiments post-hoc but doesn't determine values at runtime" false "Sacred configures an experiment, injects parameters into its functions, and records the configuration of each run" IDSIA/sacred README ("Config Scopes", "Config Injection", "Observers")
26 "Hydra ... once composed, values are static"; "MobX offers reactive state but no inheritance semantics" unverifiable negative statements about other tools removed; each tool is described by what its own repository says it does
27 "UI: Empty fields display placeholder text showing the live-resolved inherited value (via provenance)" misattributed LazyDefaultPlaceholderService formats the value a lazy instance resolves; provenance is not involved placeholder.py:33-63
28 OpenHCS has four scopes: Global, Plate, Pipeline, Step, with num_workers=8, output_dir="/results", compression="gzip", sigma=2.0 partly: the scopes exist (plus one per step function); the example values are illustrative, not from OpenHCS scopes kept without invented values OpenHCS openhcs/pyqt_gui/app.py:245-249, openhcs/authoring/session/pipelines.py:76-90, 192, 375-379
29 "Without ObjectState, each scope level would require explicit parameter passing through 20+ function calls" unverifiable (no count in either repository) removed
30 "OpenHCS pipelines process thousands of images per experiment" unverifiable here removed
31 "The UI immediately shows the inherited value via provenance tracking" misattributed (see 27) "the form shows the inherited value as a placeholder, and the help control of the field reads get_provenance to find the source" pyqt-reactive widgets/shared/clickable_help_components.py:533
32 Experiment branching: time-travel back, try another configuration, "creating a branching history" true kept, with the mechanism (the abandoned future is kept as an automatically named branch) object_state_registry.py:1200-1215; pyqt-reactive widgets/time_travel.py:54-57
33 "This is more powerful than traditional undo/redo" marketing removed
34 "When a user edits a parameter, it's marked dirty" imprecise A field is dirty when its live and saved resolved values differ, so a state is also dirty when an unsaved edit above it changes what it inherits object_state.py:2007-2040; tests/test_resolved_values_follow_registered_ancestors.py:219-228
35 repository-code: https://github.com/trissim/objectstate stale https://github.com/OpenHCSDev/ObjectState. The old URL is a separate repository last pushed 2026-01-12, not a redirect gh api repos/trissim/ObjectState; pyproject.toml:49-51
36 AI disclosure: "the complete ObjectState software implementation [is] developed without AI assistance" false at the tag see "For the owner" 1 git log trailers on main

19 claims false, misleading, misattributed or stale (2, 3, 8, 9, 11, 13, 15, 16, 17, 18, 19, 20, 24, 25, 27, 31, 34, 35, 36); 8 unverifiable and removed or narrowed (5, 12, 14, 23, 26, 29, 30, and the external cells of 24).

New statements

  • One resolver. resolve_with_provenance returns ResolvedField(value, source_scope, source_type); one function walks layers and classes (_resolve_in_layers); lazy attribute access takes .value of the same result and ObjectState unpacks all three. dual_axis_resolver.py:19-32, 114-150, 153-205; lazy_factory.py:617-620, 652; object_state.py:1877, 2222.
  • The active context is a stack of layers, each one object with the scope id it was entered under. context_manager.py:45-99, 288-296.
  • Class-first precedence, checked for every subset of nine (class, scope) sites. dual_axis_resolver.py:153-197; tests/test_resolution_precedence_order.py:86-91, 246-261, 264.
  • A state resolves against a stack built from the global config and the registered ancestors; the caller's entered context is set aside. context_manager.py:581-640; object_state.py:785-815; object_state_registry.py:475-488.
  • Each resolved snapshot keeps ResolutionSources; every resolved read compares them by identity and resolves again only the unset fields that can inherit a differing (config type, field), or every field when other scopes are registered. resolution_sources.py:36-83, 86-128; object_state.py:724-784, 834-855.
  • A lazy object no state owns reads the entered config_context. lazy_factory.py:648-652; tests/test_resolved_values_follow_registered_ancestors.py:849-865.
  • One holder of the global config: the state registered at scope "" whose object is of that type; otherwise the object given to set_global_config; one per config type per process. global_config.py:1-25, 51-70, 83-96; object_state_registry.py:444-457; tests/test_one_holder_of_the_global_config.py:250-281.
  • No locking. No Lock or RLock anywhere in src/objectstate; docs/state_propagation.rst "Limits".
  • Function parameters do not inherit. See ledger 15.
  • Python 3.11 or later. pyproject.toml:12.

For the owner

  1. AI usage disclosure. The paper said the software was "developed without AI assistance". git log main has Co-Authored-By: Claude trailers on 37 commits: Claude Sonnet 4.5 (4, 2025-12-27 to 2026-01-24), Claude Opus 4.5 (1, 2026-01-24), Claude Opus 5.5 (32, 2026-10-11 to 2026-10-12), plus two "Generated with Claude Code" bodies on 2025-12-27. The 4.0.0 design this paper now describes came in through those October commits (d85e69c, b5b06ab, 472f82e, 9e758b4). I replaced the sentence with one that says the commits carry these trailers and that this revision was made with Claude Opus 5.5. This is your attestation, not mine: please reword it as you see fit. JOSS asks for an accurate statement.
  2. Function parameters do not inherit (ledger 15). The old paper presented this as a feature with a "Practical Impact" paragraph. If OpenHCS gets that behaviour, it gets it somewhere other than ObjectState field resolution; I could not find it in this repository and did not describe it.
  3. OpenHCS is not on 4.0.0. pyproject.toml on OpenHCS origin/main pins objectstate>=3.0.4,<4, and openhcs/pyqt_gui/app.py:240 still calls ensure_global_config_context, which 4.0.0 removed. The Research Application section describes usage I read in that checkout (a global state registered at "", states per plate, pipeline, step and function, undo bound to the registry, get_provenance read by pyqt-reactive). It makes no claim that OpenHCS runs 4.0.0.
  4. State of the Field. The feature table is gone. Two of its statements were wrong when checked against the tools' own repositories (ledger 24, 25) and most other cells are negatives about other projects that I could not check. The replacement describes each tool in one clause. Sacred and MobX were checked against their READMEs and pydantic-settings against its repository description; the Hydra clause ("from files and command-line overrides") was not checked against a source in this job. If you want a table back, each cell needs a reference.
  5. Not verified. That the examples run (they were not executed; they follow the tests cited above, with one difference: the main example builds the step state after the plate is registered, which test_both_orders_of_building_give_the_same_reads covers). The first paragraph of Statement of Need (kept as written, with its citations). The relevance of @Claessen2000 ("Typed logical variables in Haskell", noted in the bib as a discussion of lazy evaluation) to lazy dataclass fields; the citation was kept where it was. https://objectstate.readthedocs.io answers with a redirect; I did not check what the hosted docs show for 4.0.0.
  6. Left alone. The title, authors, acknowledgements and date: 13 January 2026. You may want to update the date.
  7. Bibliography. paper.bib is unchanged. No reference was added. @Colvin2023 (already in the bib, https://github.com/pydantic/pydantic-settings, confirmed to exist with gh api) is now cited; before, pydantic-settings appeared only as an uncited table column. All nine cited keys resolve. The bib holds 24 entries; 15 are not cited (16 before this change).
  8. Word count. wc -w paper.md: 1365 before, 1510 after (10.6% more, code included). The two added Design paragraphs (state resolution, the global config holder) account for the increase.

🤖 Generated with Claude Code

Bring paper.md in line with the code at v4.0.0: one resolver that returns
each value with its source, class-first precedence, state resolution from
the registered hierarchy only, resolved snapshots that keep what they were
computed from, and the registered global state as the one holder of the
global config. Replace code examples that used removed or misdescribed API,
remove the zero-dependency claim and the feature table, and correct the
repository URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 20:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants