# SSOT MANIFEST — Recoil Engine Canonical Capability Map
# Generated: 2026-05-23 post-R6 baseline
#
# GRANULARITY RULE (read before adding entries):
# A capability is ONE function OR ONE module path that owns a specific job.
# The job is named at the modality+verb level: "video_payload_assembly",
# "sidecar_write", "ref_resolve".
# Sub-modalities (image vs video) ARE distinct capabilities IF AND ONLY IF
# the canonical path differs. If one function handles both image and video
# assembly, it's ONE capability. Adding a new sub-modality requires EXTENDING
# the canonical path, NOT creating a new entry.
#
# To add a genuinely new capability, the spec must include [INTENT: REGISTER_CAPABILITY]
# and a phase that amends this file before any code is written.
#
# DISCOVERY: This file is referenced in recoil/CLAUDE.md and recoil/pipeline/CLAUDE.md.
# All Claude Code sessions in this repo should read it before speccing any new capability.

capabilities:

  # 2026-06-05 ground-truth delta from TRACE 2026-06-02:
  # Recoil owns brain/provenance/validation; Flora is the decided worksurface
  # and execution target; EpisodeRunner is the converged production path; v3
  # output layout is assets/ + prep/ + renders/. Ref resolution/promotion are
  # intentionally documented as broken until code work lands.

  director_notes_ledger:
    canonical: recoil/api/notes_ledger.py
    state: active
    updated: 2026-06-18
    deprecated_paths: []

  script_edit_snapshot:
    # NARROW (JT narrow-brick decision 2026-06-18): pre-mutation snapshot for the SCRIPT-EDIT
    # transaction ONLY — NOT a general artifact-snapshot service. A broad multi-kind snapshot
    # service is a future brick, not this one (don't let this entry become a dumping ground).
    canonical: recoil/api/notes_snapshot.py::snapshot_artifact
    scope: script_edit
    state: active
    updated: 2026-06-18
    deprecated_paths: []

  plan_overrides:
    canonical: recoil/pipeline/_lib/plan_overrides.py
    state: active
    updated: 2026-06-24
    deprecated_paths: []
    scope: "durable operator plan-edit override-SSOT — plan = derive(script) ⊕ overrides, applied at the Plan Pass write seam with per-override target_span_hash currency-flagging. Owns shot-DESIGN intent (shot_type, source_text/beat, spatial). The 4 review-state fields (manual_prompt_override/human_approvals/review_status/selected_take_id) stay with merge_with_prior_plan until Brick 6b."

  # sidecar_writing — FULLY RETIRED 2026-05-24 (second foreground /converge).
  # R6 (2026-05-22) migrated 4 production sites to populate_sidecar; tombstone
  # file recoil/core/sidecar_writer.py deleted 2026-05-24. Rollback path
  # preserved via git tag `pre-r6-sidecar-consolidation`. Entry removed from
  # manifest because the capability no longer exists.

  payload_assembly:
    canonical: recoil/pipeline/_lib/dispatch_payload.py::build_dispatch_payload
    state: active
    updated: 2026-06-05
    deprecated_paths: []
    sub_modalities:
      - video_i2v
      - video_t2v
      - r2v_multi
    excluded:
      - keyframe
      - provider_transport
    note: >
      Live /generate-video uses EpisodeRunner._build_workflow_for_beat() to call
      build_dispatch_payload(), then wraps the result in WorkflowStep payloads.
      The downstream provider transport is not assembled here: Workflow.run()
      calls dispatch(), runners call StepRunner, StepRunner calls
      VideoModelClient, and VideoModelClient chooses Flora/fallback adapters.
      For tartarus identity refs, dispatch_payload resolves the composite sheet
      via ref_resolver.resolve_sheet_asset (the canonical_target bundle lineage,
      gated by use_composite_sheets) and falls back to the angle collector when a
      required sheet is absent.

  board_review_comments:
    canonical: recoil/workspace/board_comments.py
    state: active
    updated: 2026-06-18
    deprecated_paths: []
    note: >
      Human board-review comments (REC-190). Per-episode comment store at
      <project_root>/prep/<ep_NNN>/storyboards/board_comments.json, owned solely
      by recoil/workspace/board_comments.py (load_comments/add_comment/
      resolve_comment). Distinct from manifest.execution.boards (generator-owned
      board approve/reject decisions keyed by shotset_hash) — comments are human
      review intent and never written by the pipeline. Served read/write via the
      Workspace /api/episode/{project}/{episode_id}/comments routes.

  board_read_model:
    canonical: recoil/workspace/readmodel.py::get_board
    state: active
    updated: 2026-06-21
    scope: "canonical for the per-SELECTOR pointer-faithful board read (GET /api/board/{selector}) ONLY; the episode-wall batch-grid read is a SEPARATE surface still owned by build_episode_board (not deprecated here — slice 4 migrates + retires it)"
    note: >
      Pointer-faithful board read-model (workspace reconstruction slice 1a).
      CANONICAL FOR THE SELECTOR-SCOPED board read only (GET /api/board/{selector}),
      a surface no prior capability owned — clean new registration, not a transition.
      get_board(any_id, project) -> BoardModel: resolve() the selector to the
      canonical ep_{NNN}_* orchestration scene, dereference beat.board.artifact
      (the persisted SELECTION POINTER / cache mirror of the execution.boards
      decision record — no recency, no approval filtering), load the pointed
      sidecar for per-version DETAIL, and project provenance + a
      newer_unpointed_versions DIAGNOSTIC. A read-model PROJECTION, never a new
      persisted store. Single id-parse home for this read path is
      recoil/pipeline/orchestrator/batch_selector.py::resolve. Served via the
      Workspace GET /api/board/{selector} route. The legacy episode-wall reader
      build_episode_board (batch grid via a recency glob, served by
      /api/episode/.../board) is a DISTINCT surface that this slice does NOT touch
      or deprecate; its migration onto this read-model + deletion is slice 4
      (tracked there, not claimed here). Distinct from board_review_comments
      (human comments) and the canonical board DECISION record
      execution.boards[shotset_hash] (generator approve/reject — this read-model
      does not read it; beat.board is its cache).

  ref_resolution:
    canonical: recoil/core/ref_resolver.py::resolve_entity_refs
    canonical_target: recoil/core/ref_resolver.py::resolve_reference_bundle
    state: transitioning
    status: transitioning
    active_for:
      - "char: board+r2v production path → resolve_character_bundle"
    tracking_issue: "Linear REC-76 follow-up: ref resolver broken / version-split from paths layer"
    deprecated_paths:
      - recoil/core/paths.py::ProjectPaths.resolve_ref
      - recoil/core/ref_resolver.py::resolve_character_refs
      - recoil/core/ref_resolver.py::resolve_location_refs
      - recoil/core/ref_resolver.py::resolve_prop_refs
    current_broken_behavior: >
      resolve_entity_refs scans assets/{cls}/{subject}/ non-recursively, so it
      misses real v3 refs under assets/{cls}/{subject}/base/pool/{kind}/;
      ProjectPaths.resolve_ref/resolve_hero is a second resolver that scans
      base/ for hero stems and raises RefNotFoundError when base/ contains only
      pool/ and sheets/. The composite SHEET is resolved via
      ref_resolver.resolve_sheet_asset (gated by use_composite_sheets) for BOTH video
      (REC-213 C1+C2) and locked boards (REC-213 C3), so for tartarus the sheet path
      is taken before that broken angle/pool fallback is reached.
    note: >
      This entry describes the truth, not a working capability. The code-level
      fix is out of scope for this docs-only phase: pick one resolver as SSOT
      (TRACE recommends resolve_entity_refs because production payload assembly
      calls it), make it read the pool through a manifest pointer, and retire
      or delegate ProjectPaths.resolve_ref.
      REC-213 C1+C2: composite-SHEET existence+validity is owned by
      ref_resolver.resolve_sheet_asset (the canonical_target bundle lineage) and
      per-kind layout by ProjectPaths.sheet_path; the prior dispatch_payload
      composite-sheet helpers were DELETED (no surviving parallel sheet path).
      Flat-ref dict-lineage unification stays deferred (C5).

  ref_promotion:
    canonical: null
    state: KNOWN-BROKEN
    status: KNOWN-BROKEN
    tracking_issue: "Linear REC-34 design prior art; Linear REC-76 follow-up: ref resolver broken / version-split from paths layer"
    note: >
      No working ref_promotion command or char.json schema exists today. Existing
      mechanisms are incompatible and not authoritative: promote_grid copies,
      prep_character_angles renames, and asset_ops.set_hero copies toward
      subject top-level paths with non-conformant names, while the production
      resolver does not read the pool. The intended manifest-pointer design is
      a code/schema follow-up, not an active capability.

  dispatch_entry_point:
    canonical: recoil/pipeline/core/dispatch.py::dispatch
    state: active
    updated: 2026-06-05
    deprecated_paths:
      - ProductionLoop
      - run_episode.py
      - ClientSequenceRunner
    live_path:
      - recoil/pipeline/orchestrator/episode_runner.py::EpisodeRunner.run_episode_batches
      - recoil/pipeline/core/workflow.py::Workflow.run
      - recoil/pipeline/core/dispatch.py::dispatch
      - recoil/pipeline/core/registry.py::RunnerRegistry.get
      - recoil/pipeline/core/runners/video_runner.py::VideoRunner.run
      - recoil/pipeline/core/runners/r2v_multi_runner.py::R2VMultiRunner.run
      - recoil/execution/step_runner.py
      - recoil/execution/video_model_client.py::VideoModelClient
      - recoil/execution/providers/flora.py::FloraAdapter
    note: >
      Replaces the stale tombstoned/expiry entry. dispatch() remains the CP-5
      single dispatch surface. The live production entry is EpisodeRunner:
      Beats create Takes, Takes run Workflows, each WorkflowStep calls dispatch(),
      and the registered runner delegates to StepRunner.

  orchestration:
    canonical: recoil/pipeline/orchestrator/episode_runner.py::EpisodeRunner.run_episode_batches
    state: active
    cp: CP-10
    replaces:
      - ProductionLoop
      - run_episode.py
      - ClientSequenceRunner
    note: >
      EpisodeRunner is the converged production path. pipeline/cli/generate.py
      loads the plan, builds ExecutionStore + StepRunner, cold-starts
      LearningEngine -> StrategyEngine, constructs EpisodeRunner, then awaits
      run_episode_batches(). EpisodeRunner clusters shots into Batches, converts
      each Batch to a Scene of Beats, runs Beats under an asyncio semaphore, and
      performs stale-take recovery, phantom-success invalidation, and current-ref
      fingerprint revalidation before dispatch.

  scene_beat_take:
    canonical: recoil/pipeline/core/take.py
    state: active
    model: Scene -> Beat -> Take
    note: >
      Scene is a thin grouping of Beats; Beat groups multiple Takes for a
      logical shot and selects primary_take_id; Take wraps exactly one Workflow
      and executes it. EpisodeRunner materializes this model from shot batches.

  workflow_take_model:
    canonical: recoil/pipeline/core/workflow.py
    related:
      - recoil/pipeline/core/take.py
    state: active
    cp: CP-6/CP-7
    note: >
      Workflow is a DAG-shaped model with current linear execution semantics.
      WorkflowStep.modality is dispatched through dispatch(); Take is the
      editorial attempt wrapper around one Workflow. CP-9 eval hooks exist as
      substrate, but EpisodeRunner does not attach them in production, so
      production receipts still persist eval_scores as {}.

  modality_registry:
    canonical: recoil/pipeline/core/registry.py::RunnerRegistry
    bootstrap: recoil/pipeline/core/dispatch.py::register_default_runners
    state: active
    modalities:
      - image_t2i
      - video_i2v
      - r2v_multi
      - storyboard
      - audio_t2a
      - lipsync_post
      - eval_image_v1
      - eval_video_v1
      - eval_audio_v1
    note: >
      dispatch() resolves the runner with get_runner(modality). Default live
      registration is performed by register_default_runners(step_runner), which
      registers ImageRunner, VideoRunner, R2VMultiRunner, StoryboardRunner,
      AudioRunner, and LipSyncPostProcessor. Eval runners are opt-in via
      register_default_eval_runners() and are not automatically bootstrapped.

  worksurface_execution:
    canonical: recoil/execution/providers/flora.py::FloraAdapter
    provider_id: flora
    state: active
    role: worksurface_and_execution_target
    flow:
      - upload_local_refs
      - POST /generate
      - poll /runs/{run_id}
      - download video_url
      - save to renders/ep_NNN/
    note: >
      FloraAdapter uploads local reference_images and local start/base images to
      Flora before submit, infers action from payload shape (t2v/i2v/r2v/f2v and
      image variants), maps Recoil model ids to modality-prefixed Flora model ids,
      and posts to /generate. VideoModelClient polls until completion and
      downloads PollResult.video_url bytes; StepRunner then writes .mp4 files to
      the episode-scoped video_dir, which maps to core ProjectPaths
      episode_renders_dir = renders/ep_NNN/. Flora is the decided execution
      target; flora_projects.py is the canonical per-episode Flora project
      resolver. Recoil remains the provenance/validation brain.

  output_layout:
    canonical: recoil/core/paths.py::ProjectPaths
    state: active
    version: v3
    canonical_paths:
      refs: assets/{char,loc,prop}/<slug>/base/pool/{identity,turn,expr,loc,prop,scene}/
      composite_sheets: "per-kind via ProjectPaths.sheet_path — char: assets/char/<slug>/base/sheets/, loc: assets/loc/<slug>/sheets/, prop: assets/prop/<slug>/ (loc/prop do NOT use base/sheets/)"
      planning_frames: prep/ep_NNN/
      storyboards: prep/ep_NNN/storyboards/
      final_video: renders/ep_NNN/*.mp4
      boundary_frames: renders/ep_NNN/boundary_frames/
      visual_state: _pipeline/state/visual/
    deprecated_paths:
      - output/
      - sequences/
    note: >
      output/ is v1 and sequences/ is v2; both may still physically exist in
      migrated project trees, but they are not canonical. ProjectPaths.sequences_dir
      raises DeprecatedPathAPIError. Previz and keyframes share prep/ep_NNN/.
      Final videos are written to renders/ep_NNN/ by the episode-scoped
      execution.step_types.ProjectPaths adapter.

  project_path_construction:
    canonical: recoil/core/paths.py::ProjectPaths
    state: active
    version: v3
    updated: 2026-06-05
    deprecated_paths:
      - recoil/core/paths.py::project_refs_dir
      - recoil/core/paths.py::project_output_dir
      - recoil/core/paths.py::ProjectPaths.asset_kind_dir
      - recoil/core/paths.py::ProjectPaths.sequences_dir
    note: >
      ProjectPaths is the project-scoped v3 path SSOT. Current asset taxonomy is
      subject-first: assets/{cls}/{subject}/base/, with pool refs under
      base/pool/{kind}/. Composite-sheet layout is per-kind via ProjectPaths.sheet_path
      (char base/sheets/, loc sheets/, prop subject-root) — the single sheet-layout
      home that the reader (ref_resolver.resolve_sheet_asset) and the writer
      (generate_composite_sheet._sheet_dest) both call. episode_prep_dir()
      returns prep/ep_NNN/ and episode_renders_dir() returns renders/ep_NNN/.
      asset_kind_dir() raises DeprecatedPathAPIError; sequences_dir also raises
      and exists only as a deprecated v2 artifact tree.

  strategy_engine:
    canonical: recoil/pipeline/orchestrator/strategy_registry.py::StrategyEngine
    state: active_with_known_wire_gaps
    note: >
      EpisodeRunner classifies failed Takes with detect_failure_mode(), asks
      StrategyEngine.select_and_apply() for a StrategyDiff, and overlays the
      latest diff onto the next Workflow before Take.execute(). Current code
      routes seed and tier through provider_hints so those two fields reach
      the runner/adapter path; many other StrategyDiff fields remain weak or
      unwired to runner inputs (for example element_config, format_mode, and
      anchor_duration_s). Identity-drift classification is still limited by
      missing identity_score population on the multi-shot path.

  budget_guard:
    canonical: recoil/pipeline/_lib/budget_manager.py::BudgetGuard
    state: active
    note: >
      EpisodeRunner owns one BudgetGuard per run. Each non-dry-run Beat attempt
      estimates take cost, reserves with would_exceed(), charges succeeded or
      failed-but-billed attempts, and releases unrealized reservations on
      exceptions or zero-cost failures. BudgetExhaustedError halts the scene.

  typed_payload:
    canonical: recoil/execution/providers/payload_hints.py::StepRunnerHints
    state: transitioning
    expires_on: "2026-06-30"
    note: >
      StepRunnerHints carries provider_hints through StepRunner into provider
      adapter params while extra="allow" remains active. The live retry overlay
      currently relies on this channel for seed/tier to reach Flora/fallbacks.

  prompt_building:
    canonical: recoil/pipeline/_lib/prompt_engine.py
    state: active
    note: >
      CP-3 SSOT-ified. One prompt engine, all builders via get_builder(). Do not
      create parallel prompt builder files.

  dispatch_dashboard_render:
    canonical: recoil/pipeline/tools/recoil_dashboard.py
    state: active
    updated: 2026-06-21
    note: >
      Read-only at-a-glance build/PR dashboard (the DETERMINISTIC local-join capability:
      BUILDING/CAPPED/OPEN-PRS axes). Joins ~/.recoil/dispatch-runs/*/status.json
      (default; env RECOIL_DASHBOARD_STATUS_ROOT / --status-root) + `gh pr list` at render
      time. Writes nothing; never contacts Linear. The /dashboard skill
      (skills/dashboard/SKILL.md) is agent-interpreted markdown that SHELLS OUT to this
      capability and adds the in-session Linear MCP read + unified render — it is NOT a
      separate manifest capability (no executable code/state to own; manifest capabilities
      are code modules, not agent prompts). v1 of recoil-dashboard-2026-06-19.

  scene_version_manifest_writer:
    # The SINGLE owner of every mutation to ep_NNN_BATCH_NNN.versions.json:
    # append-candidate, conform, revert, mark-derived. persistence.py holds only
    # manifest path/load/parse primitives + the immutable version-body save_scene.
    canonical: recoil/pipeline/core/scene_version_store.py::SceneVersionStore
    state: active
    updated: 2026-06-21
    deprecated_paths: []

  scene_active_body_persistence:
    # Active-version-aware scene body read/write primitives: load_scene_active
    # (pointer-faithful read), load_scene_active_with_version (atomic (Scene, int)
    # read — the TOCTOU-safe primitive status writers use), active_scene_body_path
    # (active-body resolver), and save_active_scene_status (the structure-guarded
    # in-place status writer that enforces structural immutability). The canonical
    # scene-body persistence surface, distinct from the manifest writer.
    canonical: recoil/pipeline/core/persistence.py
    state: active
    updated: 2026-06-21
    deprecated_paths: []

  scene_version_read_model:
    # The selector-scoped scene-VERSION read model (get_scene_versions) +
    # SceneVersionsModel schema — distinct from board_read_model (which the manifest
    # scopes to selector board reads ONLY). Surfaces active_version + unpointed
    # candidate diagnostics; pure projection, never writes.
    canonical: recoil/workspace/readmodel.py::get_scene_versions
    state: active
    updated: 2026-06-21
    deprecated_paths: []

  atom_read_model:
    # The BEAT-GRAIN atom-version resolver (resolve_atom_version + the get_atom companion
    # in the same module) — a NEW read-model surface no prior capability owned
    # (atomic-vision Phase 0, REC-235). DISTINCT from board_read_model (get_board, the
    # per-SELECTOR board read) and scene_version_read_model (get_scene_versions, the
    # selector-scoped scene-version read): different grain (atom/beat vs board/selector),
    # no overlap. A pure projection, no premature DB.
    # EXTENDED (atomic-vision Phase 2, REC-235; SYNTHESIS Decision 3: "one index serves
    # continuity-assessment pairwise views AND cross-batch CUT assembly"): the SAME index
    # module (recoil/workspace/readmodel.py) also owns three cross-batch query/assembly
    # surfaces — wardrobe_continuity_check (continuity-assessment index query over atom
    # facets), build_cut_composition + build_pairwise_view (index-served cross-batch CUT
    # assembly). EXTENDS this index capability, NOT a new capability key; NOT an overlap
    # with composition_manifest (which still owns ONLY the data shape — these builders
    # CONSTRUCT that shape, they do not redefine it).
    canonical: recoil/workspace/readmodel.py::resolve_atom_version
    state: active
    updated: 2026-06-23
    scope: "BEAT-GRAIN atom-version resolver (resolve_atom_version, with get_atom as the beat-grain companion in the same module): address atom:{episode}/beat/{beat_id}@tN -> artifact + continuity facets, resolved against the ACTIVE scene version (REC-231 canon pointer), batch-INDEPENDENT, a pure projection (never a DB; no premature DB service/index). EXTENDED (Phase 2, REC-235; SYNTHESIS Decision 3 — one index serves continuity-assessment pairwise views AND cross-batch CUT assembly): the SAME readmodel.py index module also owns three cross-batch query/assembly surfaces — wardrobe_continuity_check (a continuity-assessment index query over atom wardrobe facets), build_cut_composition and build_pairwise_view (index-served cross-batch CUT assembly that CONSTRUCT a CompositionManifest from atom-version URNs in DIFFERENT batches/episodes). These EXTEND this index capability (NOT a new capability key) and do NOT overlap composition_manifest, which still owns ONLY the data shape — these builders construct that shape, they do not redefine it. DISTINCT from board_read_model (the per-SELECTOR pointer-faithful board read, get_board) and from scene_version_read_model (the selector-scoped scene-version read, get_scene_versions) — different grain (atom/beat vs board/selector), no overlap."
    deprecated_paths: []
    note: >
      Beat-grain atom read-model (atomic-vision Phase 0, REC-235). The atom = Beat
      (stable identity slot) + Take (versioned candidate @tN, append-only). Two resolvers
      live in recoil/workspace/readmodel.py: resolve_atom_version(atom_version_urn,
      project) -> AtomVersionModel (artifact + continuity facets + is_canon), and the
      beat-grain companion get_atom(beat_urn, project) -> AtomModel (ordered @tN, the
      canon pointer from Beat.primary_take_id, beat-grain newer_unpointed_versions). Both
      resolve against the ACTIVE persisted Scene->Beat->Take tree (the REC-231 canon
      pointer via load_scene_active_with_version) WITHOUT loading or touching the origin
      batch board. A read-model PROJECTION, never a persisted store; a SQLite/DB index is
      explicitly DEFERRED (no premature DB). Distinct grain from board_read_model
      (get_board, per-selector board read) and scene_version_read_model (get_scene_versions,
      selector-scoped scene-version read) — atom/beat vs board/selector, no overlap.
      EXTENDED (atomic-vision Phase 2, REC-235; SYNTHESIS Decision 3: "one index serves
      continuity-assessment pairwise views AND cross-batch CUT assembly"): the SAME index
      module recoil/workspace/readmodel.py also owns three cross-batch query/assembly
      surfaces. wardrobe_continuity_check(atom_version_urns, project) -> ContinuityReport is
      a continuity-assessment index query that flags any char whose wardrobe_phase_id differs
      across the RESOLVED atoms (pure projection over resolve_atom_version facets; no writes).
      build_cut_composition(atom_version_urns, layout) and build_pairwise_view(urn_a, urn_b)
      are the index-served cross-batch assembly builders: they CONSTRUCT a CompositionManifest
      (kind="CUT") from atom-version URNs drawn from DIFFERENT batches/episodes, rendered by
      the existing render_composition_view (zero generation). All three EXTEND this index
      capability (atomic-vision Phase 0's resolver) — NOT a new capability key — and do NOT
      overlap composition_manifest: that capability still owns ONLY the composition data
      shape; these builders construct that shape, they do not redefine it. DISTINCT from
      per_atom_regeneration (atomic-vision Phase 3, the write-side regenerate/approve
      surface in recoil/pipeline/core/atom_regen.py) — that capability MUTATES state
      (appends takes, moves the canon pointer); this one is a pure projection / data shape,
      no overlap.

  composition_manifest:
    # The ordered-pointer composition PRIMITIVE / data shape (atomic-vision Phase 1, REC-235).
    # OWNS exactly ONE thing: the composition data shape — an ordered list of atom-version URN
    # members + a layout hint + a kind — canonical in the CompositionManifest model. The pure
    # on-demand VIEW renderer recoil/pipeline/_lib/board_builder.py::render_composition_view
    # (Phase 3) is a CONSUMER of this shape, never a generator, and is NOT a second canonical
    # path. NOT yet the durable board SSOT — persistence / composition_ref wiring is DEFERRED.
    canonical: recoil/api/schemas/composition.py::CompositionManifest
    state: active
    updated: 2026-06-23
    scope: "the ordered-pointer composition data shape (atom-version URN members + layout + kind), canonical in recoil/api/schemas/composition.py::CompositionManifest. OWNS only the data shape — the on-demand VIEW renderer render_composition_view (board_builder, Phase 3) CONSUMES it and is NOT a second canonical path, never a generator. DISTINCT from board_read_model (get_board, per-SELECTOR pointer-faithful board read), scene_version_read_model (get_scene_versions), and atom_read_model (resolve_atom_version, beat-grain atom-version resolver). NOT yet the durable board SSOT — persistence/composition_ref wiring DEFERRED; never generates."
    deprecated_paths: []
    note: >
      Composition-manifest primitive (atomic-vision Phase 1, REC-235). Board = a
      composition manifest: an ordered list of pointers to atom-versions
      (atom:{episode}/beat/{beat_id}@tN) + a layout hint + a kind in
      {CONT, ONER, COVERAGE, BROLL, PICKUP, CUT}. The grid/strip PNG is a derived,
      disposable VIEW rendered on demand by render_composition_view (Phase 3, a
      board_builder CONSUMER that composites the atoms' EXISTING artifacts — zero
      generation, zero model call). This capability OWNS only the data shape
      (recoil/api/schemas/composition.py::CompositionManifest, the single ownership
      surface per the granularity rule); the renderer is NOT a second canonical path.
      DISTINCT from board_read_model (get_board, the per-selector pointer-faithful board
      read), scene_version_read_model (get_scene_versions), and atom_read_model
      (resolve_atom_version, the beat-grain atom-version resolver) — no overlap. NOT yet
      the durable board SSOT: persisting the manifest + wiring the composition_ref pointer
      into the REC-231 manifest.execution.boards / Beat.board is DEFERRED to a follow-on
      slice. Phase 1 is additive — it does not modify Beat.board or the generated-storyboard
      path, and never generates. DISTINCT from per_atom_regeneration (atomic-vision Phase 3,
      the write-side regenerate/approve surface in recoil/pipeline/core/atom_regen.py) —
      that capability MUTATES state (appends takes, moves the canon pointer); this one is a
      pure projection / data shape, no overlap.

  per_atom_regeneration:
    canonical: recoil/pipeline/core/atom_regen.py::regenerate_atom
    state: active
    updated: 2026-06-23
    scope: "Per-atom regeneration write-side surface (atomic-vision Phase 3, REC-235; SYNTHESIS Decisions 1/5/6). regenerate_atom(project, beat_urn, generate, *, take_metadata=None) -> str appends a NEW Take to the active Beat via an INJECTED generate callable (append-only — never mutates/overwrites a prior take) and returns the new atom-version URN; set_canon(project, beat_urn, atom_version_urn) -> None (alias approve) moves the canon pointer Beat.primary_take_id to the approved take (prior takes + bodies untouched). Persists through the REC-231 save_active_scene_status in-place writer (takes + primary_take_id are non-structural, so the structure guard passes). The generation step is INJECTED — this capability NEVER calls VideoRunner/dispatch itself; the live wiring point is documented at regenerate_atom's generate seam (the individual video_i2v VideoRunner path; identity from composite sheets). DISTINCT from atom_read_model (pure READ projection — resolve_atom_version/get_atom, no writes) and composition_manifest (the composition DATA SHAPE only): this is the first WRITE-side per-atom operation, a different concern (state mutation vs projection vs shape), no overlap. NOT a re-cut/composition writer — re-rendering the cut is the existing render_composition_view CONSUMER (zero generation)."
    deprecated_paths: []
    note: >
      Per-atom regeneration write-side surface (atomic-vision Phase 3, REC-235). The
      atom = Beat (stable identity) + Take (append-only @tN candidate). regenerate_atom
      creates a NEW take under the EXISTING beat (SYNTHESIS Decision 1) — never an
      overwrite — and approval (set_canon, alias approve) moves the beat→@tN canon pointer
      Beat.primary_take_id (the scene-versioning pointer at beat grain) to the approved
      take. Both hold the append-only + pointer-move invariants (SYNTHESIS Decision 6 —
      every layer append-only + revertible): prior takes and their bodies are never
      mutated. Per-atom video runs via the individual VideoRunner path with identity from
      composite character sheets (SYNTHESIS Decision 5), but this capability keeps that
      generation step INJECTED (a callable) so the build + tests NEVER dispatch. Persistence
      is the REC-231 active-version in-place writer (save_active_scene_status); takes and
      primary_take_id are non-structural, so the structure guard passes. NOT yet wired into
      the durable board SSOT — the beat.board → manifest flip is DEFERRED to REC-240.
