@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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/package.json +51 -0
  4. package/src/host/host-bindings.js +138 -0
  5. package/src/index.js +99 -0
  6. package/src/orchestration/index.js +40 -0
  7. package/src/orchestration/orchestrator.js +630 -0
  8. package/src/orchestration/runtime-result-consumer.js +54 -0
  9. package/src/orchestration/scene-callbacks.js +542 -0
  10. package/src/orchestration/scene-orchestrator.js +601 -0
  11. package/src/orchestration/scene-restoration.js +129 -0
  12. package/src/orchestration/scene-utils.js +36 -0
  13. package/src/runtime/active-scenes-index.js +164 -0
  14. package/src/runtime/audio-fsm-port.js +187 -0
  15. package/src/runtime/circuit-breaker.js +588 -0
  16. package/src/runtime/counter-reconciliation.js +308 -0
  17. package/src/runtime/dispatch-engine.js +1768 -0
  18. package/src/runtime/failure-taxonomy.js +125 -0
  19. package/src/runtime/hub-cancel-port.js +122 -0
  20. package/src/runtime/layer-config-port.js +136 -0
  21. package/src/runtime/lease-manager.js +607 -0
  22. package/src/runtime/persistence-port.js +162 -0
  23. package/src/runtime/placeholder-audio-port.js +117 -0
  24. package/src/runtime/progress-events-port.js +121 -0
  25. package/src/runtime/reconciliation-engine.js +2405 -0
  26. package/src/runtime/retry-budget-manager.js +229 -0
  27. package/src/runtime/runtime-metrics.js +427 -0
  28. package/src/runtime/runtime-result-emitter.js +123 -0
  29. package/src/runtime/runtime-scheduler.js +629 -0
  30. package/src/runtime/scene-data-port.js +114 -0
  31. package/src/runtime/scene-window.js +827 -0
  32. package/src/runtime/video-fsm-port.js +189 -0
  33. package/src/runtime/worker-health.js +270 -0
  34. package/test/counter-reconciliation.test.js +164 -0
  35. package/test/mocks/redis-mock.js +221 -0
  36. 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
+ };