diff --git a/backend/config/entity_references.py b/backend/config/entity_references.py new file mode 100644 index 00000000..77b8cf13 --- /dev/null +++ b/backend/config/entity_references.py @@ -0,0 +1,110 @@ +"""Every cross-entity id field on a backend model: what entity it points at, and where that +entity is resolved from an id. + +OpenSwarm stores entities as JSON records that point at each other with bare strings. A workflow +holds `edit_agent_session_id`, an app record holds `workspace_id`, a dashboard card holds +`session_id`. Nothing at the type level says the referent exists, so a reader that forgets the miss +renders a blank instead of a designed empty state. This module is the one place that says what each +pointer means, and the `dangling-refs` linter check (linter/checks/dangling_refs.py) fails any new +`*_id` / `*_ids` field on a backend model that is not declared here. + +Data only, no logic: nothing resolves a pointer for you. The `EntityStore` rows name the function +that does, and the linter checks those names still exist so a rename can't quietly rot this file. +""" + +from __future__ import annotations + +from enum import Enum +from typing import List + +from pydantic import BaseModel, ConfigDict + + +class EntityKind(str, Enum): + """A record type other records point at by id.""" + + SESSION = "session" + DASHBOARD = "dashboard" + WORKFLOW = "workflow" + WORKFLOW_RUN = "workflow_run" + OUTPUT = "output" + WORKSPACE = "workspace" + + +class EntityStore(BaseModel): + """Where one entity kind is looked up by id, so a reader knows what a pointer resolves against.""" + + model_config = ConfigDict(validate_assignment=True, frozen=True) + + kind: EntityKind + module: str + lookup: str + + +class EntityReference(BaseModel): + """One id field on one backend model, and the entity kind it points at.""" + + model_config = ConfigDict(validate_assignment=True, frozen=True) + + module: str + model: str + field: str + target: EntityKind + + +ENTITY_STORES: List[EntityStore] = [ + EntityStore(kind=EntityKind.SESSION, module="backend.apps.agents.manager.session.session_store", lookup="load_session_data"), + EntityStore(kind=EntityKind.DASHBOARD, module="backend.apps.dashboards.dashboards", lookup="load"), + EntityStore(kind=EntityKind.WORKFLOW, module="backend.apps.workflows.storage", lookup="get_workflow"), + # No get_run(run_id) exists; a run is found by scanning the listing, which is why a stale + # last_run_id reads as "no run" rather than as an error. + EntityStore(kind=EntityKind.WORKFLOW_RUN, module="backend.apps.workflows.storage", lookup="list_all_runs"), + EntityStore(kind=EntityKind.OUTPUT, module="backend.apps.outputs.workspace_io", lookup="load_output"), + # A workspace is a folder on disk, not a record, so its only by-id lookup is the read route. + EntityStore(kind=EntityKind.WORKSPACE, module="backend.apps.outputs.outputs", lookup="read_workspace"), +] + +CROSS_ENTITY_REFERENCES: List[EntityReference] = [ + EntityReference(module="backend.apps.agents.core.models", model="AgentConfig", field="dashboard_id", target=EntityKind.DASHBOARD), + EntityReference(module="backend.apps.agents.core.models", model="AgentConfig", field="selected_app_output_ids", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.agents.core.models", model="AgentConfig", field="workflow_edit_id", target=EntityKind.WORKFLOW), + EntityReference(module="backend.apps.agents.core.models", model="AgentConfig", field="workflow_run_id", target=EntityKind.WORKFLOW_RUN), + EntityReference(module="backend.apps.agents.core.models", model="AgentSession", field="dashboard_id", target=EntityKind.DASHBOARD), + EntityReference(module="backend.apps.agents.core.models", model="AgentSession", field="parent_session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.agents.core.models", model="AgentSession", field="workflow_edit_id", target=EntityKind.WORKFLOW), + EntityReference(module="backend.apps.agents.core.models", model="AgentSession", field="workflow_run_id", target=EntityKind.WORKFLOW_RUN), + EntityReference(module="backend.apps.agents.core.models", model="ApprovalRequest", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.agents.manager.Messaging", model="QueuedMessage", field="selected_app_output_ids", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.agents.manager.streaming.HookContext", model="HookContext", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.dashboard_layout.models", model="CardPosition", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.dashboard_layout.models", model="ViewCardPosition", field="output_id", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.dashboards.models", model="BrowserCardPosition", field="dashboard_id", target=EntityKind.DASHBOARD), + EntityReference(module="backend.apps.dashboards.models", model="CardPosition", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.dashboards.models", model="DashboardLayout", field="expanded_session_ids", target=EntityKind.SESSION), + EntityReference(module="backend.apps.dashboards.models", model="ViewCardPosition", field="output_id", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.outputs.models", model="AgentCreateAppRequest", field="parent_session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.outputs.models", model="Output", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.outputs.models", model="Output", field="workspace_id", target=EntityKind.WORKSPACE), + EntityReference(module="backend.apps.outputs.models", model="OutputCreate", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.outputs.models", model="OutputCreate", field="workspace_id", target=EntityKind.WORKSPACE), + EntityReference(module="backend.apps.outputs.models", model="OutputExecute", field="output_id", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.outputs.models", model="OutputExecuteResult", field="output_id", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.outputs.models", model="OutputUpdate", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.outputs.models", model="OutputUpdate", field="workspace_id", target=EntityKind.WORKSPACE), + EntityReference(module="backend.apps.outputs.models", model="PublishPreflightRequest", field="output_id", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.outputs.models", model="PublishRequest", field="output_id", target=EntityKind.OUTPUT), + EntityReference(module="backend.apps.outputs.models", model="WorkspaceSeedRequest", field="workspace_id", target=EntityKind.WORKSPACE), + EntityReference(module="backend.apps.skills.models", model="SkillWorkspaceSeedRequest", field="workspace_id", target=EntityKind.WORKSPACE), + EntityReference(module="backend.apps.workflows.models", model="AskRunBody", field="run_id", target=EntityKind.WORKFLOW_RUN), + EntityReference(module="backend.apps.workflows.models", model="MissedRun", field="workflow_id", target=EntityKind.WORKFLOW), + EntityReference(module="backend.apps.workflows.models", model="Workflow", field="dashboard_id", target=EntityKind.DASHBOARD), + EntityReference(module="backend.apps.workflows.models", model="Workflow", field="edit_agent_session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.workflows.models", model="Workflow", field="last_run_id", target=EntityKind.WORKFLOW_RUN), + EntityReference(module="backend.apps.workflows.models", model="Workflow", field="last_test_session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.workflows.models", model="Workflow", field="schedule_agent_session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.workflows.models", model="Workflow", field="source_session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.workflows.models", model="WorkflowCreate", field="dashboard_id", target=EntityKind.DASHBOARD), + EntityReference(module="backend.apps.workflows.models", model="WorkflowCreate", field="source_session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.workflows.models", model="WorkflowRun", field="session_id", target=EntityKind.SESSION), + EntityReference(module="backend.apps.workflows.models", model="WorkflowRun", field="workflow_id", target=EntityKind.WORKFLOW), +]