Skip to content

chore: merge main into development - #147

Merged
IAnMove merged 11 commits into
developmentfrom
chore/sync-main-into-development
Sep 5, 2026
Merged

chore: merge main into development#147
IAnMove merged 11 commits into
developmentfrom
chore/sync-main-into-development

Conversation

@IAnMove

@IAnMove IAnMove commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Resumen ejecutivo

Qué cambia

Merge commit de main (43f75b90, #143#145) en development (afdc0de7, #146).
Única resolución: intro de SLICE_QUEUE.md (base de integración + AGENT_QA_POLICY).

Para qué sirve

Dejar development como base de integración al día. A partir de aquí los PRs ordinarios apuntan a development.

Impacto para el usuario

Sin cambio de producto más allá de lo ya publicado en main (#143 música, #144 validación, #145 política QA) más la política de ramas ya en development (#146).

Riesgo

  • Bajo: merge de árboles ya revisados; un conflicto de documentación.

Estado

  • Listo para revisión
  • Mezclado

Summary

Authorized one-time sync: merge main into development with a merge commit (no squash, no force-push). Keep both the development integration-base paragraph and the agent QA merge wording.

Overview

Parents: afdc0de7 (development / #146) and 43f75b90 (main / #145). Future ordinary PRs target development.

Detailed changes

Backend

Music submission contract from #143.

UI and Wizard

None in the conflict resolution.

Data, provenance and compatibility

No data migrations.

Files and ownership

Conflict only in docs/development/SLICE_QUEUE.md. Other files take the already-merged main or development side via default merge.

Validation

  • pytest music + validate wrapper + development policy — 29 passed
  • scripts/check_documentation_links.py — PASS
  • scripts/verify_clean_repo.py — PASS
  • BASE_SHA=origin/development bash scripts/check_code_health_pr_base.sh — Ratchet passed
  • BASE_SHA=origin/development bash scripts/validate_local.sh — fast passed
  • Remote CI — inspect current head; not assumed by opening this PR
  • Cursor — not claimed

Code quality

No new production design. Incoming hotspot growth is from already-merged #143.

Risks and rollback

Revert this merge commit on development if needed. Do not force-reset. main is unchanged.

Dependencies and acceptance

User authorized this one-time merge of main into development. No auto-merge of later PRs.

Task cost report

External provider calls: 0. Real media generations: 0.


Note

Medium Risk
Changes the Story music job acceptance path in _launch_runtime.py with durable idempotency and concurrency rules; mistakes could duplicate jobs or strand reservations, though contract tests cover replay and races.

Overview
Syncs development with landed work from main: an idempotent music submission contract wired into the async MiniMax Story song job route, plus fast vs --full local validation, agent QA policy docs, and PR template tweaks.

Music (phase 4): New music_submission service reserves command/generation/task/candidate IDs and a TaskRegistry row before the provider worker starts, with JSON dedup by idempotency key + spec hash (replay vs 409), Story destination checks by ID (not title), and optional retry/new-version intents. POST …/music-candidates/jobs now calls that reservation first: replays return the live job or restart a single worker when the checkpoint is missing; concurrent replays must not spawn duplicate workers. The 202 body gains additive fields (generationId, commandId, candidateId, idempotencyKey, replay).

Tooling & process: validate_local.sh defaults to a fast pre-push path (explicitly not CI-equivalent) and adds --full CI-like guards, full pytest, UI budget, logging, and fail-closed behavior when the code-health base SHA cannot be resolved; check_code_health_pr_base.sh is stricter. Docs add MUSIC_SUBMISSION.md, AGENT_QA_POLICY.md, updated LOCAL_VALIDATION.md / SLICE_QUEUE.md, and PR checklist items for validation scope and current HEAD CI/review evidence.

Reviewed by Cursor Bugbot for commit b1c2510. Configure here.

…ubmit

Accept Story music jobs only after persisting command/task/candidate IDs.
The same idempotency key and spec replay; a reused key with a different
spec conflicts. Retry/new-version mint a new attempt. Existing 202 jobs
and the sync compatibility route stay.
Default remains a labeled fast pre-push mode. --full runs the CI-safe
guards, pytest suite, ratchet, UI checks, budget and simulated E2E.
A missing ratchet base or a failed analyzer is now a hard failure.
Technical review belongs to agents; a human merge click is operational.
Document the intended required checks and the admin steps that still need
authorization. Do not change remote protection in this PR.
bash -lc could replace nvm/Pinokio PATH after require_cmd npm. Use a
non-login helper so the same npm binary is invoked.
An AND list under set -e does not abort. Run build and budget as
separate steps so a failed build never reaches E2E or a CI-equivalent PASS.
Omit a key to start a new attempt. Retry needs its own key so a lost
response can replay. Replay without a MiniMax checkpoint starts the worker
again. Default candidate count matches the jobs endpoint.
Insert the in-memory job under the existing lock; if the reserved
job_id is already live, return that snapshot instead of starting a
second provider thread.
feat: reserve music generation IDs before inference with idempotent submit
fix(ci): make local validation explicit and fail closed
docs: record agent QA policy without applying GitHub rulesets
Bring published #143#145 (music submission, local validation, agent QA
policy) onto the integration branch. Keep the development base section
and the AGENT_QA_POLICY merge wording in SLICE_QUEUE.md.
@IAnMove

IAnMove commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

cursor review

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

PR Review — Loreframe Studio

Risk: low
Scope: 13 file(s); +1273/-78; GitHub workflows, backend services, docs, repo scripts / CI

Automated review from scripts/analyze_pr.py. This is a heuristic pass (no LLM) so humans still own the merge decision.

Findings

  • No heuristic issues. Still run the CI checklist below.

Changed files

  • added: app/services/music_submission.py, docs/development/AGENT_QA_POLICY.md, docs/development/MUSIC_SUBMISSION.md, tests/test_music_submission.py, tests/test_validate_local_wrapper.py
  • modified: .github/pull_request_template.md, app/_launch_runtime.py, docs/development/LOCAL_VALIDATION.md, docs/development/SLICE_QUEUE.md, fase4.md, scripts/check_code_health_pr_base.sh, scripts/validate_local.sh, tests/test_minimax_music_jobs.py

CONTRIBUTING checklist

  • python scripts/verify_clean_repo.py
  • python -m compileall -q app/services app/launch.py scripts
  • cd ui && npm run build if the UI changed
  • No weights, CivitAI sidecars, or generated guides
  • Stays local-first (no required accounts / telemetry)

Posted by the repo PR review workflow. Re-runs on each push to the PR.

@IAnMove
IAnMove merged commit 7699663 into development Sep 5, 2026
4 of 5 checks passed
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Code health

Quality score: 49.9/100

Higher is better. The score is a trend dashboard; the independent ratchet below remains the CI gate.

Component Weight Current Change
Cyclomatic health 45% 52.5 +0.0
File concentration 25% 55.5 +0.1
Oversized-file debt 20% 30.8 +0.1
Modularity 10% 62.3 +0.0

Change vs PR base: +0.1 points.

Metric Value
Production LOC 245,474
Production files 541
Test LOC 72,628
Functions measured 15,344
Functions complexity ≥ 15 793
Maximum complexity 667

Markdown, JSON catalogs and tests are out of this table. Only app/ runtime + ui/src TS/JS count.

Most complex functions

Complexity Where
667 app/wgp.py:7164 generate_video
374 ui/src/stores/useStore.ts:4024 Async method 'startGeneration'
355 app/_launch_runtime.py:23508 _run_generation
308 app/wgp.py:12281 generate_video_tab
271 ui/src/components/Sidebar/SceneAnimatorPanel.tsx:474 Function 'SceneAnimatorPanel'
266 ui/src/stores/useStore.ts:8566 Async method 'loadSettingsFromOutput'
258 app/services/director/planners/short_film.py:3433 ShortFilmPlanner._plan_story_driven
248 app/services/director_pipeline.py:13735 _run_video_generation
245 app/services/director_pipeline.py:7860 _run_pipeline
243 ui/src/features/agent/agentActions.ts:1128 Function 'parseAction'
226 app/services/director_pipeline.py:6689 update_comic_preview
225 ui/src/features/agent/agentActions.ts:2796 Async function 'executeAgentActions'

Trend vs baseline

Metric Δ
Production LOC +373
Test LOC +554
Functions ≥ 15 +1
Maximum complexity +0

Warnings

  • production LOC increased by +373
  • functions at complexity >= 15 increased by +1
  • hotspot app/_launch_runtime.py increased by +39 lines

Ratchet passed.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Publish wipes reserved task identity
    • El upsert ahora fusiona metadata reservada y conserva workflow/title, y el publish de MiniMax proyecta generation_id, candidate_id, command_id e idempotency_key desde el job.

Create PR

Or push these changes by commenting:

@cursor push 90761cd2ee
Preview (90761cd2ee)
diff --git a/app/_launch_runtime.py b/app/_launch_runtime.py
--- a/app/_launch_runtime.py
+++ b/app/_launch_runtime.py
@@ -35994,6 +35994,22 @@
         # cancellation/completion won. Canonical resume explicitly transitions
         # the registry before the next active adapter snapshot arrives.
         return existing
+    existing_metadata = existing.get("metadata") if isinstance(existing.get("metadata"), dict) else {}
+    incoming_metadata = fields.get("metadata") if isinstance(fields.get("metadata"), dict) else None
+    if incoming_metadata is not None:
+        merged_metadata = dict(existing_metadata)
+        for key, value in incoming_metadata.items():
+            if value is not None:
+                merged_metadata[key] = value
+        fields["metadata"] = merged_metadata
+        # Reservation owns attempt identity. Adapter snapshots must not
+        # replace workflow/title after submit_music_generation wrote them.
+        if any(
+            existing_metadata.get(key)
+            for key in ("generation_id", "candidate_id", "command_id", "idempotency_key")
+        ):
+            fields.pop("workflow", None)
+            fields.pop("title", None)
     mutable = {
         key: value for key, value in fields.items()
         if key not in {"id", "created_at"}
@@ -36328,9 +36344,16 @@
             "actor": provenance.get("actor") or "unknown",
             "tool": provenance.get("tool") or adapter,
             "capability": provenance.get("capability"),
-            "command_id": command.get("command_id"),
+            "command_id": (
+                command.get("command_id")
+                or record.get("commandId")
+                or record.get("command_id")
+            ),
             "workflow_id": command.get("workflow_id"),
             "run_id": command.get("run_id"),
+            "generation_id": record.get("generationId") or record.get("generation_id"),
+            "candidate_id": record.get("candidateId") or record.get("candidate_id"),
+            "idempotency_key": record.get("idempotencyKey") or record.get("idempotency_key"),
         },
     )
 

diff --git a/tests/test_task_adapter_helpers.py b/tests/test_task_adapter_helpers.py
--- a/tests/test_task_adapter_helpers.py
+++ b/tests/test_task_adapter_helpers.py
@@ -256,3 +256,81 @@
 
     assert captured["id"] == "task-model3d-backend-id"
     assert captured["root_id"] == "task-series-root"
+
+
+def test_generic_adapter_publishes_reserved_music_identity():
+    publish, captured = _load_publisher("_publish_generic_legacy_task")
+
+    publish({
+        "jobId": "minimax-music-abc123def456",
+        "taskId": "task-minimax-music-abc123def456",
+        "workspace": "default",
+        "status": "queued",
+        "generationId": "gen-1",
+        "commandId": "cmd-1",
+        "candidateId": "song-1",
+        "idempotencyKey": "idem-1",
+    }, "minimax-music")
+
+    assert captured["metadata"]["generation_id"] == "gen-1"
+    assert captured["metadata"]["command_id"] == "cmd-1"
+    assert captured["metadata"]["candidate_id"] == "song-1"
+    assert captured["metadata"]["idempotency_key"] == "idem-1"
+
+
+def test_upsert_keeps_reserved_task_identity():
+    existing = {
+        "id": "task-minimax-music-abc",
+        "status": "queued",
+        "workflow": "generate_story_song",
+        "title": "Story song",
+        "metadata": {
+            "generation_id": "gen-1",
+            "candidate_id": "song-1",
+            "command_id": "cmd-1",
+            "idempotency_key": "idem-1",
+        },
+    }
+    updated = {}
+
+    class Registry:
+        def get(self, _task_id):
+            return existing
+
+        def update(self, task_id, **fields):
+            updated.update(fields)
+            return {**existing, **{key: value for key, value in fields.items()
+                                   if key not in {"force", "event_type", "event_exclude_fields"}}}
+
+    namespace = {
+        "_task_registry": lambda _workspace: Registry(),
+    }
+    node = _function("_upsert_canonical_task")
+    exec(compile(ast.Module(body=[node], type_ignores=[]), str(LAUNCH_PATH), "exec"), namespace)
+
+    result = namespace["_upsert_canonical_task"](
+        "default",
+        "task-minimax-music-abc",
+        workflow="minimax-music",
+        title="MiniMax Music",
+        status="queued",
+        metadata={
+            "adapter": "minimax-music",
+            "actor": "unknown",
+            "tool": "minimax-music",
+            "capability": None,
+            "command_id": None,
+            "workflow_id": None,
+            "run_id": None,
+        },
+    )
+
+    assert "workflow" not in updated
+    assert "title" not in updated
+    assert result["workflow"] == "generate_story_song"
+    assert result["title"] == "Story song"
+    assert result["metadata"]["generation_id"] == "gen-1"
+    assert result["metadata"]["candidate_id"] == "song-1"
+    assert result["metadata"]["command_id"] == "cmd-1"
+    assert result["metadata"]["idempotency_key"] == "idem-1"
+    assert result["metadata"]["adapter"] == "minimax-music"

You can send follow-ups to the cloud agent here.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b1c2510. Configure here.

Comment thread app/_launch_runtime.py
# missed the checkpoint). Do not start a second worker.
public = _public_minimax_music_job(existing_live)
public["replay"] = True
return public

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Publish wipes reserved task identity

Medium Severity

submit_music_generation stores generation_id, candidate_id, command_id, and idempotency_key on the TaskRegistry row, then start_story_music_candidates_job publishes the MiniMax job to the same taskId. That publish upserts a replacement metadata object from job provenance (usually empty) and overwrites workflow/title, so the reserved identity does not survive on the canonical task.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b1c2510. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant