@animastor/orchestration 0.1.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/LICENSE +21 -0
- package/README.md +75 -0
- package/package.json +51 -0
- package/src/host/host-bindings.js +138 -0
- package/src/index.js +99 -0
- package/src/orchestration/index.js +40 -0
- package/src/orchestration/orchestrator.js +630 -0
- package/src/orchestration/runtime-result-consumer.js +54 -0
- package/src/orchestration/scene-callbacks.js +542 -0
- package/src/orchestration/scene-orchestrator.js +601 -0
- package/src/orchestration/scene-restoration.js +129 -0
- package/src/orchestration/scene-utils.js +36 -0
- package/src/runtime/active-scenes-index.js +164 -0
- package/src/runtime/audio-fsm-port.js +187 -0
- package/src/runtime/circuit-breaker.js +588 -0
- package/src/runtime/counter-reconciliation.js +308 -0
- package/src/runtime/dispatch-engine.js +1768 -0
- package/src/runtime/failure-taxonomy.js +125 -0
- package/src/runtime/hub-cancel-port.js +122 -0
- package/src/runtime/layer-config-port.js +136 -0
- package/src/runtime/lease-manager.js +607 -0
- package/src/runtime/persistence-port.js +162 -0
- package/src/runtime/placeholder-audio-port.js +117 -0
- package/src/runtime/progress-events-port.js +121 -0
- package/src/runtime/reconciliation-engine.js +2405 -0
- package/src/runtime/retry-budget-manager.js +229 -0
- package/src/runtime/runtime-metrics.js +427 -0
- package/src/runtime/runtime-result-emitter.js +123 -0
- package/src/runtime/runtime-scheduler.js +629 -0
- package/src/runtime/scene-data-port.js +114 -0
- package/src/runtime/scene-window.js +827 -0
- package/src/runtime/video-fsm-port.js +189 -0
- package/src/runtime/worker-health.js +270 -0
- package/test/counter-reconciliation.test.js +164 -0
- package/test/mocks/redis-mock.js +221 -0
- package/test/test-bindings.js +60 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// ======================================================
|
|
2
|
+
// Runtime Result Emitter — Phase 5 seam (producer side)
|
|
3
|
+
// ======================================================
|
|
4
|
+
// Runtime reports job/dispatch execution outcomes to orchestration
|
|
5
|
+
// through an INJECTED consumer callback — never by requiring
|
|
6
|
+
// orchestration modules directly (Phase 5 main architectural contract:
|
|
7
|
+
// runtime → result contract/callback, NOT runtime → orchestration).
|
|
8
|
+
//
|
|
9
|
+
// The consumer is registered once at the composition root
|
|
10
|
+
// (backend.cjs): runtimeResultEmitter.setConsumer(consumerFn).
|
|
11
|
+
// Default (unset consumer): emit is a no-op returning { delivered: false }.
|
|
12
|
+
//
|
|
13
|
+
// Delivery guarantees:
|
|
14
|
+
// - at most one consumer (last registration wins);
|
|
15
|
+
// - emitRuntimeResult NEVER throws — consumer errors are logged and
|
|
16
|
+
// swallowed so dispatch finalization semantics cannot change;
|
|
17
|
+
// - notification-only: the consumer receives the frozen result object
|
|
18
|
+
// and must not throw on slow paths (it is awaited if it returns a
|
|
19
|
+
// promise, but rejections are contained).
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
// §32.30: the Runtime Result Contract leaf moved to @animastor/contracts.
|
|
24
|
+
const { createRuntimeResult, statusFromOutcome } = require('@animastor/contracts').runtimeResult;
|
|
25
|
+
|
|
26
|
+
const logPrefix = '[RUNTIME-RESULT]';
|
|
27
|
+
|
|
28
|
+
function log(msg) {
|
|
29
|
+
console.log(`${logPrefix} ${msg}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function warn(msg) {
|
|
33
|
+
console.warn(`${logPrefix} ⚠️ ${msg}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let consumer = null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register the runtime result consumer (orchestration adapter).
|
|
40
|
+
* Injected from the composition root; runtime never imports it.
|
|
41
|
+
*
|
|
42
|
+
* @param {(result: object) => (void|Promise<void>)} fn
|
|
43
|
+
*/
|
|
44
|
+
function setConsumer(fn) {
|
|
45
|
+
if (typeof fn !== 'function') {
|
|
46
|
+
throw new Error('runtime-result-emitter: consumer must be a function');
|
|
47
|
+
}
|
|
48
|
+
consumer = fn;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Test/multi-instance helper: drop the registered consumer. */
|
|
52
|
+
function resetConsumer() {
|
|
53
|
+
consumer = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Normalize a dispatch finalization into the Runtime Result contract
|
|
58
|
+
* and deliver it to the registered consumer (if any).
|
|
59
|
+
*
|
|
60
|
+
* Never throws: contract-construction or consumer errors are logged and
|
|
61
|
+
* contained so callers (finalizeDispatch) keep their exact semantics.
|
|
62
|
+
*
|
|
63
|
+
* @param {Object} params
|
|
64
|
+
* @param {string} params.bookId
|
|
65
|
+
* @param {string} params.chapterId
|
|
66
|
+
* @param {string} params.sceneId
|
|
67
|
+
* @param {'success'|'failure'|'cancelled'} params.outcome dispatch-internal outcome
|
|
68
|
+
* @param {string|null} [params.jobId] Job Protocol v2 job id, when known
|
|
69
|
+
* @param {string|null} [params.dispatchId]
|
|
70
|
+
* @param {string|null} [params.stage]
|
|
71
|
+
* @param {string|null} [params.error] reason string
|
|
72
|
+
* @param {Object} [params.metadata]
|
|
73
|
+
* @returns {Promise<{delivered: boolean, reason?: string}>}
|
|
74
|
+
*/
|
|
75
|
+
async function emitRuntimeResult(params = {}) {
|
|
76
|
+
const { bookId, chapterId, sceneId, outcome, jobId = null, dispatchId = null, stage = null, error = null, metadata = {} } = params;
|
|
77
|
+
|
|
78
|
+
const status = statusFromOutcome(outcome);
|
|
79
|
+
if (!status) {
|
|
80
|
+
warn(`emit skipped: unknown outcome '${outcome}' (${bookId}/${chapterId}/${sceneId})`);
|
|
81
|
+
return { delivered: false, reason: 'unknown_outcome' };
|
|
82
|
+
}
|
|
83
|
+
if (!consumer) {
|
|
84
|
+
// Composition root has not registered a consumer — legal state
|
|
85
|
+
// (tests, tooling). Not an error.
|
|
86
|
+
return { delivered: false, reason: 'no_consumer' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
result = createRuntimeResult({
|
|
92
|
+
bookId,
|
|
93
|
+
chapterId,
|
|
94
|
+
sceneId,
|
|
95
|
+
jobId,
|
|
96
|
+
dispatchId,
|
|
97
|
+
stage,
|
|
98
|
+
status,
|
|
99
|
+
error,
|
|
100
|
+
metadata,
|
|
101
|
+
});
|
|
102
|
+
} catch (contractErr) {
|
|
103
|
+
warn(`emit skipped: invalid result for ${bookId}/${chapterId}/${sceneId}: ${contractErr.message}`);
|
|
104
|
+
return { delivered: false, reason: 'invalid_result' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await consumer(result);
|
|
109
|
+
} catch (consumerErr) {
|
|
110
|
+
// Contained: a failing consumer must never break dispatch finalization.
|
|
111
|
+
warn(`consumer failed for ${bookId}/${chapterId}/${sceneId} ${status}: ${consumerErr.message}`);
|
|
112
|
+
return { delivered: false, reason: 'consumer_error' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
log(`${status}: ${bookId}/${chapterId}/${sceneId}${stage ? ':' + stage : ''} dispatch=${dispatchId ? dispatchId.slice(0, 24) + '...' : 'n/a'}`);
|
|
116
|
+
return { delivered: true };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
setConsumer,
|
|
121
|
+
resetConsumer,
|
|
122
|
+
emitRuntimeResult,
|
|
123
|
+
};
|