mirror of
https://github.com/reconurge/flowsint.git
synced 2026-05-07 04:09:49 -05:00
[GH-ISSUE #127] [WiP] RFC: Agnostic Revert Feature #1051
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @gustavorps on GitHub (Feb 27, 2026).
Original GitHub issue: https://github.com/reconurge/flowsint/issues/127
RFC: Agnostic Revert Feature for User and Enricher Actions
1. Overview
This RFC proposes an architecture and implementation strategy for a generic and reusable "Undo" feature within Flowsint. The feature will allow the system to revert creations, deletions, and updates on graph elements (
FlowNodeandFlowEdge), whether these actions are initiated manually by a user or automatically by an enricher/pipeline/flow.The history of actions will be stored in PostgreSQL using a data model based on [Schema.org/Action](https://schema.org/Action), a proposal for consistent ontology and data models for future implementations, while the actual graph data remains in Neo4j.
2. Motivation
As users investigate data and run enrichers, the graph can quickly become cluttered or altered in unintended ways. Providing a robust undo feature allows users to easily roll back mistakes, revert noisy enricher outputs, and safely explore different investigation paths without permanently damaging the graph state.
3. Data Model Proposal
We will adapt the
schema.org/Actionmodel and map it to a PostgreSQL table (e.g.,action_history) to keep track of changes.@type(Action Type): e.g.,CreateAction,DeleteAction,UpdateAction,EnrichAction.agent: Who performed the action (a User ID or an Enricher ID).object: A JSON representation of the entity state before the action (useful for updates/deletes).result: A JSON representation of the entity state after the action (useful for creations/updates).target(EntryPoint): Specifies the mechanism to revert the action. It will store the repository name, function, and arguments required to execute the undo.identifier: A unique ID or trace ID grouping multiple sub-actions (e.g., an enricher creating 10 nodes will share the same transaction identifier).Example Postgres Schema representation:
4. Target Entities
The undo system will primarily target the following core components:
src/flowsint_core/core/types.py:FlowNode: Undoing node creations, updates, and deletions.src/flowsint_core/core/types.py:FlowEdge: Undoing edge creations, updates, and deletions.src/flowsint_core/core/enricher_base.py:execute: A macro-action that encompasses multiple node and edge operations. Reverting an enricher execution means looking up all actions tied to the enricher'sidentifier(transaction ID) and undoing them in reverse order.5. Architectural Design Patterns
To implement this agnostically, we can evaluate two core Object-Oriented Design Patterns.
Option A: The Command Pattern (Highly Recommended)
The Command pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
In this architecture, every mutation to the graph (Create, Update, Delete) is encapsulated in a Command object that knows how to execute itself and how to undo itself.
Python Implementation Example
Usage Example for the "Undo" Resolver
When a user clicks "Undo" for a specific transaction (e.g., reverting a noisy enricher run), the orchestrator fetches the history from Postgres and reconstructs the objects:
Pros:
target.EntryPointmapping is easily achieved since the command knows exactly which repository method to call for the undo.CreateFlowNodeCommandandCreateFlowEdgeCommandinto aMacroCommandto represent anEnricher.execute()run.Cons:
Option B: The Memento Pattern
The Memento pattern captures and externalizes an object's internal state without violating encapsulation, allowing the object to be restored to this state later.
Python Implementation Example
Pros:
Cons:
7. Preflight Check
To ensure that an undo operation is safe to execute and won't inadvertently corrupt the graph or overwrite newer changes, we should introduce a Preflight Check feature.
This feature will simulate or validate the undo operation before committing to it. For example, before reverting an update, the system should verify that no other user or enricher has modified the node in the meantime.
Here are the implementation steps for the Preflight Check feature:
Step 1: Define a Preflight Result Model
Create a standard response object that the preflight checks will return. This allows the system to differentiate between blocking errors (cannot undo) and warnings (can undo, but the user should be notified).
Step 2: Extend the Command Interface
Add a
preflight_check()method to the abstractCommandbase class. Every command type must implement its own validation logic.Step 3: Implement Check Logic per Command Type
Each specific command implements checks relevant to its operation.
Example 1: Undoing a Creation (Delete the node)
If we are undoing a node creation, we need to check if the node still exists and if it has new edges attached to it that weren't part of this transaction.
Example 2: Undoing an Update (Revert to previous state)
If we are undoing an update, we must check for "intervening edits." If the current state of the node in Neo4j doesn't match the state we recorded after our action, someone else modified it.
Step 4: Integrate Preflight into the Undo Orchestrator
Update the resolver that handles user requests to undo a transaction. It should run the preflight checks before triggering any database mutations.
Benefits of this Implementation:
7. Recommended Approach
The Command Pattern is highly recommended for this implementation, potentially augmented with elements of Event Sourcing.
Implementation Steps:
ActionHistoryServicethat acts as the Command Invoker. When an enricher callsexecute(), all resulting node/edge creations are routed through this service. The service executes the command on Neo4j and immediately serializes thedump_json()to PostgreSQL.Enricher.execute()to generate a uniqueidentifier(UUID) per execution. AllFlowNodeandFlowEdgecommands emitted during this execution will share this identifier.identifier, sort them in reverse chronological order by timestamp, and execute the repository logic defined in thetarget(EntryPoint) field.