@@ -68,14 +68,17 @@ def commit(
6868 ephemeral : bool = False ,
6969 actor : str | None = None ,
7070 reason : str | None = None ,
71- ):
71+ ) -> str :
7272 with self ._lock :
7373 validated_payload = self ._schema_registry .validate (fact .type , fact .payload )
7474 fact .payload = validated_payload
7575
7676 if session_id :
7777 fact .session_id = session_id
7878
79+ previous_state = None
80+ op = Operation .COMMIT
81+
7982 constraint = self ._constraints .get (fact .type )
8083
8184 if constraint and constraint .singleton_key :
@@ -86,26 +89,37 @@ def commit(
8689
8790 if matches :
8891 existing_raw = matches [0 ]
89- existing_id = existing_raw ["id" ]
90-
9192 if constraint .immutable :
9293 raise ConflictError (f"Immutable constraint violation: { fact .type } :{ key_val } " )
9394
94- before = copy .deepcopy (existing_raw )
95- fact .id = existing_id
96- after = fact .model_dump ()
95+ # We found a duplicate, so this is an UPDATE of an existing one
96+ previous_state = copy .deepcopy (existing_raw )
97+ fact .id = existing_raw ["id" ] # We replace the ID of the new fact with the old one
98+ op = Operation .UPDATE
99+
100+ if op != Operation .UPDATE :
101+ existing = self .storage .load (fact .id )
102+ if existing :
103+ previous_state = copy .deepcopy (existing )
104+ op = Operation .UPDATE
105+ else :
106+ op = Operation .COMMIT_EPHEMERAL if ephemeral else Operation .COMMIT
107+
108+ try :
109+ new_state = fact .model_dump ()
110+ self .storage .save (new_state )
111+ self ._log_tx (op , fact .id , previous_state , new_state , actor , reason )
112+ self ._notify_hooks (op , fact .id , fact )
97113
98- self .storage .save (after )
99- self ._log_tx (Operation .UPDATE , existing_id , before , after , actor , reason )
100- return existing_id
114+ return fact .id
101115
102- existing = self .storage .load (fact .id )
103- op = Operation .UPDATE if existing else (Operation .COMMIT_EPHEMERAL if ephemeral else Operation .COMMIT )
116+ except HookError as e :
117+ if op == Operation .UPDATE and previous_state :
118+ self .storage .save (previous_state )
119+ else :
120+ self .storage .delete (fact .id )
104121
105- self .storage .save (fact .model_dump ())
106- self ._log_tx (op , fact .id , existing , fact .model_dump (), actor , reason )
107- self ._notify_hooks (op , fact .id , fact )
108- return fact .id
122+ raise e
109123
110124 def update (self , fact_id : str , patch : dict [str , Any ], actor : str | None = None , reason : str | None = None ) -> str :
111125 with self ._lock :
0 commit comments