name: canonworks-serial-canon-state-sync
version: '1.0'
description: CanonWorks generic post-episode canon state sync. This preserves the working example state-sync workflow's
  top-level step contract and adds project-config normalization plus target/backfill inputs for any Kumiho canon project.
inputs:
- name: project_config_yaml
  type: string
  required: true
  description: CanonWorks project config YAML/JSON, or a file path generated by canonworks_commit. Operators should normally
    use canonworks_sync_state, which passes the stored config path automatically.
- name: target_episode_number
  type: string
  required: false
  default: ''
  description: Specific episode number to sync instead of latest production-ready episode.
- name: target_episode_kref
  type: string
  required: false
  default: ''
  description: Exact source episode revision kref for targeted sync/backfill.
- name: target_patch_kref
  type: string
  required: false
  default: ''
  description: Exact canon patch revision kref for targeted sync/backfill.
- name: bootstrap_mode
  type: string
  required: false
  default: 'off'
  description: off|sequential_backfill|as_of_snapshot.
- name: apply_mode
  required: false
  default: propose_only
  description: 보수적 적용 모드. propose_only이면 관계 변화와 major timeline 변화는 approval-required notes로만 남긴다. apply_approved일 때만 승인된 risky
    delta를 current snapshot에 반영할 수 있다.
- name: continuity_context
  required: false
  description: 운영자가 제공하는 추가 연속성 맥락 또는 승인 메모.
- name: review_focus
  required: false
  description: 이번 sync에서 특별히 더 엄격하게 볼 캐릭터, 관계, 사건, 떡밥.
steps:
- id: project-config
  name: canonworks_project_config
  type: python
  position:
    x: -1240
    y: -620
  description: Parse and normalize CanonWorks project_config_yaml for all project-specific values.
  python:
    code: |
      import json
      import re
      import sys
      from pathlib import Path

      try:
          import yaml
      except Exception:
          yaml = None

      raw_payload = sys.stdin.buffer.read().decode('utf-8', errors='replace')
      payload = json.loads(raw_payload or '{}')
      args = payload.get('args') or {}
      context_inputs = ((payload.get('context') or {}).get('inputs') or {})
      raw = str(args.get('project_config_yaml') or context_inputs.get('project_config_yaml') or '').strip()

      if raw and len(raw) < 500 and ('\n' not in raw) and Path(raw).expanduser().exists():
          raw = Path(raw).expanduser().read_text(encoding='utf-8')
      if not raw:
          raise SystemExit('project_config_yaml is required')

      data = yaml.safe_load(raw) if yaml else json.loads(raw)
      if not isinstance(data, dict):
          raise SystemExit('project_config_yaml must parse to a mapping')
      cfg = data.get('canon_project') or data.get('story_project') or data
      if not isinstance(cfg, dict):
          raise SystemExit('canon_project must be a mapping')

      def first(*values):
          for value in values:
              if value is not None and str(value).strip():
                  return str(value).strip()
          return ''

      def slugify(value):
          value = re.sub(r'[^A-Za-z0-9가-힣_-]+', '-', str(value).strip()).strip('-')
          return value.lower() or 'canonworks-project'

      project_id = first(cfg.get('project'), cfg.get('project_id'), cfg.get('kumiho_project'))
      title = first(cfg.get('title'), cfg.get('series_title'), cfg.get('name'))
      story_slug = first(cfg.get('id'), cfg.get('slug'), slugify(title))
      if not project_id:
          raise SystemExit('canon_project.project is required')
      if not title:
          raise SystemExit('canon_project.title is required')

      spaces = cfg.get('spaces') if isinstance(cfg.get('spaces'), dict) else {}
      bundles = cfg.get('bundles') if isinstance(cfg.get('bundles'), dict) else {}
      krefs = cfg.get('krefs') if isinstance(cfg.get('krefs'), dict) else {}
      naming = cfg.get('naming') if isinstance(cfg.get('naming'), dict) else {}

      def space(key, default_name):
          value = first(spaces.get(key), default_name)
          if '/' in value or value.startswith(project_id):
              return value.strip('/')
          return f'{project_id}/{value.strip("/")}'

      def bundle(key, default_suffix):
          return first(bundles.get(key), f'{story_slug}-{default_suffix}')

      def kref(key, default_path):
          return first(krefs.get(key), f'kref://{project_id}/{default_path}')

      def as_yamlish(value):
          if value in (None, ''):
              return ''
          try:
              if yaml:
                  return yaml.safe_dump(value, allow_unicode=True, sort_keys=False).strip()
          except Exception:
              pass
          return json.dumps(value, ensure_ascii=False)

      char_snapshot = first(naming.get('character_state_snapshot_name'), 'current-character-state-snapshot')
      rel_snapshot = first(naming.get('relationship_state_snapshot_name'), 'current-relationship-state-snapshot')
      timeline_snapshot = first(naming.get('timeline_progress_snapshot_name'), 'current-timeline-progress-snapshot')
      storyline_snapshot = first(naming.get('storyline_progress_snapshot_name'), 'current-storyline-progress-snapshot')
      foreshadow_snapshot = first(naming.get('foreshadow_progress_snapshot_name'), 'current-foreshadow-progress-snapshot')
      episode_name_prefix = first(naming.get('episode_name_prefix'), 'ep')
      episode_id_prefix = first(naming.get('episode_id_prefix'), 'ep')
      volume_bundle_prefix = first(naming.get('volume_bundle_prefix'), f'{story_slug}-vol')

      out = {
          'project_id': project_id,
          'story_slug': story_slug,
          'title': title,
          'language': first(cfg.get('language'), 'ko-KR'),
          'cadence': first(cfg.get('cadence'), 'web_serial'),
          'default_episode_length_chars': first(cfg.get('default_episode_length_chars'), '6000'),
          'episode_kind': first(cfg.get('episode_kind'), 'webnovel-episode'),
          'patch_kind': first(cfg.get('patch_kind'), 'canon-patch'),
          'context_pack_kind': first(cfg.get('context_pack_kind'), 'context-pack'),
          'episode_name_prefix': episode_name_prefix,
          'episode_id_prefix': episode_id_prefix,
          'context_pack_suffix': first(naming.get('context_pack_suffix'), 'context'),
          'patch_name_suffix': first(naming.get('patch_name_suffix'), 'canon-patch'),
          'blocked_name_suffix': first(naming.get('blocked_name_suffix'), 'blocked'),
          'volume_bundle_prefix': volume_bundle_prefix,
          'episode_space': space('episodes', 'Episodes'),
          'patch_space': space('patches', 'Patches'),
          'context_pack_space': space('context_packs', 'ContextPacks'),
          'state_space': space('state', 'State'),
          'progress_space': space('progress', 'Progress'),
          'report_space': space('reports', 'Reports'),
          'main_canon_bundle': bundle('main_canon', 'main-canon'),
          'production_style_bundle': bundle('production_style', 'production-style'),
          'production_episodes_bundle': bundle('production_episodes', 'production-episodes'),
          'canon_patch_candidates_bundle': bundle('canon_patch_candidates', 'canon-patch-candidates'),
          'current_character_states_bundle': bundle('current_character_states', 'current-character-states'),
          'current_relationship_states_bundle': bundle('current_relationship_states', 'current-relationship-states'),
          'current_timeline_progress_bundle': bundle('current_timeline_progress', 'current-timeline-progress'),
          'current_storyline_progress_bundle': bundle('current_storyline_progress', 'current-storyline-progress'),
          'current_foreshadow_progress_bundle': bundle('current_foreshadow_progress', 'current-foreshadow-progress'),
          'state_sync_snapshots_bundle': bundle('state_sync_snapshots', 'state-sync-snapshots'),
          'canon_state_sync_reports_bundle': bundle('canon_state_sync_reports', 'canon-state-sync-reports'),
          'active_storylines_bundle': bundle('active_storylines', 'active-storylines'),
          'active_foreshadow_bundle': bundle('active_foreshadow', 'active-foreshadow'),
          'context_packs_bundle': bundle('context_packs', 'context-packs'),
          'blocked_episodes_bundle': bundle('blocked_episodes', 'blocked-episodes'),
          'series_bible_kref': kref('series_bible', 'Series/main.series-bible'),
          'series_synopsis_kref': kref('series_synopsis', 'Series/canon-synopsis.series-synopsis'),
          'character_index_kref': kref('character_index', 'Characters/index.character-index'),
          'relationship_map_kref': kref('relationship_map', 'Relationships/main.relationship-map'),
          'relationship_map_artifact_kref': first(
              krefs.get('relationship_map_artifact'),
              f'kref://{project_id}/Relationships/main.relationship-map?r=1&a=RELATIONSHIP_MAP.md',
          ),
          'timeline_kref': kref('timeline', 'Timeline/main.timeline'),
          'roadmap_kref': kref('roadmap', 'Roadmaps/long-arc.series-roadmap'),
          'current_character_state_kref': kref('current_character_state', f'State/{char_snapshot}.character-state'),
          'current_relationship_state_kref': kref('current_relationship_state', f'State/{rel_snapshot}.relationship-state'),
          'current_timeline_progress_kref': kref('current_timeline_progress', f'Progress/{timeline_snapshot}.timeline-progress'),
          'current_storyline_progress_kref': kref('current_storyline_progress', f'Progress/{storyline_snapshot}.storyline-progress'),
          'current_foreshadow_progress_kref': kref('current_foreshadow_progress', f'Progress/{foreshadow_snapshot}.foreshadow-progress'),
          'character_state_snapshot_name': char_snapshot,
          'relationship_state_snapshot_name': rel_snapshot,
          'timeline_progress_snapshot_name': timeline_snapshot,
          'storyline_progress_snapshot_name': storyline_snapshot,
          'foreshadow_progress_snapshot_name': foreshadow_snapshot,
          'genre_modules_text': ', '.join(str(x) for x in (cfg.get('genre_modules') or [])),
          'agent_personas_text': as_yamlish(cfg.get('agent_personas') or {}),
          'priority_rules_text': as_yamlish(cfg.get('priority_rules') or []),
          'audit_rules_text': as_yamlish(cfg.get('audit_rules') or []),
          'external_reference_seed': first(
              cfg.get('external_reference_seed'),
              cfg.get('reference_seed'),
              '작품 장르, 시대, 직업, 문화, 플랫폼 맥락에 맞는 외부 레퍼런스',
          ),
      }
      out['config_summary'] = json.dumps({
          'project': out['project_id'],
          'story_slug': out['story_slug'],
          'title': out['title'],
          'language': out['language'],
          'episode_space': out['episode_space'],
          'patch_space': out['patch_space'],
          'main_canon_bundle': out['main_canon_bundle'],
          'production_episodes_bundle': out['production_episodes_bundle'],
      }, ensure_ascii=False)
      sys.stdout.buffer.write(json.dumps(out, ensure_ascii=False).encode('utf-8'))
    args:
      project_config_yaml: ${inputs.project_config_yaml}
    timeout: 30
    allow_failure: false
- id: latest-production-episode
  name: latest_production_episode
  type: resolve
  position:
    x: -920
    y: -420
  description: Resolve source production-ready episode. Defaults to latest; target_episode_number resolves a generated episode
    name.
  retry: 1
  resolve:
    kind: ${project-config.output_data.episode_kind}
    tag: production-ready
    name_pattern: ${{ project_config.output_data.episode_name_prefix + '-' + str(int(inputs.target_episode_number)).zfill(3)
      if str(inputs.target_episode_number or '').strip() else '' }}
    space: ${project-config.output_data.episode_space}
    mode: latest
    fields:
    - episode_number
    - episode_id
    - volume
    - title
    - storyline_ids
    - foreshadow_ids
    - next_hook
    - summary
    - content
    - output
    - artifact_path
    fail_if_missing: true
- id: sync-info
  name: sync_info
  type: compute
  position:
    x: -620
    y: -420
  description: 최신 회차 기준 patch/snapshot/report 이름을 계산한다.
  retry: 1
  compute:
    outputs:
      episode_number: ${{ (int(inputs.target_episode_number) if str(inputs.target_episode_number or '').strip() else int(latest_production_episode.output_data.episode_number))
        }}
      episode_id: ${{ project_config.output_data.episode_id_prefix + '-' + str((int(inputs.target_episode_number) if str(inputs.target_episode_number
        or '').strip() else int(latest_production_episode.output_data.episode_number))).zfill(3) }}
      volume: ${{ int(latest_production_episode.output_data.volume) }}
      volume_bundle: ${{ project_config.output_data.volume_bundle_prefix + '-' + str(int(latest_production_episode.output_data.volume)).zfill(2)
        }}
      source_episode_name: ${{ project_config.output_data.episode_name_prefix + '-' + str((int(inputs.target_episode_number)
        if str(inputs.target_episode_number or '').strip() else int(latest_production_episode.output_data.episode_number))).zfill(3)
        }}
      patch_name: ${{ outputs.source_episode_name + '-' + project_config.output_data.patch_name_suffix }}
      sync_run_name: ${{ outputs.source_episode_name + '-state-sync' }}
      sync_report_name: ${{ outputs.source_episode_name + '-state-sync-report' }}
      character_state_snapshot_name: ${{ project_config.output_data.character_state_snapshot_name }}
      relationship_state_snapshot_name: ${{ project_config.output_data.relationship_state_snapshot_name }}
      timeline_progress_snapshot_name: ${{ project_config.output_data.timeline_progress_snapshot_name }}
      storyline_progress_snapshot_name: ${{ project_config.output_data.storyline_progress_snapshot_name }}
      foreshadow_progress_snapshot_name: ${{ project_config.output_data.foreshadow_progress_snapshot_name }}
      risky_apply_policy: ${{ 'apply-approved-deltas' if inputs.apply_mode == 'apply_approved' else 'requires-human-approval'
        }}
      source_episode_kref: ${{ inputs.target_episode_kref if str(inputs.target_episode_kref or '').strip() else latest_production_episode.output_data.kref
        }}
      target_patch_kref: ${{ inputs.target_patch_kref if str(inputs.target_patch_kref or '').strip() else '' }}
  depends_on:
  - latest-production-episode
- id: canon-patch-candidate
  name: canon_patch_candidate
  type: resolve
  position:
    x: -320
    y: -420
  description: 최신 production-ready 회차에 대응하는 canon patch candidate를 불러온다.
  retry: 1
  resolve:
    kind: ${project-config.output_data.patch_kind}
    tag: candidate
    name_pattern: ${sync-info.output_data.patch_name}
    space: ${project-config.output_data.patch_space}
    mode: latest
    fields:
    - patch_id
    - patch_status
    - apply_policy
    - source_episode
    - episode_number
    - volume
    - summary
    - content
    - output
    - artifact_path
    fail_if_missing: true
  depends_on:
  - sync-info
- id: state-sync-context
  name: state_sync_context
  type: kumiho_context
  position:
    x: 0
    y: -420
  description: latest episode, canon patch candidate, current state/progress bundles, canon graph를 locked context로 조립한다.
  retry: 1
  kumiho:
    project: ${project-config.output_data.project_id}
    mode: graph_augmented_context
    seed:
      bundles:
      - ${project-config.output_data.main_canon_bundle}
      - ${project-config.output_data.production_style_bundle}
      - ${sync-info.output_data.volume_bundle}
      - ${project-config.output_data.production_episodes_bundle}
      - ${project-config.output_data.canon_patch_candidates_bundle}
      - ${project-config.output_data.current_character_states_bundle}
      - ${project-config.output_data.active_storylines_bundle}
      - ${project-config.output_data.active_foreshadow_bundle}
      krefs:
      - ${sync-info.output_data.target_patch_kref}
      - ${sync-info.output_data.source_episode_kref}
      - ${sync-info.output_data.source_episode_kref}
      - ${canon_patch_candidate.output_data.kref}
      - ${project-config.output_data.series_bible_kref}
      - ${project-config.output_data.series_synopsis_kref}
      - ${project-config.output_data.character_index_kref}
      - ${project-config.output_data.relationship_map_kref}
      - ${project-config.output_data.relationship_map_artifact_kref}
      - ${project-config.output_data.timeline_kref}
      - ${project-config.output_data.roadmap_kref}
      queries:
      - <<${project-config.output_data.title}>> Ep.${sync-info.output_data.episode_number} post-episode canon state sync
      - latest production-ready episode state deltas character relationship timeline storyline foreshadow
      - canon patch candidate ${sync-info.output_data.patch_name} proposed updates
      - current state snapshots and active progress before Ep.${sync-info.output_data.episode_number}
      - ${inputs.continuity_context}
      - ${inputs.review_focus}
    traversal:
      max_depth: 2
      direction: both
      edge_types:
      - DEPENDS_ON
      - DERIVED_FROM
      - REFERENCES
      - REFERENCED
      - ADVANCES
      - FORESHADOWS
      - PAYOFF_TARGET
      - UPDATES
      - CONTRADICTS
      - BLOCKS
      - RELATED_TO
    filters:
      include_kinds:
      - series-bible
      - series-synopsis
      - style-guide
      - canon-rule
      - character-index
      - character
      - character-state
      - relationship-map
      - relationship-state
      - timeline
      - timeline-event
      - timeline-progress
      - storyline
      - storyline-progress
      - plot-thread
      - foreshadow-thread
      - foreshadow-progress
      - volume-plan
      - series-roadmap
      - arc-blueprint
      - webnovel-episode
      - canon-patch
      - continuity-audit
      - canon-state-sync-report
      exclude_tags:
      - deprecated
      - rejected
      max_items: 100
      max_revisions_per_item: 1
    ranking:
      method: hybrid
      semantic_query: Ep.${sync-info.output_data.episode_number} canon patch state delta current snapshots relationship timeline
        storyline foreshadow ${inputs.review_focus}
      prefer_recent: true
      prefer_current_tags: true
      boost_kinds:
        canon-patch: 2.4
        webnovel-episode: 2.2
        character-state: 2
        relationship-state: 2
        relationship-map: 2
        timeline-progress: 1.9
        storyline-progress: 1.9
        foreshadow-progress: 1.9
        storyline: 1.6
        foreshadow-thread: 1.6
        timeline-event: 1.5
        canon-rule: 1.8
      boost_edge_types:
        CONTRADICTS: 2.5
        BLOCKS: 2.2
        UPDATES: 2
        REFERENCED: 1.8
        ADVANCES: 1.7
        FORESHADOWS: 1.5
    lock:
      revisions: true
      tag_preference:
      - current
      - candidate
      - production-ready
      - active
      - ready
      - latest
    output:
      format: canon_state_sync_context
      include_artifact_summaries: true
      include_artifact_content: false
      max_artifact_chars_per_item: 4000
      max_total_artifact_chars: 90000
      include_edge_map: true
      include_conflict_warnings: true
      include_missing_context: true
      include_source_krefs: true
  depends_on:
  - latest-production-episode
  - sync-info
  - canon-patch-candidate
- id: state-delta-context-lite
  name: state_delta_context_lite
  type: kumiho_context
  position:
    x: 180
    y: -160
  description: state-delta-extractor 전용 경량 context. canon patch와 current snapshots만 얇게 읽어 timeout/token burn을 막는다.
  retry: 1
  kumiho:
    project: ${project-config.output_data.project_id}
    mode: graph_augmented_context
    seed:
      bundles:
      - ${project-config.output_data.canon_patch_candidates_bundle}
      - ${project-config.output_data.current_character_states_bundle}
      - ${project-config.output_data.current_relationship_states_bundle}
      - ${project-config.output_data.current_timeline_progress_bundle}
      - ${project-config.output_data.current_storyline_progress_bundle}
      - ${project-config.output_data.current_foreshadow_progress_bundle}
      - ${project-config.output_data.state_sync_snapshots_bundle}
      krefs:
      - ${sync-info.output_data.target_patch_kref}
      - ${sync-info.output_data.source_episode_kref}
      - ${sync-info.output_data.source_episode_kref}
      - ${canon_patch_candidate.output_data.kref}
      queries:
      - Ep.${sync-info.output_data.episode_number} canon patch to state delta normalization
      - current snapshots before Ep.${sync-info.output_data.episode_number}
      - current story clock / 극중 시간 / previous episode end time / next episode start anchor
      - ${inputs.review_focus}
    traversal:
      max_depth: 1
      direction: both
      edge_types:
      - UPDATES
      - REFERENCES
      - REFERENCED
      - ADVANCES
      - FORESHADOWS
      - RELATED_TO
    filters:
      include_kinds:
      - character-state
      - relationship-state
      - timeline
      - timeline-event
      - timeline-progress
      - storyline-progress
      - foreshadow-progress
      - webnovel-episode
      - canon-patch
      - canon-state-sync-report
      exclude_tags:
      - deprecated
      - rejected
      max_items: 25
      max_revisions_per_item: 1
    ranking:
      method: hybrid
      semantic_query: Ep.${sync-info.output_data.episode_number} patch current state delta ${inputs.review_focus}
      prefer_recent: true
      prefer_current_tags: true
      boost_kinds:
        canon-patch: 3
        character-state: 2.3
        relationship-state: 2.1
        timeline-progress: 2
        storyline-progress: 2
        foreshadow-progress: 2
        webnovel-episode: 1.4
    lock:
      revisions: true
      tag_preference:
      - current
      - candidate
      - production-ready
      - latest
    output:
      format: canon_state_delta_lite_context
      include_artifact_summaries: true
      include_artifact_content: false
      max_artifact_chars_per_item: 1000
      max_total_artifact_chars: 18000
      include_edge_map: false
      include_conflict_warnings: false
      include_missing_context: false
      include_source_krefs: true
  depends_on:
  - latest-production-episode
  - sync-info
  - canon-patch-candidate
- id: state-delta-extractor
  name: state_delta_extractor
  type: agent
  position:
    x: 360
    y: -420
  description: 최신 회차와 patch candidate에서 상태/progress delta를 추출한다. 적용 판정은 하지 않는다.
  agent:
    agent_type: codex
    role: reviewer
    prompt: |
      <<${project-config.output_data.title}>> Ep.${sync-info.output_data.episode_number} state delta extraction.

      너는 post-episode state delta extractor다.
      main canon, item revision, tag, edge를 수정하지 않는다.
      이 단계는 continuity 판정자가 아니라 patch normalizer다.
      canon patch candidate를 주 입력으로 삼아 current snapshot 업데이트 후보 형식으로 정규화한다.
      전체 산문을 재분석하려고 하지 말고, patch candidate와 episode metadata로 확인 가능한 변화만 추출한다.

      SOURCE_EPISODE_METADATA:
      - episode_number: ${sync-info.output_data.episode_number}
      - episode_id: ${sync-info.output_data.episode_id}
      - title: ${latest_production_episode.output_data.title}
      - summary: ${latest_production_episode.output_data.summary}
      - storyline_ids: ${latest_production_episode.output_data.storyline_ids}
      - foreshadow_ids: ${latest_production_episode.output_data.foreshadow_ids}
      - next_hook: ${latest_production_episode.output_data.next_hook}
      - episode_kref: ${sync-info.output_data.source_episode_kref}

      CANON_PATCH_CANDIDATE_PRIMARY_INPUT:
      ${canon-patch-candidate.output}

      LITE_CONTEXT_ONLY_FOR_CURRENT_BASELINE:
      ${state-delta-context-lite.output}

      OPERATOR_CONTEXT:
      - apply_mode: ${inputs.apply_mode}
      - continuity_context: ${inputs.continuity_context}
      - review_focus: ${inputs.review_focus}

      추출 기준:
      - patch candidate에 명시된 변화만 우선 추출한다.
      - 시간축은 예외적으로 반드시 정규화한다: current_story_time, previous_episode_end_time, episode_start_time, episode_end_time, elapsed_time, next_episode_start_anchor를 patch/episode metadata/context에서 추출한다.
      - 직전 장면 anchor(previous_scene_location, previous_scene_cast, active_event)와 다음 회차 시작 anchor(next_episode_start_location, next_episode_start_cast)를 함께 추출한다.
      - 시간 변화가 없더라도 no_time_jump/continued_from_previous marker를 timeline_progress_deltas에 기록한다.
      - 시간 이동이 있으면 허용 사유를 분류한다: parallel_scene, explicit_elapsed_time, flashback, flashforward, new_location_new_cast, same_scene_unmarked_jump.
      - same_scene_unmarked_jump는 requires_human_approval=true 또는 unsupported_delta_candidates에 넣는다.
      - episode metadata와 lite context로 확인 불가한 변화는 unsupported_delta_candidates에 넣는다.
      - evidence_locator는 patch candidate 항목, episode summary, next_hook, 명시 metadata 중 하나로 짧게 쓴다.
      - 전체 산문 기반 재해석, 장면 재구성, 장황한 근거 설명을 금지한다.
      - 명시적으로 발생하지 않은 심리/관계 변화는 inference_risk를 medium 또는 high로 둔다.
      - 관계 변화와 major timeline 변화는 이 단계에서 적용하지 않는다. proposed delta로만 분리한다.
      - 변화 없음은 no_change에 기록한다.
      - 각 목록은 실제 변화만 담고, 빈 예시 항목을 출력하지 않는다.

      반드시 FINAL_OUTPUT YAML만 출력하라.

      FINAL_OUTPUT:
      extracted_state_deltas:
        source_episode:
          episode_number: ${sync-info.output_data.episode_number}
          episode_id: "${sync-info.output_data.episode_id}"
          episode_kref: "${sync-info.output_data.source_episode_kref}"
        source_patch:
          patch_name: "${sync-info.output_data.patch_name}"
          patch_kref: "${canon_patch_candidate.output_data.kref}"
        character_state_deltas:
          - character_id: ""
            character_kref: ""
            before: ""
            after: ""
            evidence_locator: ""
            source_patch_entry: ""
            inference_risk: "low|medium|high"
            confidence: "high|medium|low"
        relationship_state_delta_candidates:
          - relation_label: ""
            source_character_kref: ""
            target_character_kref: ""
            before: ""
            after: ""
            evidence_locator: ""
            source_patch_entry: ""
            risk_level: "low|medium|high"
            requires_human_approval: true
        timeline_progress_deltas:
          - event_or_milestone: ""
            before_status: ""
            after_status: ""
            current_story_time: ""
            previous_episode_end_time: ""
            previous_scene_location: ""
            previous_scene_cast: []
            active_event: ""
            episode_start_time: ""
            episode_start_location: ""
            episode_start_cast: []
            episode_end_time: ""
            episode_end_location: ""
            episode_end_cast: []
            elapsed_time: ""
            next_episode_start_anchor: ""
            next_episode_start_location: ""
            next_episode_start_cast: []
            time_jump: false
            time_shift_kind: "none|parallel_scene|explicit_elapsed_time|flashback|flashforward|new_location_new_cast|same_scene_unmarked_jump"
            required_temporal_marker: ""
            continuity_marker: "continued_from_previous|no_time_jump|time_advanced|time_jump_requires_approval"
            evidence_locator: ""
            source_patch_entry: ""
            major_timeline_change: false
            requires_human_approval: false
        storyline_progress_deltas:
          - storyline_id: ""
            before_status: ""
            after_status: ""
            progress_note: ""
            evidence_locator: ""
            confidence: "high|medium|low"
        foreshadow_progress_deltas:
          - foreshadow_id: ""
            before_status: ""
            after_status: ""
            setup_or_payoff_note: ""
            evidence_locator: ""
            confidence: "high|medium|low"
        unsupported_delta_candidates: []
        no_change: []
    timeout: 1800
    max_turns: 12
  depends_on:
  - state-delta-context-lite
- id: state-delta-review
  name: state_delta_review
  type: agent
  position:
    x: 720
    y: -420
  description: continuity-lorekeeper가 delta를 검토하고 current snapshot 반영 가능 범위를 정한다.
  agent:
    agent_type: codex
    role: reviewer
    template: continuity-lorekeeper
    prompt: |
      <<${project-config.output_data.title}>> Ep.${sync-info.output_data.episode_number} state delta review.

      너는 continuity-lorekeeper다.
      state delta extraction을 검토하고, current snapshots에 무엇을 반영할지 보수적으로 판정한다.
      main canon, item revision, tag, edge를 수정하지 않는다.

      APPLY_MODE: ${inputs.apply_mode}
      RISKY_APPLY_POLICY: ${sync-info.output_data.risky_apply_policy}

      KUMIHO_CONTEXT_PACK:
      ${state-sync-context.output}

      EXTRACTED_STATE_DELTAS:
      ${state-delta-extractor.output}

      CANON_PATCH_CANDIDATE:
      ${canon-patch-candidate.output}

      운영 원칙:
      - character state와 storyline/foreshadow progress는 명확한 evidence와 patch 근거가 있으면 current snapshot 반영 대상으로 둘 수 있다.
      - relationship 변화는 기본적으로 requires_human_approval이다.
      - timeline continuity marker(current_story_time, previous_episode_end_time, episode_start_time, episode_end_time, elapsed_time, next_episode_start_anchor)는 major_timeline_change가 아니면 current snapshot에 반영 대상으로 둔다.
      - major_timeline_change가 true인 변화는 기본적으로 requires_human_approval이다.
      - apply_mode가 apply_approved가 아니면 relationship 변화와 major timeline 변화는 applied_delta가 아니라 pending_human_approval로 분류한다.
      - apply_mode가 apply_approved여도 evidence와 patch 근거가 부족하면 pending_human_approval 또는 reject로 둔다.
      - output snapshot은 만들 수 있어야 하지만 risky change가 조용히 current 사실처럼 섞이면 안 된다.

      반드시 FINAL_OUTPUT YAML만 출력하라.

      FINAL_OUTPUT:
      verdict: "APPROVED|NEEDS_HUMAN_APPROVAL|BLOCKED"
      safe_to_emit_current_snapshots: true
      requires_relationship_approval: true
      requires_major_timeline_approval: true
      review:
        accepted_character_state_deltas: []
        accepted_storyline_progress_deltas: []
        accepted_foreshadow_progress_deltas: []
        accepted_timeline_progress_deltas: []
        pending_human_approval:
          relationships: []
          major_timeline_changes: []
        rejected_or_unsupported_deltas: []
        approval_notes:
          - "relationship 변화와 major timeline 변화는 apply_mode=apply_approved 및 명시 승인 없이는 current 사실로 병합하지 않는다."
        snapshot_build_rules:
          - "모든 snapshot metadata에 source episode, source patch, review verdict, apply_mode를 기록한다."
          - "pending_human_approval 항목은 snapshot notes에 남기되 applied state로 쓰지 않는다."
          - "근거가 부족하면 no_assertion 또는 unchanged로 둔다."
      summary: ""
    timeout: 1500
    max_turns: 35
    output_fields:
    - verdict
    - safe_to_emit_current_snapshots
    - requires_relationship_approval
    - requires_major_timeline_approval
    - review
    - summary
  assign: continuity-lorekeeper
  depends_on:
  - state-delta-extractor
- id: current-snapshot-builder
  name: current_snapshot_builder
  type: agent
  position:
    x: 1080
    y: -420
  description: 검토된 delta에서 current character/relationship/timeline/storyline/foreshadow snapshots와 sync report 본문을 작성한다.
  agent:
    agent_type: codex
    role: reviewer
    template: continuity-lorekeeper
    prompt: |
      <<${project-config.output_data.title}>> Ep.${sync-info.output_data.episode_number} current state/progress snapshot builder.

      너는 snapshot builder다.
      Kumiho current snapshot artifact 본문을 만든다.
      main canon, source episode, source patch candidate, graph edge를 수정하지 않는다.

      APPLY_MODE: ${inputs.apply_mode}
      REVIEW_VERDICT: ${state-delta-review.output_data.verdict}

      KUMIHO_CONTEXT_PACK:
      ${state-sync-context.output}

      EXTRACTED_STATE_DELTAS:
      ${state-delta-extractor.output}

      DELTA_REVIEW:
      ${state-delta-review.output}

      출력 규칙:
      - current_character_state_snapshot은 accepted character state deltas만 반영한다.
      - current_relationship_state_snapshot은 apply_mode=apply_approved이고 DELTA_REVIEW가 승인한 관계 변화만 applied로 쓴다. 그 외 관계 변화는 pending_human_approval_notes로 둔다.
      - current_timeline_progress_snapshot은 극중 시간 연속성을 별도 최상위 섹션으로 반드시 포함한다: current_story_time, previous_episode_end_time, previous_scene_location, previous_scene_cast, active_event, episode_start_time, episode_start_location, episode_start_cast, episode_end_time, episode_end_location, episode_end_cast, elapsed_time, next_episode_start_anchor, next_episode_start_location, next_episode_start_cast, time_shift_policy, time_jump_allowed, source_episode.
      - major timeline change는 apply_mode=apply_approved이고 DELTA_REVIEW가 승인한 경우에만 applied로 쓴다. 그 외에는 pending_human_approval_notes로 둔다.
      - no_time_jump/continued_from_previous 같은 timeline continuity marker는 major change가 아니므로 다음 episode factory용 current 사실로 반영한다.
      - same_scene_unmarked_jump는 current 사실로 반영하지 말고 pending_human_approval_notes 또는 rejected_or_unsupported_summary에 둔다.
      - current_storyline_progress_snapshot과 current_foreshadow_progress_snapshot은 accepted progress만 반영하고 speculative changes는 보류한다.
      - 각 snapshot에는 source_episode_kref, source_patch_kref, source_context_krefs, generated_by workflow/version, apply_mode, approval_notes를 포함한다.
      - post_episode_sync_report는 무엇을 적용했는지, 무엇을 보류했는지, 다음 episode factory가 읽어야 할 bundle/kref를 요약한다.

      반드시 FINAL_OUTPUT YAML만 출력하라.

      FINAL_OUTPUT:
      snapshots:
        current_character_state_snapshot:
          snapshot_name: "${sync-info.output_data.character_state_snapshot_name}"
          tag: current
          episode_number: ${sync-info.output_data.episode_number}
          applied_deltas: []
          unchanged_or_unknown: []
          approval_notes: []
        current_relationship_state_snapshot:
          snapshot_name: "${sync-info.output_data.relationship_state_snapshot_name}"
          tag: current
          episode_number: ${sync-info.output_data.episode_number}
          applied_deltas: []
          pending_human_approval_notes: []
          approval_notes: []
        current_timeline_progress_snapshot:
          snapshot_name: "${sync-info.output_data.timeline_progress_snapshot_name}"
          tag: current
          episode_number: ${sync-info.output_data.episode_number}
          temporal_continuity:
            current_story_time: ""
            previous_episode_end_time: ""
            previous_scene_location: ""
            previous_scene_cast: []
            active_event: ""
            episode_start_time: ""
            episode_start_location: ""
            episode_start_cast: []
            episode_end_time: ""
            episode_end_location: ""
            episode_end_cast: []
            elapsed_time: ""
            next_episode_start_anchor: ""
            next_episode_start_location: ""
            next_episode_start_cast: []
            time_shift_policy: "same_scene_continuity|required_marker|parallel_scene|flashback|explicit_elapsed_time"
            time_jump_allowed: false
            required_temporal_marker: ""
            continuity_marker: "continued_from_previous|no_time_jump|time_advanced|time_jump_requires_approval"
            source_episode: "${sync-info.output_data.source_episode_name}"
          applied_deltas: []
          pending_human_approval_notes: []
          approval_notes: []
        current_storyline_progress_snapshot:
          snapshot_name: "${sync-info.output_data.storyline_progress_snapshot_name}"
          tag: current
          episode_number: ${sync-info.output_data.episode_number}
          applied_deltas: []
          active_storylines: []
          approval_notes: []
        current_foreshadow_progress_snapshot:
          snapshot_name: "${sync-info.output_data.foreshadow_progress_snapshot_name}"
          tag: current
          episode_number: ${sync-info.output_data.episode_number}
          applied_deltas: []
          active_foreshadow: []
          approval_notes: []
      post_episode_sync_report:
        sync_run: "${sync-info.output_data.sync_run_name}"
        source_episode: "${sync-info.output_data.source_episode_name}"
        source_patch: "${sync-info.output_data.patch_name}"
        apply_mode: "${inputs.apply_mode}"
        review_verdict: "${state-delta-review.output_data.verdict}"
        bundles_for_next_factory:
          - ${project-config.output_data.current_character_states_bundle}
          - ${project-config.output_data.current_relationship_states_bundle}
          - ${project-config.output_data.current_timeline_progress_bundle}
          - ${project-config.output_data.current_storyline_progress_bundle}
          - ${project-config.output_data.current_foreshadow_progress_bundle}
          - ${project-config.output_data.state_sync_snapshots_bundle}
        krefs_for_next_factory:
          - kref://${project-config.output_data.state_space}/${sync-info.output_data.character_state_snapshot_name}
          - kref://${project-config.output_data.state_space}/${sync-info.output_data.relationship_state_snapshot_name}
          - kref://${project-config.output_data.progress_space}/${sync-info.output_data.timeline_progress_snapshot_name}
          - kref://${project-config.output_data.progress_space}/${sync-info.output_data.storyline_progress_snapshot_name}
          - kref://${project-config.output_data.progress_space}/${sync-info.output_data.foreshadow_progress_snapshot_name}
        applied_summary: []
        pending_human_approval_summary: []
        rejected_or_unsupported_summary: []
        next_factory_context_notes: []
    timeout: 1800
    max_turns: 45
  assign: continuity-lorekeeper
  depends_on:
  - state-delta-review
- id: emit-character-state-snapshot
  name: emit_character_state_snapshot
  type: output
  position:
    x: 0
    y: 80
  description: current character-state snapshot을 저장한다.
  retry: 2
  output:
    format: markdown
    template: |
      # Character-State Snapshot: ${sync-info.output_data.character_state_snapshot_name}

      Source episode: ${sync-info.output_data.source_episode_name}
      Source patch: ${sync-info.output_data.patch_name}
      Apply mode: ${inputs.apply_mode}

      ## Snapshot Payload

      ${current-snapshot-builder.output}
    entity_name: ${sync-info.output_data.character_state_snapshot_name}
    entity_kind: character-state
    entity_tag: current
    entity_space: ${project-config.output_data.state_space}
    metadata_target: revision
    entity_metadata:
      series: ${project-config.output_data.title}
      workflow: canonworks-serial-canon-state-sync
      workflow_version: '1.0'
      snapshot_type: character-state
      source_episode: ${sync-info.output_data.source_episode_name}
      source_episode_kref: ${sync-info.output_data.source_episode_kref}
      source_patch: ${sync-info.output_data.patch_name}
      source_patch_kref: ${canon_patch_candidate.output_data.kref}
      episode_number: ${sync-info.output_data.episode_number}
      volume: ${sync-info.output_data.volume}
      apply_mode: ${inputs.apply_mode}
      review_verdict: ${state-delta-review.output_data.verdict}
      story_slug: ${project-config.output_data.story_slug}
      project_id: ${project-config.output_data.project_id}
      language: ${project-config.output_data.language}
      bootstrap_mode: ${inputs.bootstrap_mode}
      target_episode_kref: ${sync-info.output_data.source_episode_kref}
      target_patch_kref: ${sync-info.output_data.target_patch_kref}
  depends_on:
  - current-snapshot-builder
  - latest-production-episode
  - canon-patch-candidate
  - state-delta-review
  - sync-info
- id: emit-relationship-state-snapshot
  name: emit_relationship_state_snapshot
  type: output
  position:
    x: 360
    y: 80
  description: current relationship-state snapshot을 저장한다. 승인 전 risky change는 notes에만 둔다.
  retry: 2
  output:
    format: markdown
    template: |
      # Relationship-State Snapshot: ${sync-info.output_data.relationship_state_snapshot_name}

      Source episode: ${sync-info.output_data.source_episode_name}
      Source patch: ${sync-info.output_data.patch_name}
      Apply mode: ${inputs.apply_mode}

      ## Approval Guard

      Relationship changes require human approval unless apply_mode is apply_approved and the review accepted them.

      ## Snapshot Payload

      ${current-snapshot-builder.output}
    entity_name: ${sync-info.output_data.relationship_state_snapshot_name}
    entity_kind: relationship-state
    entity_tag: current
    entity_space: ${project-config.output_data.state_space}
    metadata_target: revision
    entity_metadata:
      series: ${project-config.output_data.title}
      workflow: canonworks-serial-canon-state-sync
      workflow_version: '1.0'
      snapshot_type: relationship-state
      source_episode: ${sync-info.output_data.source_episode_name}
      source_episode_kref: ${sync-info.output_data.source_episode_kref}
      source_patch: ${sync-info.output_data.patch_name}
      source_patch_kref: ${canon_patch_candidate.output_data.kref}
      episode_number: ${sync-info.output_data.episode_number}
      volume: ${sync-info.output_data.volume}
      apply_mode: ${inputs.apply_mode}
      review_verdict: ${state-delta-review.output_data.verdict}
      requires_human_approval: ${state-delta-review.output_data.requires_relationship_approval}
      story_slug: ${project-config.output_data.story_slug}
      project_id: ${project-config.output_data.project_id}
      language: ${project-config.output_data.language}
      bootstrap_mode: ${inputs.bootstrap_mode}
      target_episode_kref: ${sync-info.output_data.source_episode_kref}
      target_patch_kref: ${sync-info.output_data.target_patch_kref}
  depends_on:
  - emit-character-state-snapshot
  - current-snapshot-builder
  - latest-production-episode
  - canon-patch-candidate
  - state-delta-review
  - sync-info
- id: emit-timeline-progress-snapshot
  name: emit_timeline_progress_snapshot
  type: output
  position:
    x: 720
    y: 80
  description: current timeline-progress snapshot을 저장한다. major timeline change는 승인 전 notes에만 둔다.
  retry: 2
  output:
    format: markdown
    template: |
      # Timeline-Progress Snapshot: ${sync-info.output_data.timeline_progress_snapshot_name}

      Source episode: ${sync-info.output_data.source_episode_name}
      Source patch: ${sync-info.output_data.patch_name}
      Apply mode: ${inputs.apply_mode}

      ## Temporal Continuity Contract

      This snapshot must expose the current story clock and scene anchor for the next episode factory:
      - current_story_time
      - previous_episode_end_time
      - previous_scene_location
      - previous_scene_cast
      - active_event
      - episode_start_time
      - episode_start_location
      - episode_start_cast
      - episode_end_time
      - episode_end_location
      - episode_end_cast
      - elapsed_time
      - next_episode_start_anchor
      - next_episode_start_location
      - next_episode_start_cast
      - time_shift_policy
      - time_jump_allowed
      - required_temporal_marker

      Major timeline changes require human approval unless apply_mode is apply_approved and the review accepted them.
      Continuity markers such as no_time_jump/continued_from_previous are safe current facts.
      Same-scene unmarked jumps are not safe current facts and must be pending/rejected.

      ## Snapshot Payload

      ${current-snapshot-builder.output}
    entity_name: ${sync-info.output_data.timeline_progress_snapshot_name}
    entity_kind: timeline-progress
    entity_tag: current
    entity_space: ${project-config.output_data.progress_space}
    metadata_target: revision
    entity_metadata:
      series: ${project-config.output_data.title}
      workflow: canonworks-serial-canon-state-sync
      workflow_version: '1.0'
      snapshot_type: timeline-progress
      source_episode: ${sync-info.output_data.source_episode_name}
      source_episode_kref: ${sync-info.output_data.source_episode_kref}
      source_patch: ${sync-info.output_data.patch_name}
      source_patch_kref: ${canon_patch_candidate.output_data.kref}
      episode_number: ${sync-info.output_data.episode_number}
      volume: ${sync-info.output_data.volume}
      apply_mode: ${inputs.apply_mode}
      review_verdict: ${state-delta-review.output_data.verdict}
      requires_human_approval: ${state-delta-review.output_data.requires_major_timeline_approval}
      story_slug: ${project-config.output_data.story_slug}
      project_id: ${project-config.output_data.project_id}
      language: ${project-config.output_data.language}
      bootstrap_mode: ${inputs.bootstrap_mode}
      target_episode_kref: ${sync-info.output_data.source_episode_kref}
      target_patch_kref: ${sync-info.output_data.target_patch_kref}
  depends_on:
  - emit-relationship-state-snapshot
  - current-snapshot-builder
  - latest-production-episode
  - canon-patch-candidate
  - state-delta-review
  - sync-info
- id: emit-storyline-progress-snapshot
  name: emit_storyline_progress_snapshot
  type: output
  position:
    x: 1080
    y: 80
  description: current storyline-progress snapshot을 저장한다.
  retry: 2
  output:
    format: markdown
    template: |
      # Storyline-Progress Snapshot: ${sync-info.output_data.storyline_progress_snapshot_name}

      Source episode: ${sync-info.output_data.source_episode_name}
      Source patch: ${sync-info.output_data.patch_name}
      Apply mode: ${inputs.apply_mode}

      ## Snapshot Payload

      ${current-snapshot-builder.output}
    entity_name: ${sync-info.output_data.storyline_progress_snapshot_name}
    entity_kind: storyline-progress
    entity_tag: current
    entity_space: ${project-config.output_data.progress_space}
    metadata_target: revision
    entity_metadata:
      series: ${project-config.output_data.title}
      workflow: canonworks-serial-canon-state-sync
      workflow_version: '1.0'
      snapshot_type: storyline-progress
      source_episode: ${sync-info.output_data.source_episode_name}
      source_episode_kref: ${sync-info.output_data.source_episode_kref}
      source_patch: ${sync-info.output_data.patch_name}
      source_patch_kref: ${canon_patch_candidate.output_data.kref}
      episode_number: ${sync-info.output_data.episode_number}
      volume: ${sync-info.output_data.volume}
      apply_mode: ${inputs.apply_mode}
      review_verdict: ${state-delta-review.output_data.verdict}
      story_slug: ${project-config.output_data.story_slug}
      project_id: ${project-config.output_data.project_id}
      language: ${project-config.output_data.language}
      bootstrap_mode: ${inputs.bootstrap_mode}
      target_episode_kref: ${sync-info.output_data.source_episode_kref}
      target_patch_kref: ${sync-info.output_data.target_patch_kref}
  depends_on:
  - emit-timeline-progress-snapshot
  - current-snapshot-builder
  - latest-production-episode
  - canon-patch-candidate
  - state-delta-review
  - sync-info
- id: emit-foreshadow-progress-snapshot
  name: emit_foreshadow_progress_snapshot
  type: output
  position:
    x: 1440
    y: 80
  description: current foreshadow-progress snapshot을 저장한다.
  retry: 2
  output:
    format: markdown
    template: |
      # Foreshadow-Progress Snapshot: ${sync-info.output_data.foreshadow_progress_snapshot_name}

      Source episode: ${sync-info.output_data.source_episode_name}
      Source patch: ${sync-info.output_data.patch_name}
      Apply mode: ${inputs.apply_mode}

      ## Snapshot Payload

      ${current-snapshot-builder.output}
    entity_name: ${sync-info.output_data.foreshadow_progress_snapshot_name}
    entity_kind: foreshadow-progress
    entity_tag: current
    entity_space: ${project-config.output_data.progress_space}
    metadata_target: revision
    entity_metadata:
      series: ${project-config.output_data.title}
      workflow: canonworks-serial-canon-state-sync
      workflow_version: '1.0'
      snapshot_type: foreshadow-progress
      source_episode: ${sync-info.output_data.source_episode_name}
      source_episode_kref: ${sync-info.output_data.source_episode_kref}
      source_patch: ${sync-info.output_data.patch_name}
      source_patch_kref: ${canon_patch_candidate.output_data.kref}
      episode_number: ${sync-info.output_data.episode_number}
      volume: ${sync-info.output_data.volume}
      apply_mode: ${inputs.apply_mode}
      review_verdict: ${state-delta-review.output_data.verdict}
      story_slug: ${project-config.output_data.story_slug}
      project_id: ${project-config.output_data.project_id}
      language: ${project-config.output_data.language}
      bootstrap_mode: ${inputs.bootstrap_mode}
      target_episode_kref: ${sync-info.output_data.source_episode_kref}
      target_patch_kref: ${sync-info.output_data.target_patch_kref}
  depends_on:
  - emit-storyline-progress-snapshot
  - current-snapshot-builder
  - latest-production-episode
  - canon-patch-candidate
  - state-delta-review
  - sync-info
- id: emit-post-episode-sync-report
  name: emit_post_episode_sync_report
  type: output
  position:
    x: 720
    y: 420
  description: post-episode sync report를 저장한다.
  retry: 2
  output:
    format: markdown
    template: |
      # Post-Episode Canon State Sync Report: ${sync-info.output_data.sync_report_name}

      ## Source

      - Episode: ${sync-info.output_data.source_episode_name}
      - Episode Number: ${sync-info.output_data.episode_number}
      - Patch Candidate: ${sync-info.output_data.patch_name}
      - Apply Mode: ${inputs.apply_mode}
      - Review Verdict: ${state-delta-review.output_data.verdict}

      ## Delta Extraction

      ${state-delta-extractor.output}

      ## Delta Review

      ${state-delta-review.output}

      ## Snapshots

      ${current-snapshot-builder.output}
    entity_name: ${sync-info.output_data.sync_report_name}
    entity_kind: canon-state-sync-report
    entity_tag: state-sync-report
    entity_space: ${project-config.output_data.report_space}
    metadata_target: revision
    entity_metadata:
      series: ${project-config.output_data.title}
      workflow: canonworks-serial-canon-state-sync
      workflow_version: '1.0'
      source_episode: ${sync-info.output_data.source_episode_name}
      source_episode_kref: ${sync-info.output_data.source_episode_kref}
      source_patch: ${sync-info.output_data.patch_name}
      source_patch_kref: ${canon_patch_candidate.output_data.kref}
      episode_number: ${sync-info.output_data.episode_number}
      volume: ${sync-info.output_data.volume}
      apply_mode: ${inputs.apply_mode}
      review_verdict: ${state-delta-review.output_data.verdict}
      relationship_changes_require_approval: ${state-delta-review.output_data.requires_relationship_approval}
      major_timeline_changes_require_approval: ${state-delta-review.output_data.requires_major_timeline_approval}
      story_slug: ${project-config.output_data.story_slug}
      project_id: ${project-config.output_data.project_id}
      language: ${project-config.output_data.language}
      bootstrap_mode: ${inputs.bootstrap_mode}
      target_episode_kref: ${sync-info.output_data.source_episode_kref}
      target_patch_kref: ${sync-info.output_data.target_patch_kref}
  depends_on:
  - emit-foreshadow-progress-snapshot
  - current-snapshot-builder
  - latest-production-episode
  - canon-patch-candidate
  - state-delta-review
  - sync-info
  - state-delta-extractor
- id: update-state-sync-bundles
  name: update_state_sync_bundles
  type: kumiho_bundle_update
  position:
    x: 720
    y: 760
  description: current snapshots와 sync report를 다음 episode factory가 읽는 Kumiho bundles에 등록한다.
  retry: 1
  kumiho:
    project: ${project-config.output_data.project_id}
    mode: add_members
    create_if_missing: true
    idempotent: true
    allow_protected: true
    updates:
    - bundle: ${project-config.output_data.current_character_states_bundle}
      add:
      - item_kref: ${emit-character-state-snapshot.output_data.entity_kref}
        reason: Current character-state snapshot synced after Ep.${sync-info.output_data.episode_number}
        metadata:
          workflow: canonworks-serial-canon-state-sync
          workflow_version: '1.0'
          snapshot_type: character-state
          episode_number: ${sync-info.output_data.episode_number}
          source_episode: ${sync-info.output_data.source_episode_name}
          source_patch: ${sync-info.output_data.patch_name}
    - bundle: ${project-config.output_data.current_relationship_states_bundle}
      add:
      - item_kref: ${emit-relationship-state-snapshot.output_data.entity_kref}
        reason: Current relationship-state snapshot synced after Ep.${sync-info.output_data.episode_number}; risky changes
          require approval unless apply_mode=apply_approved
        metadata:
          workflow: canonworks-serial-canon-state-sync
          workflow_version: '1.0'
          snapshot_type: relationship-state
          episode_number: ${sync-info.output_data.episode_number}
          source_episode: ${sync-info.output_data.source_episode_name}
          source_patch: ${sync-info.output_data.patch_name}
          apply_mode: ${inputs.apply_mode}
          requires_human_approval: ${state-delta-review.output_data.requires_relationship_approval}
    - bundle: ${project-config.output_data.current_timeline_progress_bundle}
      add:
      - item_kref: ${emit-timeline-progress-snapshot.output_data.entity_kref}
        reason: Current timeline-progress snapshot synced after Ep.${sync-info.output_data.episode_number}; major changes
          require approval unless apply_mode=apply_approved
        metadata:
          workflow: canonworks-serial-canon-state-sync
          workflow_version: '1.0'
          snapshot_type: timeline-progress
          episode_number: ${sync-info.output_data.episode_number}
          source_episode: ${sync-info.output_data.source_episode_name}
          source_patch: ${sync-info.output_data.patch_name}
          apply_mode: ${inputs.apply_mode}
          requires_human_approval: ${state-delta-review.output_data.requires_major_timeline_approval}
    - bundle: ${project-config.output_data.current_storyline_progress_bundle}
      add:
      - item_kref: ${emit-storyline-progress-snapshot.output_data.entity_kref}
        reason: Current storyline-progress snapshot synced after Ep.${sync-info.output_data.episode_number}
        metadata:
          workflow: canonworks-serial-canon-state-sync
          workflow_version: '1.0'
          snapshot_type: storyline-progress
          episode_number: ${sync-info.output_data.episode_number}
          source_episode: ${sync-info.output_data.source_episode_name}
          source_patch: ${sync-info.output_data.patch_name}
    - bundle: ${project-config.output_data.current_foreshadow_progress_bundle}
      add:
      - item_kref: ${emit-foreshadow-progress-snapshot.output_data.entity_kref}
        reason: Current foreshadow-progress snapshot synced after Ep.${sync-info.output_data.episode_number}
        metadata:
          workflow: canonworks-serial-canon-state-sync
          workflow_version: '1.0'
          snapshot_type: foreshadow-progress
          episode_number: ${sync-info.output_data.episode_number}
          source_episode: ${sync-info.output_data.source_episode_name}
          source_patch: ${sync-info.output_data.patch_name}
    - bundle: ${project-config.output_data.state_sync_snapshots_bundle}
      add:
      - item_kref: ${emit-character-state-snapshot.output_data.entity_kref}
        reason: Current character-state snapshot for next episode factory context
        metadata:
          snapshot_type: character-state
          episode_number: ${sync-info.output_data.episode_number}
      - item_kref: ${emit-relationship-state-snapshot.output_data.entity_kref}
        reason: Current relationship-state snapshot for next episode factory context
        metadata:
          snapshot_type: relationship-state
          episode_number: ${sync-info.output_data.episode_number}
          requires_human_approval: ${state-delta-review.output_data.requires_relationship_approval}
      - item_kref: ${emit-timeline-progress-snapshot.output_data.entity_kref}
        reason: Current timeline-progress snapshot for next episode factory context
        metadata:
          snapshot_type: timeline-progress
          episode_number: ${sync-info.output_data.episode_number}
          requires_human_approval: ${state-delta-review.output_data.requires_major_timeline_approval}
      - item_kref: ${emit-storyline-progress-snapshot.output_data.entity_kref}
        reason: Current storyline-progress snapshot for next episode factory context
        metadata:
          snapshot_type: storyline-progress
          episode_number: ${sync-info.output_data.episode_number}
      - item_kref: ${emit-foreshadow-progress-snapshot.output_data.entity_kref}
        reason: Current foreshadow-progress snapshot for next episode factory context
        metadata:
          snapshot_type: foreshadow-progress
          episode_number: ${sync-info.output_data.episode_number}
    - bundle: ${project-config.output_data.canon_state_sync_reports_bundle}
      add:
      - item_kref: ${emit-post-episode-sync-report.output_data.entity_kref}
        reason: Post-episode canon state sync report for Ep.${sync-info.output_data.episode_number}
        metadata:
          workflow: canonworks-serial-canon-state-sync
          workflow_version: '1.0'
          episode_number: ${sync-info.output_data.episode_number}
          source_episode: ${sync-info.output_data.source_episode_name}
          source_patch: ${sync-info.output_data.patch_name}
          apply_mode: ${inputs.apply_mode}
          review_verdict: ${state-delta-review.output_data.verdict}
    fail_if_missing_bundle: true
    fail_if_missing_item: true
  depends_on:
  - emit-character-state-snapshot
  - emit-relationship-state-snapshot
  - emit-timeline-progress-snapshot
  - emit-storyline-progress-snapshot
  - emit-foreshadow-progress-snapshot
  - emit-post-episode-sync-report
- id: run-summary
  name: run_summary
  type: output
  position:
    x: 720
    y: 1080
  description: 이번 canon state sync 실행 결과 요약.
  output:
    format: markdown
    template: |
      # CanonWorks Canon State Sync Result

      - Workflow: `canonworks-serial-canon-state-sync`
      - Version: `1.0`
      - Episode: `${sync-info.output_data.episode_number}`
      - Episode ID: `${sync-info.output_data.episode_id}`
      - Source Episode: `${sync-info.output_data.source_episode_name}`
      - Source Patch: `${sync-info.output_data.patch_name}`
      - Apply Mode: `${inputs.apply_mode}`
      - Review Verdict: `${state-delta-review.output_data.verdict}`

      ## Current Snapshot Outputs

      - Character State: `${emit-character-state-snapshot.output_data.entity_kref}`
      - Relationship State: `${emit-relationship-state-snapshot.output_data.entity_kref}`
      - Timeline Progress: `${emit-timeline-progress-snapshot.output_data.entity_kref}`
      - Storyline Progress: `${emit-storyline-progress-snapshot.output_data.entity_kref}`
      - Foreshadow Progress: `${emit-foreshadow-progress-snapshot.output_data.entity_kref}`
      - Sync Report: `${emit-post-episode-sync-report.output_data.entity_kref}`

      ## Bundle Update

      ${update-state-sync-bundles.output}

      ## Review

      ${state-delta-review.output}
  depends_on:
  - update-state-sync-bundles
tags:
- canonworks
- creative
- writing
- serial
- kumiho
- state-sync
- workflow-template
