@animastor/orchestration 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 (2026-11-01) — результат GPU обрабатывается один раз
|
|
4
|
+
|
|
5
|
+
Основание: `reports/2026-11-01/gpu-audio-image-run-analysis.md`, P1 — каждый
|
|
6
|
+
результат обрабатывался дважды (HTTP-push hub → backend, затем Phase A
|
|
7
|
+
reconcile подбирал оставшийся `animastor:result:*` мимо
|
|
8
|
+
`animastor:result-processed:*`). Описание: animastor-docs
|
|
9
|
+
`architecture/GPU_HUB_CONTRACT.md` §«Result ACK».
|
|
10
|
+
|
|
11
|
+
### Changed (поведение)
|
|
12
|
+
|
|
13
|
+
- `reconciliation-engine.recoverResultKeys` (Phase A): перед
|
|
14
|
+
`handleTaskResult` читает маркер `animastor:result-processed:{job}:{build}`.
|
|
15
|
+
Значение `done:<dispatch_id>` того же dispatch, что в result-ключе, —
|
|
16
|
+
ключ удаляется молча (без лога), handler не вызывается.
|
|
17
|
+
Маркер `'1'` (в работе / старый формат), маркер другого dispatch
|
|
18
|
+
(перегенерация audio/video в пределах TTL) или его отсутствие — как
|
|
19
|
+
раньше, через fenced handler.
|
|
20
|
+
- Терминальный отказ (`no_active_dispatch` и др.) для дубля доставленного
|
|
21
|
+
результата (маркер есть) — уровень info; для сироты (маркера нет) — warn.
|
|
22
|
+
- `orchestrator.resetScenes` вместе с `iu-progress` удаляет
|
|
23
|
+
`animastor:iu-progress-ids:*` (множество IU×fence, хост task-handler).
|
|
24
|
+
|
|
25
|
+
Fence, dedup-ключ маршрута и command-gate (Stage 155) не меняются.
|
|
26
|
+
|
|
3
27
|
## 0.3.0 (2026-10-31) — генерация только по явной команде
|
|
4
28
|
|
|
5
29
|
Правило владельца: GPU-генерация запускается только кнопками «аудио» /
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animastor/orchestration",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Animastor Orchestration — the generation orchestration/runtime contour of the Animastor backend, physically extracted from the host (§32.30): the unified generation lifecycle owner (orchestrator facade, stage executor, scene callbacks/restoration), the dispatch engine and its lease/quota/retry managers, the reconciliation engine and counter reconciliation, the scene window/scheduler/worker-health, the runtime-result emitter/consumer, and the eight O-2..O-10 port contracts (Persistence, SceneData, PlaceholderAudio, ProgressEvents, AudioFsm, VideoFsm, HubCancel, LayerConfig). The package reaches the host (runtime config slices, Redis-domain state layer, media facades, the S-5 seam registry, the artifact root, the PW-2 workspace resolver) ONLY through the composition-root host-bindings seam — no backend/src require, no storage/book/routes/services, no Redis, no PG, no Express inside.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"animastor",
|
|
@@ -575,6 +575,8 @@ async function resetScenes(redis, bookId, buildId, scenes, layerCfg, options = {
|
|
|
575
575
|
|
|
576
576
|
const progKey = `animastor:iu-progress:${bookId}:${ds.chapter_id}:${ds.scene_id}:image`;
|
|
577
577
|
try { await redis.del(progKey); } catch (_) {}
|
|
578
|
+
// SYNC: backend task-handler — the IU×fence set behind the counter.
|
|
579
|
+
try { await redis.del(`animastor:iu-progress-ids:${bookId}:${ds.chapter_id}:${ds.scene_id}:image`); } catch (_) {}
|
|
578
580
|
|
|
579
581
|
try {
|
|
580
582
|
let cursor = '0';
|
|
@@ -63,6 +63,25 @@ function error(msg) {
|
|
|
63
63
|
console.error(`${logPrefix} ❌ ${msg}`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// SYNC: backend/src/helpers/queue-idempotency.cjs — resultDedupKey /
|
|
67
|
+
// readResultDedup (marker `'1'` = in flight, `done:<dispatch_id>` = settled).
|
|
68
|
+
// Read-only here: the result route is the only writer.
|
|
69
|
+
const RESULT_DEDUP_DONE_PREFIX = 'done:';
|
|
70
|
+
async function readResultDedup(redis, jobId, buildId) {
|
|
71
|
+
let raw = null;
|
|
72
|
+
try {
|
|
73
|
+
raw = await redis.get(`animastor:result-processed:${jobId}:${buildId}`);
|
|
74
|
+
} catch (_) {
|
|
75
|
+
return { state: 'unknown', dispatchId: null };
|
|
76
|
+
}
|
|
77
|
+
if (raw == null || raw === false) return { state: 'none', dispatchId: null };
|
|
78
|
+
const v = String(raw);
|
|
79
|
+
if (v.startsWith(RESULT_DEDUP_DONE_PREFIX)) {
|
|
80
|
+
return { state: 'settled', dispatchId: v.slice(RESULT_DEDUP_DONE_PREFIX.length) || null };
|
|
81
|
+
}
|
|
82
|
+
return { state: 'in_flight', dispatchId: null };
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
// ======================================================
|
|
67
86
|
// RECONCILIATION REPORT
|
|
68
87
|
// ======================================================
|
|
@@ -1947,11 +1966,34 @@ async function recoverResultKeys(redis, deps, scope) {
|
|
|
1947
1966
|
warn(`Skipping legacy result without build/dispatch identity: ${job_id}`);
|
|
1948
1967
|
continue;
|
|
1949
1968
|
}
|
|
1969
|
+
|
|
1970
|
+
// P1 (2026-11-01): the hub's fallback copy of a delivery the
|
|
1971
|
+
// result route already SETTLED is a duplicate — drop it
|
|
1972
|
+
// silently, never replay it (the replay re-ran the ready chain
|
|
1973
|
+
// and logged false no_active_dispatch warnings). Only a marker
|
|
1974
|
+
// carrying THIS dispatch counts: an in-flight / legacy '1'
|
|
1975
|
+
// marker or one from an older dispatch of the same job_id
|
|
1976
|
+
// (audio/video regeneration within the TTL) is no proof — such
|
|
1977
|
+
// a result goes through the fenced handler exactly as before.
|
|
1978
|
+
const dedup = await readResultDedup(redis, job_id, buildId);
|
|
1979
|
+
if (dedup.state === 'settled' && dedup.dispatchId === String(dispatchId)) {
|
|
1980
|
+
await redis.del(key);
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1950
1983
|
try {
|
|
1951
1984
|
await taskHandler.handleTaskResult(job_id, result_base64, buildId, dispatchId);
|
|
1952
1985
|
} catch (rejectErr) {
|
|
1953
1986
|
if (rejectErr && TERMINAL_REJECTIONS.has(rejectErr.code)) {
|
|
1954
|
-
|
|
1987
|
+
// A replay of a delivery the route has seen (a marker
|
|
1988
|
+
// exists) is a duplicate: info. No marker (an orphan
|
|
1989
|
+
// that never reached the route) or an unreadable
|
|
1990
|
+
// marker: warn, as before.
|
|
1991
|
+
const msg = `Result ${job_id} terminally rejected (${rejectErr.code}) — dropping result key, no infinite retry`;
|
|
1992
|
+
if (dedup.state === 'in_flight' || dedup.state === 'settled') {
|
|
1993
|
+
log(`${msg} (duplicate of a delivered result)`);
|
|
1994
|
+
} else {
|
|
1995
|
+
warn(msg);
|
|
1996
|
+
}
|
|
1955
1997
|
await redis.del(key);
|
|
1956
1998
|
} else {
|
|
1957
1999
|
throw rejectErr;
|