@@ -540,23 +540,30 @@ export class LocalBackend implements KnowledgeBackend {
540540 async updateMemory ( input : UpdateMemoryInput ) : Promise < Memory > {
541541 const existing = await this . readMemory ( input . id ) ;
542542 if ( ! existing ) throw new Error ( `memory ${ input . id } not found` ) ;
543+ if ( input . expectedVersionId && input . expectedVersionId !== existing . versionId ) {
544+ throw new Error ( "content_version_conflict" ) ;
545+ }
543546 const ts = this . now ( ) ;
544- this . db
547+ const nextVersionId = this . genId ( ) ;
548+ const result = this . db
545549 . prepare (
546550 `UPDATE memories SET title = ?, content = ?, node_id = ?, version_id = ?, version_number = ?,
547- last_edited_by = ?, last_edited_at = ?, updated_at = ? WHERE id = ?` ,
551+ last_edited_by = ?, last_edited_at = ?, updated_at = ?
552+ WHERE id = ? AND version_id = ?` ,
548553 )
549554 . run (
550555 input . title ?? existing . title ,
551556 input . content ?? existing . content ,
552557 input . nodeId ?? existing . nodeId ,
553- this . genId ( ) ,
558+ nextVersionId ,
554559 existing . versionNumber + 1 ,
555560 this . principal ,
556561 ts ,
557562 ts ,
558563 input . id ,
564+ input . expectedVersionId ?? existing . versionId ,
559565 ) ;
566+ if ( result . changes !== 1 ) throw new Error ( "content_version_conflict" ) ;
560567 const updated = await this . readMemory ( input . id ) ;
561568 if ( ! updated ) throw new Error ( `memory ${ input . id } vanished after update` ) ;
562569 // Re-embed only when the embedded text actually changed. A node-only re-home
@@ -734,24 +741,31 @@ export class LocalBackend implements KnowledgeBackend {
734741 async updateRule ( input : UpdateRuleInput ) : Promise < Rule > {
735742 const existing = await this . readRule ( input . id ) ;
736743 if ( ! existing ) throw new Error ( `rule ${ input . id } not found` ) ;
744+ if ( input . expectedVersionId && input . expectedVersionId !== existing . versionId ) {
745+ throw new Error ( "content_version_conflict" ) ;
746+ }
737747 const ts = this . now ( ) ;
738- this . db
748+ const nextVersionId = this . genId ( ) ;
749+ const result = this . db
739750 . prepare (
740751 `UPDATE rules SET name = ?, content = ?, scope_type = ?, priority = ?, version_id = ?,
741- version_number = ?, last_edited_by = ?, last_edited_at = ?, updated_at = ? WHERE id = ?` ,
752+ version_number = ?, last_edited_by = ?, last_edited_at = ?, updated_at = ?
753+ WHERE id = ? AND version_id = ?` ,
742754 )
743755 . run (
744756 input . name ?? existing . name ,
745757 input . content ?? existing . content ,
746758 input . scopeType ?? existing . scopeType ,
747759 input . priority ?? existing . priority ,
748- this . genId ( ) ,
760+ nextVersionId ,
749761 existing . versionNumber + 1 ,
750762 this . principal ,
751763 ts ,
752764 ts ,
753765 input . id ,
766+ input . expectedVersionId ?? existing . versionId ,
754767 ) ;
768+ if ( result . changes !== 1 ) throw new Error ( "content_version_conflict" ) ;
755769 if ( input . nodeId ) {
756770 // Re-home: replace any existing attachments with one pointing at the new node.
757771 this . db . prepare ( "DELETE FROM node_rules WHERE rule_id = ?" ) . run ( input . id ) ;
@@ -858,6 +872,9 @@ export class LocalBackend implements KnowledgeBackend {
858872 async updateSkill ( input : UpdateSkillInput ) : Promise < Skill > {
859873 const existing = await this . readSkill ( input . id ) ;
860874 if ( ! existing ) throw new Error ( `skill ${ input . id } not found` ) ;
875+ if ( input . expectedVersionId && input . expectedVersionId !== existing . versionId ) {
876+ throw new Error ( "content_version_conflict" ) ;
877+ }
861878 const ts = this . now ( ) ;
862879 // Patch only supplied fields (null clears description/github_url; never reorder).
863880 const sets = [
@@ -880,8 +897,11 @@ export class LocalBackend implements KnowledgeBackend {
880897 sets . push ( "content_fetched_at = ?" ) ;
881898 vals . push ( ts ) ;
882899 }
883- vals . push ( input . id ) ;
884- this . db . prepare ( `UPDATE skills SET ${ sets . join ( ", " ) } WHERE id = ?` ) . run ( ...vals ) ;
900+ vals . push ( input . id , input . expectedVersionId ?? existing . versionId ) ;
901+ const result = this . db
902+ . prepare ( `UPDATE skills SET ${ sets . join ( ", " ) } WHERE id = ? AND version_id = ?` )
903+ . run ( ...vals ) ;
904+ if ( result . changes !== 1 ) throw new Error ( "content_version_conflict" ) ;
885905 if ( input . nodeId ) {
886906 this . db . prepare ( "DELETE FROM node_skills WHERE skill_id = ?" ) . run ( input . id ) ;
887907 this . db
0 commit comments