Skip to content

fetch_schema rewrites src/osw/model/entity.py as a side effect of ordinary read/write calls #141

Open

Description

@LukasGold

fetch_schema regenerates src/osw/model/entity.py inside the installed package. It is not called explicitly by the user, it is reached transitively from ordinary operations, currently create_or_update_entity and export_entity_jsonld in the MCP server (#133), and soon from the planned CLI.

Why it matters

Suggested direction

  • Make the write opt-in (explicit fetch_schema call or a flag), and have the transitive callers use an in-memory or cache-dir model instead.
  • Failing that, write to a user cache dir rather than the package directory.

Interim mitigation in the MCP/CLI layer: document the side effect in the help text of the affected commands.

Activity

  1. LukasGold commented on Aug 28, 2026

    @LukasGold
    ContributorAuthor

    @SimonStier picking this up from the #133 follow-up triage. Two things I could
    confirm while looking at it, and one of them makes the obvious fix harder than it
    looks. Worth a broader design discussion before anyone writes code, because it
    touches load_entity too, not just the MCP/CLI surface.

    The corruption is unrecoverable, not just untidy

    #125 shows fetch_schema writing a syntactically invalid entity.py and then
    reloading it. The part worth stating explicitly is what that leaves behind. I
    appended a single invalid line to src/osw/model/entity.py and ran osw --help:

    File "src/osw/controller/__init__.py", line 8, in <module>
      import osw.model.entity as model
    File "src/osw/model/entity.py", line 30
      broken: Foo | None = Field(default_factory=lambda :Foo.parse_obj(<object object at 0x1>))
                                                                      ^
    SyntaxError: invalid syntax
    

    So it is not only import osw.core that breaks, it is the CLI entry point
    itself. src/osw/cli/main.py:24 imports osw.service.ops, which reaches
    osw.core, which does import osw.model.entity as model at src/osw/core.py:40.
    (I restored the file; nothing is committed in that state.)

    Consequence for a reset tool

    A reset command that lives in the CLI or the MCP server cannot run in the exact
    state it exists to repair.
    Whatever we build has to be reachable without
    importing osw.core: a separate console script, a python -m osw.model.reset,
    or a subcommand registered before the ops import. This is the main reason I did
    not just implement the reset directly.

    The good news

    The pristine src/osw/model/entity.py is 28 lines, a pure re-export shim from
    opensemantic.core.v1 and opensemantic.base.v1. So "original state" is well
    defined and trivial to restore. The catch is that the first fetch_schema
    overwrites the only copy, so a pristine duplicate has to be shipped alongside it
    or embedded.

    Prevention may be cheaper than recovery

    _fetch_schema writes entity.py and then reloads it
    (src/osw/core.py:997-1001). Generating to a temp file, compile()-ing it, and
    only moving it into place on success turns #125 from a bricked install into a
    failed operation that leaves the previous working model untouched.

    That also covers the load_entity path, which is how #125 is actually triggered:
    lazy relation resolution -> resolver -> load_entity(autofetch_schema=True) ->
    fetch_schema. A reset tool alone would not help there, since the user has to
    notice and run it.

    Open questions for the discussion

    1. Should fetch_schema keep writing into the installed package at all, or write
      to a user cache dir? There is already a precedent to reuse in
      _default_state_dir() at src/osw/service/ledger.py:25-33, which needs no
      extra dependency.
    2. Should generation be validated before it replaces a working entity.py?
    3. Should autofetch_schema still default to True on the resolver path, given
      that is the path that bricks the install?
    4. If we do add a reset, where does it live so it survives the broken state?

    For context on the current blast radius: the MCP get_category_schema tool is
    not affected, it reads the jsonschema slot directly
    (src/osw/service/ops/schema.py:28-31). Only create_or_update_entity
    (src/osw/service/ops/entities.py:132-134, unconditional on every call) and
    export_entity_jsonld via autofetch_schema=True reach fetch_schema today.

  2. LukasGold commented on Sep 21, 2026

    @LukasGold
    ContributorAuthor

    Now observed through the CLI, which this issue anticipated.

    An agent driving only the generic osw CLI ran osw entity put once against a live instance. src/osw/model/entity.py appeared as modified in git status immediately afterwards, in an editable checkout. The agent flagged it unprompted as surprising for a command whose stated job is a remote write.

    Two further data points from the same session:

    • The regenerated file grows by roughly 5500 lines for a single category fetch, so the diff hides any real change to that file.
    • A clean src/osw/model/entity.py is a 28-line stub that re-exports from opensemantic.core.v1. No category class is shipped, so every model class exists only after fetch_schema has written that file. Any code wanting a category class in-process has no alternative today.

    The interim mitigation suggested here is in place: the side effect is documented in the help text of the affected commands.

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

Metadata

Metadata

Labels

bugSomething isn't workingenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions