@@ -460,6 +460,7 @@ def get_draft(self, draft_id):
460460
461461 def export_draft_to_markdown (self , draft_id ):
462462 from substack .mdexport import document_to_markdown
463+ from substack .revision import compute_draft_revision , format_revision_marker
463464
464465 draft = self .get_draft (draft_id )
465466 draft_body = draft .get ("draft_body" )
@@ -474,9 +475,15 @@ def export_draft_to_markdown(self, draft_id):
474475 raise ValueError ("Malformed draft body: draft_body must be a JSON object" )
475476
476477 markdown , unsupported_nodes = document_to_markdown (draft_body )
478+ revision = compute_draft_revision (draft )
479+ revision_marker = format_revision_marker (revision )
480+ full_markdown = (
481+ f"{ revision_marker } \n \n { markdown } " if markdown else f"{ revision_marker } \n "
482+ )
477483 return {
478484 "draft" : draft ,
479- "markdown" : markdown ,
485+ "markdown" : full_markdown ,
486+ "revision" : revision ,
480487 "unsupported_nodes" : unsupported_nodes ,
481488 }
482489
@@ -496,13 +503,22 @@ def update_draft_from_markdown(
496503 dry_run : bool = False ,
497504 allow_unsupported_change : bool = False ,
498505 allow_image_replacement : bool = False ,
506+ expected_revision = None ,
507+ allow_conflict : bool = False ,
508+ force : bool = False ,
499509 ) -> dict :
500510 """
501511 Update an existing draft body from Markdown, with optional metadata changes.
502512 """
503513 from substack import mdrender
514+ from substack .exceptions import DraftConflictError
504515 from substack .mdexport import document_to_markdown
505516 from substack .post import Post
517+ from substack .revision import (
518+ compare_draft_revisions ,
519+ compute_draft_revision ,
520+ parse_revision_marker ,
521+ )
506522
507523 draft = self .get_draft (draft_id )
508524 draft_body = draft .get ("draft_body" )
@@ -516,6 +532,50 @@ def update_draft_from_markdown(
516532 if not isinstance (draft_body , dict ):
517533 raise ValueError ("Malformed draft body: draft_body must be a JSON object" )
518534
535+ current_rev = compute_draft_revision (draft )
536+
537+ target_expected_rev = None
538+ if expected_revision is not None :
539+ if isinstance (expected_revision , dict ):
540+ target_expected_rev = expected_revision
541+ elif isinstance (expected_revision , str ):
542+ target_expected_rev = {"revision" : expected_revision }
543+ else :
544+ raise ValueError ("expected_revision must be a revision dict or string" )
545+ else :
546+ target_expected_rev = parse_revision_marker (markdown )
547+
548+ override_enabled = bool (allow_conflict or force )
549+ conflict_detected = False
550+ mismatches = []
551+
552+ if target_expected_rev is None :
553+ conflict_detected = True
554+ mismatches = [
555+ "Markdown file is missing revision metadata (unprotected update); safe conflict comparison is impossible."
556+ ]
557+ if not override_enabled and not dry_run :
558+ raise DraftConflictError (
559+ "Refusing to update: Markdown file is missing revision metadata (unprotected update). "
560+ "Export the draft first or pass allow_conflict=True to proceed." ,
561+ mismatches = mismatches ,
562+ expected_revision = None ,
563+ current_revision = current_rev ,
564+ )
565+ else :
566+ mismatches = compare_draft_revisions (target_expected_rev , current_rev )
567+ if mismatches :
568+ conflict_detected = True
569+ if not override_enabled and not dry_run :
570+ details = "; " .join (mismatches )
571+ raise DraftConflictError (
572+ f"Refusing to update: remote draft has changed since export ({ details } ). "
573+ "Re-export the draft or pass allow_conflict=True to proceed." ,
574+ mismatches = mismatches ,
575+ expected_revision = target_expected_rev ,
576+ current_revision = current_rev ,
577+ )
578+
519579 _ , remote_unsupported = document_to_markdown (draft_body )
520580
521581 image_markers = []
@@ -657,7 +717,7 @@ def update_draft_from_markdown(
657717 if tags_list :
658718 tags_result = self .add_tags_to_post (draft_id , tags_list )
659719
660- return {
720+ result_payload = {
661721 "action" : "update" ,
662722 "draft_id" : draft_id ,
663723 "dry_run" : dry_run ,
@@ -666,7 +726,14 @@ def update_draft_from_markdown(
666726 "draft" : updated_draft ,
667727 "tags" : tags_result ,
668728 "unsupported_nodes" : unsupported_nodes ,
729+ "conflict" : conflict_detected ,
730+ "mismatches" : mismatches ,
731+ "expected_revision" : target_expected_rev ,
732+ "current_revision" : current_rev ,
669733 }
734+ if override_enabled and conflict_detected :
735+ result_payload ["override" ] = True
736+ return result_payload
670737
671738 def delete_draft (self , draft_id ):
672739 """
0 commit comments