--- name: video-editing-architecture-contract description: Load when changing module boundaries, workflow orchestration, domain models, ports/adapters, project-folder protocols, state transitions, render approval, local-model integration, or persistence in the video-editing service; use it to preserve current contracts while moving the code toward a production-grade modular monolith. --- # Video Editing Architecture Contract Use this contract before changing a workflow boundary, state transition, persisted JSON shape, project directory, scheduler, renderer, model worker, or adapter. Treat it as a map of the implementation as verified on **2026-07-21**, not as proof that the target architecture already exists. ## Do not use this skill for | Need | Load instead | |---|---| | Decide whether a proposed change is allowed, gated, or ready to merge | `video-editing-change-control` | | Reproduce a build or repair a workstation/runtime | `video-editing-build-and-env` | | Look up a property, default, or environment variable | `video-editing-config-and-flags` | | Diagnose a concrete failure or stuck project | `video-editing-debugging-playbook` | | Decide whether media output meets its evidence bar | `video-editing-validation-and-qa` | | Operate schedulers, inputs, projects, or outputs | `video-editing-run-and-operate` | | Improve highlight selection or cinematic quality | `video-editing-cinematic-highlights-campaign` | Do not use architecture language to waive a quality gate. Route every behavior-changing proposal through `video-editing-change-control`. ## Define the terms - **Capability**: a cohesive business responsibility, such as upload-and-clipping or cinematic highlight production. - **Domain**: business concepts and invariants that can be tested without Spring, HTTP, JPA, FFmpeg, or the filesystem. - **Application service**: orchestration for one use case; it calls domain behavior and outbound ports. - **Port**: an interface owned by the core that expresses a required inbound or outbound capability. - **Adapter**: framework or infrastructure code implementing a port, such as JPA, S3, FFmpeg, or a controller. - **Filesystem state machine**: a workflow in which directory location and artifact presence act as queue and state signals. - **Contract artifact**: a persisted file whose name, location, or JSON shape is consumed by another stage. - **Modular monolith**: one deployable application with enforced capability boundaries and inward-pointing dependencies. One Maven module or one JVM process alone does not make the code modular. ## Start with the honest baseline As of 2026-07-21, this is one Spring Boot application and one Maven module. It contains four workflows with different contracts. The upload-and-clipping workflow has recognizable ports and adapters. The folder workflow is a small vertical slice. The `editing` package contains multi-clip and single-source highlight domains, orchestration, filesystem persistence, process execution, and model-worker integration together. The project does **not** currently have Spring Modulith, ArchUnit, automated package-boundary rules, a module diagram in code, or enforced clean-architecture dependency direction. `VideoAssetService` imports HTTP request/response DTOs, and `EditProjectController` invokes `EditRenderer` directly. Do not describe the current tree as a completed hexagonal architecture. ## Map the four workflows | Workflow | Trigger and orchestrator | Durable state | Main outputs | Representative tests | |---|---|---|---|---| | API upload and clipping | `/v1/video-assets`, `/v1/clip-jobs`, and `/v1/clips`; `VideoAssetService`; `ClipJobQueuePort`; `ClipProcessor` | Repository records, object keys, and optionally `queue_messages` | Uploaded clip objects plus `Clip` records and signed download URLs | `VideoAssetControllerTest`, `DatabaseBackedClipJobQueueAdapterTest` | | Folder segmentation | Poll `input-directory`; `FolderVideoScanScheduler` -> `FolderVideoValidator` -> `FolderFfmpegClipper` | File location: source -> working -> processed/rejected; `.failed` is last-resort quarantine | `output-directory/[-N]/clip_%05d.` | `FolderVideoScanSchedulerTest`, `FolderFfmpegIntegrationTest` | | Multi-clip cinematic edit | REST API or poll `editing.local-director.source-directory`; `LocalDirectorScheduler`, `EditPlanInboxScanner`, `FfmpegEditRenderer` | `project.json`, analysis/plan/manifest JSON, inbox file names, and source-folder location | One project `final.mp4`, `render-manifest.json`, `qa-report.json` | `CinematicEditingIntegrationTest`, `EditPlanInboxScannerTest`, `FfmpegEditRendererTest` | | Single-source highlights | Poll `editing.highlight-scheduler.source-directory`; `HighlightSourceScheduler`, `HighlightDirectorPlanScanner`, `HighlightDirectorFlowService` | Highlight project tree, `project.json`, director-plan presence, approval marker, final-file presence | Per-highlight final/preview/QA/manifests plus project `final.mp4` | `HighlightSourceSchedulerTest`, `HighlightDirectorFlowServiceTest`, `HighlightFfmpegRendererTest` | Do not connect two workflows merely because both call FFmpeg or share records. First identify the owner of the use case, state, retry semantics, and output contract. ## Preserve the implemented dependency map ### Upload and clipping Follow this current call direction while improving it incrementally: ```text api controllers -> application.VideoAssetService -> application.VideoClippingRepository -> queue.ClipJobQueuePort -> storage.ObjectStoragePort queue adapters -> processing.ClipProcessor processing.ClipProcessor -> application.VideoClippingRepository -> processing.VideoClipperPort -> storage.ObjectStoragePort outbound implementations: infrastructure.InMemoryVideoAssetRepository persistence.JpaVideoClippingRepository queue.InMemoryClipJobQueueAdapter / DatabaseBackedClipJobQueueAdapter processing.StubVideoClipperAdapter / FfmpegVideoClipperAdapter storage.InMemoryObjectStorageAdapter / S3ObjectStorageAdapter ``` Treat `VideoClippingRepository`, `ClipJobQueuePort`, `VideoClipperPort`, and `ObjectStoragePort` as existing contracts. Do not make core business behavior depend on JPA entities, Spring Data repositories, S3 types, or `ProcessBuilder`. Current exception: `VideoAssetService` consumes types from `api.dto`. Move transport-to-command mapping to the inbound adapter when changing this surface; preserve API compatibility and add characterization tests first. ### Folder segmentation Treat `org.example.videoclips.folder` as a self-contained file-ingestion capability. It currently calls FFprobe and FFmpeg directly through package-private functional test seams. Do not route folder jobs through the API repository or object storage unless a stated product requirement unifies their ownership and recovery model. ### Editing workflows Recognize these existing interfaces, but do not mistake their package location for enforced isolation: | Port-like interface | Current owner and purpose | |---|---| | `EditProjectStore` | Multi-clip project tree and top-level JSON artifacts | | `HighlightProjectStore` | Single-source highlight project tree and nested artifacts | | `EditRenderer` | Render a saved multi-clip project by project ID | | `VoiceoverGenerator` | Produce project voiceover from timed lines | | `EditAssetProvider` / `EditAssetLibrary` | Resolve or select local media assets | | `VisualAnalysisProvider` | Analyze a clip from metadata, thumbnails, and shots | Keep application-facing interfaces free of `Process`, HTTP client, and framework-specific return types. Introduce a port only for a concrete substitution, isolation, or testability need. ## Preserve each state machine ### API upload and clipping states ```text VideoAsset: PENDING_UPLOAD -> UPLOADED -> PROCESSING -> READY | failure/retry -> UPLOADED any retained terminal asset -> DELETED ClipJob: QUEUED -> RUNNING -> SUCCEEDED | -> FAILED QUEUED or RUNNING -> CANCEL_REQUESTED -> CANCELLED ``` Enforce these invariants: - Create a job only for an `UPLOADED` or `READY` asset. - Keep tenant ownership checks at every read or mutation boundary. - Preserve idempotency for create-asset and create-job requests. - Store generated media before exposing a persisted clip as downloadable. - Return an asset to `UPLOADED` after a failed or cancelled processing attempt. - Keep DB-queue retry ownership in `DatabaseBackedClipJobQueueAdapter`; do not persist terminal failure from `ClipProcessor` before retries are exhausted. ### Folder file states ```text source candidate --atomic move--> working working --valid + FFmpeg success--> processed working --invalid or processing failure--> rejected working --rejection move failure--> .failed beside active file ``` Preserve one-file-per-scan behavior, natural filename ordering, refusal to overwrite a destination, and exclusion of hidden/partial/failed files. Atomic moves assume directories share a filesystem; treat cross-device deployment as unsupported until tested and designed. ### Multi-clip edit states The implemented local-director path normally follows: ```text CREATED -> ANALYZING -> ANALYZED -> WAITING_FOR_DIRECTOR -> PLANNED -> RENDERING -> RENDERED any handled workflow/render failure -> FAILED ``` `PLANNING` exists in `EditProjectStatus` but is not part of the normal scheduler sequence. `EditProjectService.updateProject` does not validate legal transitions. Add transition enforcement before relying on status as a concurrency or authorization boundary. The source folder also moves `source -> working -> processed/rejected`. An accepted inbox plan moves from `inbox/edit-plan.json` to `inbox/edit-plan.json.accepted`; a rejected plan becomes `.rejected`. Approval is signaled by `inbox/approved.flag` when configured. ### Single-source highlight states The observed new-project path is: ```text CREATED -> WAITING_FOR_DIRECTOR -> RENDERING -> RENDERED invalid empty director plan -> FAILED ``` `ANALYZING` and `PLANNED` exist in `HighlightProjectStatus`, but the primary scheduler/flow does not currently persist them. The render scanner considers a project renderable when `director/edit-plan.json` exists and project `final.mp4` does not. Its in-process `AtomicBoolean` prevents overlapping scans only inside one JVM; it is not a distributed claim. Do not infer progress solely from the enum. Check artifacts and logs. Before horizontal scaling, add durable claims, leases, idempotent stages, and recovery tests. ## Preserve the two editing contracts separately ### Multi-clip contract The root is `video-clipping.editing.project-directory` (default `./output/edit-projects`). A project currently uses: ```text / project.json analysis.json cinematic-highlight-analysis.json category.json highlight-candidates.json thumbnails/ contact-sheets/ proxies/ audio/ inbox/ edit-plan.json final.mp4 render-manifest.json qa-report.json ``` `EditProject` describes an input directory containing multiple clips. `EditPlan.decisions` can reference multiple clip IDs and builds one combined timeline. ### Single-source highlight contract The root is `video-clipping.editing.highlight-project-directory` (default `./output/highlight-projects`). `FileSystemHighlightProjectStore` creates: ```text / project.json manifest.json source/ analysis/ ffprobe.json scene-segments.json audio-analysis.json visual-analysis.json source-analysis.json frames/ contact-sheets/ proxies/ audio/ director/ director-brief.md director-prompt.md edit-plan.json approved.flag # only when approval is required assets/{voiceover,music,sfx,overlays}/ highlights// storyboard.md edit-plan.json assets/ final.mp4 preview.mp4 render-manifest.json qa-report.json final.mp4 render-manifest.json ``` `HighlightFolderContract.standard()` lists intended names such as `shots.json`, `scenes.json`, `transcript.json`, `audio-events.json`, `category.json`, and `highlight-candidates.json`. The source analyzer writes its five technical analysis JSON files; the scheduler's `HighlightCandidateGenerator` separately writes `category.json` and `highlight-candidates.json`. `shots.json`, `scenes.json`, `transcript.json`, and `audio-events.json` remain manifest intentions, so do not claim every listed artifact exists. `HighlightDirectorPlan` selects several windows from exactly one source and carries story purpose, visual treatment, music/SFX directions, voiceover, and overlays. `HighlightDirectorFlowService` converts each item to a single-decision `EditPlan`, renders per-highlight outputs, then concatenates them. ### Why the contracts remain separate Keep them separate because they have different aggregates, input cardinality, director schemas, folder protocols, approval locations, output cardinality, scheduler ownership, and recovery signals. Sharing `EditPlan`, `RenderManifest`, or analysis helpers is implementation reuse, not schema equivalence. Do not merge the stores, statuses, or roots until all of these exist: - A versioned replacement contract and explicit migration/compatibility decision. - Golden JSON fixtures for both schemas and backward-read tests. - Recovery tests from every durable stage boundary. - A collision strategy for project IDs and artifact names. - An approval model that cannot be bypassed by REST or scheduler entry points. - A rollout and rollback plan approved through `video-editing-change-control`. ## Enforce load-bearing invariants Apply this checklist to every architecture change: - [ ] Keep project IDs restricted to `[A-Za-z0-9][A-Za-z0-9._-]{0,127}` and reject `..`. - [ ] Normalize and containment-check every project-relative path before reading or writing it. - [ ] Never accept an absolute path or backslash-containing relative artifact name in the highlight store. - [ ] Refuse overwrite when claiming source files; never silently replace an existing processed/rejected input. - [ ] Treat source media and completed contract artifacts as immutable inputs to later stages. - [ ] Validate project ID, source identity, time ranges, supported effects, and required licensed assets before rendering. - [ ] Persist an artifact successfully before advertising the status that promises it exists. - [ ] Make retries idempotent: never duplicate a rendered highlight, queue message, clip record, or asset charge. - [ ] Record model identity/version, input hash, asset provenance/license, FFmpeg command, and output hash for reproducibility. - [ ] Derive acceptance from measured media probes and approved creative review, never file existence or visual impression alone. - [ ] Keep transport DTOs, JPA entities, and worker-process details outside the domain. - [ ] Keep framework annotations out of new domain objects unless a decision record justifies them. ## Enforce the local-runtime and render prohibitions The following are project directives, even where current defaults or code contradict them: 1. Do not download dependencies or models automatically at startup, test time, or job time. 2. Do not call external AI services. Model/media inference must use in-process calls or approved non-network inter-process communication; loopback HTTP is still network and is not a certified path. 3. Do not permit model/media inference or acquisition to use any network, including loopback. Pre-provision and verify dependencies, model weights, and licenses during an approved artifact-build process. Production API, database, storage, and telemetry integrations remain separately approved and secured network boundaries. 4. Do not ingest or generate assets without recorded licensing and provenance. 5. Do not substitute silence, sine tones, or placeholder media for missing voiceover, music, or SFX. 6. Do not render without explicit approval bound to the exact plan and source digest. 7. Do not change production-facing defaults without change-control approval, compatibility analysis, and rollback evidence. Current disqualifying gaps include: - `tools/run_local_asset_worker.sh` and `tools/run_local_cv_worker.sh` install Python dependencies in `auto` mode; the CV script can load/download YOLO weights, and AudioCraft `get_pretrained` can fetch models. - The 2026-07-21 working tree removes worker silence/tone/host-speech fallbacks and makes requested assets mandatory at both renderer boundaries. Standalone bootstrap scripts and local-CV heuristic fallback remain noncompliant gaps. - `application.yml` enables highlight ingestion but now defaults highlight rendering off and director approval on. Approval remains a bare, non-digest-bound file. - `POST /v1/edit-projects/{projectId}:render` invokes the renderer without an approval check. - `HighlightFfmpegRenderer` can copy video forward after audio mixing fails, and several QA checks are hard-coded `true` rather than measured. Treat these as open architecture defects. Do not operate them as production behavior, and do not hide them with documentation. Repair them only through the change-control and validation skills. ## Know the current weak points | Weak point | Evidence and consequence | Required direction | |---|---|---| | No enforced modules | One Maven module; no ArchUnit/Modulith dependency test | Add capability-oriented boundaries and fail the build on violations | | Anemic state records | Public records allow unchecked states; update methods accept arbitrary status | Put invariants and legal transitions in domain/application code | | Filesystem is database and queue | Direct JSON writes, artifact-presence polling, local `AtomicBoolean` guards | Add atomic publish, durable claim/lease, restart recovery, retention, and concurrency tests | | Highlight analysis is incomplete | New-source analyzer does not call `CinematicHighlightAnalyzer`; prompt tolerates absent category/candidates | Make multimodal candidate production an explicit, measured stage | | Director plan contract is gated but creative truth is not proven | `HighlightDirectorPlanValidator` validates identity, category/candidate linkage, safe IDs, ranges, configured bounds, and required directions; it does not verify visible claims, asset licenses, or artistic fit | Keep media review and explicit render approval; add provenance and content-grounding evidence before claiming production readiness | | Local worker boundary is porous | Shell bootstrap downloads; loopback endpoint is configurable; models are named rather than content-addressed | Package locked runtime/model artifacts; verify hashes; replace loopback HTTP with in-process or approved non-network IPC and deny inference networking | | QA can overstate success | Highlight checks assert duration, overlays, assets, and mastering without measuring output | Probe outputs and fail closed on production-required checks | | Renderer configuration leaks | Project concatenation uses literal `ffmpeg`/`ffprobe` while other stages use typed properties | Route executables and process policy through an outbound media-tool port | | OS assumptions leak | macOS `say`, local paths, atomic rename, and worker virtualenvs are runtime details | Prove equivalent macOS dev and Linux/VPS/cloud behavior; remove OS-specific production fallbacks | | API/core dependency points outward | Application service imports `api.dto`; controller directly calls renderer | Map DTOs at inbound adapters and expose application use cases | | Local project state blocks horizontal safety | Edit/highlight projects require shared local paths and have no distributed ownership | Define durable object/project storage before claiming cloud scalability | ## Move toward the target modular monolith Use capabilities, not technical-layer-only packages, as the top-level target. Keep one deployable unless measured requirements justify distribution. ```text org.example.videoclips clipping/ # upload sessions, assets, clip jobs, clip publication folderingestion/ # watched-folder segmentation workflow multiedit/ # multi-source project, plan, render lifecycle highlights/ # single-source analysis, direction, assets, render lifecycle media/ # approved FFmpeg/FFprobe and local-model runtime adapters platform/ # configuration, persistence wiring, security, observability ``` Within each capability, point dependencies inward: ```text inbound adapters -> application use cases -> domain outbound adapters -> application-owned outbound ports configuration -> all adapters for composition only domain -> no Spring, HTTP, persistence, filesystem, process, or sibling adapter package ``` Expose cross-capability access through a small documented application API or durable event. Do not import another capability's adapter or persistence entity. Do not create a generic base service, universal repository, universal mapper, CQRS infrastructure, event bus, or microservice without a measured requirement. ### Execute boundary migration safely 1. Characterize the current API, JSON, file names, state changes, and restart behavior with tests. 2. Write an ADR and route it through `video-editing-change-control`. 3. Define the destination capability and its public application API. 4. Move domain types without changing behavior; keep compatibility mapping at the old boundary. 5. Move outbound interfaces inward, then adapt JPA, filesystem, FFmpeg, and model workers outside them. 6. Add ArchUnit or Spring Modulith verification only when its rule corresponds to the documented dependency direction. 7. Run narrow tests, full tests, clean-checkout verification, and artifact contract tests. 8. Remove compatibility code only after persisted-project and API migration evidence is approved. For macOS development, Linux/VPS production, and cloud deployment, use the same platform-neutral application artifact and contract versions. Build signed platform-specific runtime/model/image bundles, then promote each bundle without rebuild for the same OS/CPU target. Permit environment-specific adapters/configuration, not environment-specific domain behavior. ## Record every major decision For every module split, new dependency, persistence change, state change, model/runtime choice, or contract version, record: 1. Requirement being addressed. 2. Selected approach. 3. Alternatives considered. 4. Benefits and trade-offs. 5. Operational consequences. 6. Security implications. 7. Automated and manual verification. 8. Revisit conditions. Label the target as `proposed`, `accepted`, `implemented`, or `verified`. Never call it production-ready until clean-checkout build, security, observability, recovery, load, media-quality, and operational gates all pass. ## Review checklist - [ ] Name the affected workflow and aggregate. - [ ] List every changed API, JSON, file, state, metric, and approval contract. - [ ] Show dependency direction before and after. - [ ] Keep domain behavior testable without Spring. - [ ] Add a port only where a real adapter boundary exists. - [ ] Prove restart and retry behavior at the changed durable boundary. - [ ] Prove path containment and tenant/project isolation. - [ ] Prove no startup/job-time downloads or external network calls occur. - [ ] Prove all assets and models are pre-provisioned, hashed, and licensed. - [ ] Prove approval binds source, plan, configuration, model, and asset digests. - [ ] Prove QA from measurements; reject placeholder audio and degraded render fallback. - [ ] Run the relevant workflow tests plus the full change-control gates. ## Provenance and maintenance Re-verify the package inventory: `find src/main/java/org/example/videoclips -type d | sort` Re-verify application wiring and schedulers: `rg -n '@SpringBootApplication|@EnableAsync|@EnableScheduling|@Scheduled|@ConditionalOn' src/main/java` Re-verify existing ports: `rg -n '^public interface ' src/main/java/org/example/videoclips/{application,processing,queue,storage,editing}` Re-verify outward application imports: `rg -n '^import org\.example\.videoclips\.(api|persistence|storage|queue|processing)' src/main/java/org/example/videoclips/{domain,application}` Re-verify statuses: `for f in src/main/java/org/example/videoclips/domain/ClipJobStatus.java src/main/java/org/example/videoclips/editing/{EditProjectStatus,HighlightProjectStatus}.java; do sed -n '1,120p' "$f"; done` Re-verify filesystem writes and render signals: `rg -n 'writeJson\(|Files\.(move|copy|write)|final\.mp4|approved\.flag' src/main/java/org/example/videoclips/{folder,editing}` Re-verify highlight artifacts against the declared folder contract: `rg -n 'analysis/.*\.json|director/.*\.json|HighlightFolderContract' src/main/java/org/example/videoclips/editing` Re-verify prohibited downloads and fallbacks: `rg -n 'pip install|get_pretrained|YOLO\(|write_silence|fallback[_ -]tone|anullsrc|sine=' tools src/main/java src/main/resources` Re-verify production-facing defaults: `sed -n '1,180p' src/main/resources/application.yml && sed -n '1,120p' src/main/resources/application.properties` Re-verify architecture enforcement dependencies/tests: `rg -n 'archunit|spring-modulith|ApplicationModules|ArchRule' pom.xml src/test || true` Re-run focused contract tests after architecture changes: `mvn -o -Dtest=VideoAssetControllerTest,DatabaseBackedClipJobQueueAdapterTest,FolderVideoScanSchedulerTest,EditPlanInboxScannerTest,FileSystemEditProjectStoreTest,FileSystemHighlightProjectStoreTest,HighlightDirectorFlowServiceTest test`