@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Animastor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # @animastor/orchestration
2
+
3
+ Generation orchestration and runtime contour of the Animastor backend,
4
+ physically extracted from the host (`backend/src/orchestration/**` +
5
+ `backend/src/runtime/**` — §32.30; recon:
6
+ `docs/architecture/generation-module-extraction-reconnaissance.md` §32.29).
7
+
8
+ The package owns:
9
+
10
+ - the orchestration facade (stage executor + FSM-safe scene writers)
11
+ - the dispatch engine and its lease/quota/retry managers
12
+ - the reconciliation engine and counter reconciliation
13
+ - scene window/scheduler/worker-health
14
+ - the runtime-result emitter/consumer
15
+ - the eight O-2…O-10 port contracts (Persistence, SceneData,
16
+ PlaceholderAudio, ProgressEvents, AudioFsm, VideoFsm, HubCancel,
17
+ LayerConfig)
18
+
19
+ ## Dependency direction
20
+
21
+ ```
22
+ backend host → @animastor/orchestration → ports / host-bindings / contracts
23
+ ```
24
+
25
+ The package reaches the host (runtime config, Redis-domain state, media
26
+ facades, S-5 seam registry, artifact root, PW-2 workspace resolver)
27
+ **only** through the composition-root host-bindings seam — no
28
+ `backend/src` require, no storage/book/routes/services, no Redis, no PG,
29
+ no Express inside.
30
+
31
+ Runtime dependencies: `@animastor/contracts`, `@animastor/generation`,
32
+ `music-metadata`.
33
+
34
+ ## Public API
35
+
36
+ ```js
37
+ const orchestration = require('@animastor/orchestration');
38
+ ```
39
+
40
+ | Export | Kind | Description |
41
+ |---|---|---|
42
+ | orchestration facade | lazy | stage executor + FSM scene writers (begin/plan/dispatch/complete/fail/rollback) |
43
+ | `createRuntimeResultConsumer` | eager | Phase-5 result-reporting consumer |
44
+ | `ports` | eager | `{ persistence, sceneData, placeholderAudio, progressEvents, audioFsm, videoFsm, hubCancel, layerConfig }` |
45
+ | `bindHostModules` | eager | composition-root binding surface |
46
+ | `hostBinding` | eager | host-binding accessor |
47
+ | `runtime` | lazy namespace | `{ scheduler, activeScenes, reconciliation, dispatch, leaseManager, counterReconciliation, metrics, workerHealth, sceneWindow, failureTaxonomy, runtimeResultEmitter }` |
48
+
49
+ Facade exports are **lazy getters** — the FSM-writer re-exports resolve
50
+ the `stateOps` host binding on access. `Object.defineProperties` on the
51
+ root prevents eager evaluation at `require()` time.
52
+
53
+ Deep imports (`@animastor/orchestration/runtime/…` etc.) are NOT part of
54
+ the public surface — the exports map exposes only `'.'`.
55
+
56
+ ## Host responsibilities (composition root)
57
+
58
+ 1. Call `bindHostModules(…)` to wire config/state/media/seams/artifact
59
+ root/PW-2 resolver before accessing any lazy export.
60
+ 2. Register `createRuntimeResultConsumer` via
61
+ `runtimeResultEmitter.setConsumer(…)`.
62
+
63
+ Without host binding, lazy getters throw on first access. This is
64
+ intentional — the package is a pure domain contour until the composition
65
+ root wires it.
66
+
67
+ ## Tests
68
+
69
+ ```bash
70
+ npm test # package-own suite (standalone, no backend required)
71
+ ```
72
+
73
+ Backend-side guards: `backend/tests/architecture/physical-move-gate.test.js`
74
+ (PM-G suite) ensures the moved files are gone from `backend/src/runtime`
75
+ and the barrel is not consumed.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@animastor/orchestration",
3
+ "version": "0.1.1",
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
+ "keywords": [
6
+ "animastor",
7
+ "orchestration",
8
+ "runtime",
9
+ "dispatch",
10
+ "reconciliation",
11
+ "generation"
12
+ ],
13
+ "main": "src/index.js",
14
+ "exports": {
15
+ ".": "./src/index.js"
16
+ },
17
+ "files": [
18
+ "src/",
19
+ "test/",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "test": "mocha --exit --require test/test-bindings.js test/*.test.js"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "license": "MIT",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Animastor/animastor.git",
36
+ "directory": "packages/animastor-orchestration"
37
+ },
38
+ "homepage": "https://github.com/Animastor/animastor/tree/main/packages/animastor-orchestration#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/Animastor/animastor/issues"
41
+ },
42
+ "dependencies": {
43
+ "@animastor/contracts": "^0.1.0",
44
+ "@animastor/generation": "^0.1.0",
45
+ "music-metadata": "^11.12.3"
46
+ },
47
+ "devDependencies": {
48
+ "chai": "^6.2.2",
49
+ "mocha": "^11.7.5"
50
+ }
51
+ }
@@ -0,0 +1,138 @@
1
+ // ======================================================
2
+ // HOST BINDINGS — the package↔host seam (S-6 convention)
3
+ // ======================================================
4
+ // The moved runtime/orchestration files reach the HOST-OWNED modules they
5
+ // were allowed to touch (§32.29 host-stays allowlist) ONLY through this
6
+ // surface. It is the same zero-require contract + fail-fast call-time
7
+ // resolver + one set/wired/reset shape as the ports in ../runtime/*-port.js,
8
+ // but for the host composition instead of one adapter: the composition root
9
+ // (backend/src/backend.cjs) binds the real host modules once at startup and
10
+ // the moved files resolve them lazily at call time.
11
+ //
12
+ // What crosses here (exactly the measured §32.29 host-stays, nothing more):
13
+ // config — backend/src/config/runtime-config (runtime slices)
14
+ // state — backend/src/state (Redis-domain state layer facade)
15
+ // stateOps — backend/src/state/scene-state-ops (pure FSM writers)
16
+ // journal — backend/src/state/event-journal (append-only sink)
17
+ // media — { audio, image, video } host media facades
18
+ // genScope — backend/src/services/gen-scope (single-consumer edge)
19
+ // seams — backend/src/runtime/orchestration-seams (S-5 registry)
20
+ // artifactRoot — the artifact output root (former process.env.OUTPUT_DIR
21
+ // read — the §32.29 step-4a closure of the O-G9 leak)
22
+ // resolveWorkspaceForBook — the PW-2 workspace routing resolver (former
23
+ // scene-window → gpu-dispatcher lazy call — the §32.29
24
+ // step-4b closure; optional-load semantics preserved by
25
+ // the injected wrapper host-side)
26
+ //
27
+ // Deliberately NOT reachable through here: storage/**, book/**, routes/**,
28
+ // PG clients, Redis clients, Express, gpu-dispatcher internals. The host
29
+ // binds adapters; the package never requires backend/src.
30
+ //
31
+ // Docs: docs/architecture/generation-module-extraction-reconnaissance.md
32
+ // §32.29 (gate) · §32.30 (physical extraction landed)
33
+
34
+ const BINDING_NAMES = [
35
+ 'config',
36
+ 'state',
37
+ 'stateOps',
38
+ 'journal',
39
+ 'media',
40
+ 'genScope',
41
+ 'seams',
42
+ 'artifactRoot',
43
+ 'resolveWorkspaceForBook',
44
+ ];
45
+
46
+ const bindings = new Map();
47
+
48
+ /**
49
+ * Wire the host-side implementations. Accepts a partial merge (tests may
50
+ * rebind individual bindings); unknown binding names are rejected — the
51
+ * surface cannot grow silently.
52
+ *
53
+ * RESOLVER SEMANTICS (§32.30): every binding is a ZERO-ARG FUNCTION that
54
+ * returns the host module/value. It is invoked at RESOLUTION time (i.e. when
55
+ * a moved file actually touches the host), not at binding time — the same
56
+ * deferral the former lazy host-module require call sites had, so host
57
+ * modules can be stubbed via require.cache after binding and stay visible
58
+ * to the moved code (test-binding parity).
59
+ */
60
+ function bindHostModules(impls = {}) {
61
+ for (const [name, value] of Object.entries(impls)) {
62
+ if (!BINDING_NAMES.includes(name)) {
63
+ throw new Error(`host-bindings: unknown binding '${name}' (allowed: ${BINDING_NAMES.join(', ')})`);
64
+ }
65
+ if (typeof value !== 'function') {
66
+ throw new Error(`host-bindings: binding '${name}' must be a resolver function (zero-arg), got ${typeof value}`);
67
+ }
68
+ bindings.set(name, value);
69
+ }
70
+ }
71
+
72
+ /** Test hygiene: drop all bindings (mocha afterEach hooks). */
73
+ function clearHostBindings() {
74
+ bindings.clear();
75
+ }
76
+
77
+ /**
78
+ * Resolve a binding at CALL time (invokes the resolver). Fail-fast with a
79
+ * wiring instruction — a missing binding must never degrade into a silent
80
+ * no-op.
81
+ */
82
+ function hostBinding(name) {
83
+ const value = bindings.get(name);
84
+ if (typeof value !== 'function') {
85
+ throw new Error(
86
+ `host-bindings: '${name}' is not wired — the composition root must call ` +
87
+ `bindHostModules({ ${name}, ... }) before orchestration runs (§32.30)`
88
+ );
89
+ }
90
+ return value();
91
+ }
92
+
93
+ function isHostBindingWired(name) {
94
+ return bindings.has(name);
95
+ }
96
+
97
+ /**
98
+ * Lazy host-module accessor: returns a proxy that resolves the binding (and
99
+ * optional sub-path) at PROPERTY-ACCESS time. Lets a moved file keep its
100
+ * original identifier usage verbatim (`state.getAssetStates(...)`,
101
+ * `audio.segments.buildSegments(...)`) while the underlying host module is
102
+ * only resolved when actually used — same deferral the former lazy
103
+ * `require(...)` call sites had, but through the binding seam.
104
+ */
105
+ function lazyHostBinding(name) {
106
+ const [root, ...rest] = name.split('.');
107
+ return new Proxy({}, {
108
+ get(_, prop) {
109
+ let target = hostBinding(root);
110
+ for (const key of rest) target = target[key];
111
+ return target[prop];
112
+ },
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Direct call-time host access for non-member callables (the PW-2 resolver,
118
+ * the artifact root): `hostCall('resolveWorkspaceForBook')(bookId)`.
119
+ */
120
+ function hostCall(name) {
121
+ return hostBinding(name);
122
+ }
123
+
124
+ /** All names the composition root is expected to wire. */
125
+ function requiredHostBindings() {
126
+ return [...BINDING_NAMES];
127
+ }
128
+
129
+ module.exports = {
130
+ BINDING_NAMES,
131
+ bindHostModules,
132
+ clearHostBindings,
133
+ hostBinding,
134
+ hostCall,
135
+ isHostBindingWired,
136
+ lazyHostBinding,
137
+ requiredHostBindings,
138
+ };
package/src/index.js ADDED
@@ -0,0 +1,99 @@
1
+ // ======================================================
2
+ // @animastor/orchestration — package root (public API)
3
+ // ======================================================
4
+ // The orchestration/runtime contour physically extracted from the backend
5
+ // host (§32.30; gate: §32.29 — READY FOR PHYSICAL MOVE). This root exports
6
+ // EXACTLY the measured host consumption surface — nothing "just in case":
7
+ //
8
+ // 1. The orchestration facade (former backend/src/orchestration/index.js,
9
+ // moved verbatim) — the stage executor + FSM-safe scene writers the
10
+ // host and the S-5 seam registry consume.
11
+ // 2. createRuntimeResultConsumer — the Phase-5 result-reporting consumer
12
+ // the composition root registers via runtimeResultEmitter.setConsumer.
13
+ // 3. The eight O-2..O-10 port CONTRACT modules (tier-owned, moved with
14
+ // the contour) — the host storage adapters bind these at startup.
15
+ // 4. bindHostModules / hostBinding — the composition-root binding surface
16
+ // that wires the host-owned modules the moved files consume
17
+ // (config/state/media/seams/artifact root/PW-2 resolver).
18
+ //
19
+ // §32.30: the facade exports are LAZY getters (the FSM-writer re-exports
20
+ // resolve the stateOps host binding on access), so the root itself builds
21
+ // its export object with defineProperties — a literal spread would fire
22
+ // those getters at require time, before the composition root binds host
23
+ // modules. The surface (names + facade-wins precedence) is unchanged.
24
+ //
25
+ // Everything else in the package (dispatch-engine, reconciliation-engine,
26
+ // scene-window, scheduler, lease/circuit/retry managers, worker-health,
27
+ // metrics) is INTERNAL: no host module and no host test may deep-import it.
28
+ //
29
+ // Dependency direction after the move (guarded by backend PM-G suite):
30
+ // backend host → @animastor/orchestration → ports/host-bindings/contracts
31
+ // The package requires @animastor/generation (root specifier only) and
32
+ // @animastor/contracts; it never requires backend/src, storage, book,
33
+ // routes, services, Redis or PG clients.
34
+ //
35
+ // Docs: docs/architecture/generation-module-extraction-reconnaissance.md
36
+ // §32.29 (frozen API) · §32.30 (physical extraction landed)
37
+
38
+ const orchestrationFacade = require('./orchestration');
39
+ const { createRuntimeResultConsumer } = require('./orchestration/runtime-result-consumer');
40
+ const hostBindings = require('./host/host-bindings');
41
+
42
+ // 1 — orchestration facade (lazy accessors over the moved facade modules)
43
+ const FACADE_KEYS = Object.keys(orchestrationFacade);
44
+ const LAZY_FACADE_PROPS = Object.fromEntries(FACADE_KEYS.map((key) => [key, {
45
+ enumerable: true,
46
+ get() { return orchestrationFacade[key]; },
47
+ }]));
48
+
49
+ // 2 + 3 + 4 — eager values (no binding resolution at require time)
50
+ const EAGER_EXPORTS = {
51
+ createRuntimeResultConsumer,
52
+ ports: {
53
+ persistence: require('./runtime/persistence-port'),
54
+ sceneData: require('./runtime/scene-data-port'),
55
+ placeholderAudio: require('./runtime/placeholder-audio-port'),
56
+ progressEvents: require('./runtime/progress-events-port'),
57
+ audioFsm: require('./runtime/audio-fsm-port'),
58
+ videoFsm: require('./runtime/video-fsm-port'),
59
+ hubCancel: require('./runtime/hub-cancel-port'),
60
+ layerConfig: require('./runtime/layer-config-port'),
61
+ },
62
+ bindHostModules: hostBindings.bindHostModules,
63
+ clearHostBindings: hostBindings.clearHostBindings,
64
+ hostBinding: hostBindings.hostBinding,
65
+ isHostBindingWired: hostBindings.isHostBindingWired,
66
+ lazyHostBinding: hostBindings.lazyHostBinding,
67
+ requiredHostBindings: hostBindings.requiredHostBindings,
68
+ BINDING_NAMES: hostBindings.BINDING_NAMES,
69
+ };
70
+
71
+ // 5 — the host-facing runtime namespace: the measured routes/services/helpers
72
+ // consumption surface (the former backend/src/runtime/index.js barrel minus
73
+ // its host-stay members loop/gpuDispatcher/retentionManager). Lazy getters —
74
+ // no eager module loads at require time.
75
+ const RUNTIME_NS = {
76
+ scheduler: './runtime/runtime-scheduler',
77
+ activeScenes: './runtime/active-scenes-index',
78
+ reconciliation: './runtime/reconciliation-engine',
79
+ dispatch: './runtime/dispatch-engine',
80
+ leaseManager: './runtime/lease-manager',
81
+ counterReconciliation: './runtime/counter-reconciliation',
82
+ metrics: './runtime/runtime-metrics',
83
+ workerHealth: './runtime/worker-health',
84
+ sceneWindow: './runtime/scene-window',
85
+ failureTaxonomy: './runtime/failure-taxonomy',
86
+ runtimeResultEmitter: './runtime/runtime-result-emitter',
87
+ };
88
+ const LAZY_RUNTIME_PROPS = Object.fromEntries(Object.entries(RUNTIME_NS).map(([key, target]) => [key, {
89
+ enumerable: true,
90
+ get() { return require(target); },
91
+ }]));
92
+
93
+ const base = { ...EAGER_EXPORTS };
94
+ Object.defineProperties(base, LAZY_FACADE_PROPS);
95
+ Object.defineProperties(base, {
96
+ runtime: { enumerable: true, value: Object.defineProperties({}, LAZY_RUNTIME_PROPS) },
97
+ });
98
+
99
+ module.exports = base;
@@ -0,0 +1,40 @@
1
+ // ======================================================
2
+ // Orchestration Module - v2.0.0
3
+ // ======================================================
4
+
5
+ // Existing contract: orchestration.dispatchStage / handle*Completed
6
+ // are consumed by dispatch-engine.js (require('../orchestration')).
7
+ // We preserve those exports and add the new Orchestrator facade alongside,
8
+ // so callers can migrate to orchestration.orchestrator.* incrementally
9
+ // without breaking the existing surface (Шаг 0, docs-claude/03_Orchestrator.md).
10
+ //
11
+ // Orchestrator facade functions (markDirtyScene, planScene, beginStage, etc.)
12
+ // are also spread to the top level so callers that receive 'orchestrator' via
13
+ // deps (reconciliation-engine, scene-asset-registry, etc.) can access them
14
+ // directly as deps.orchestrator.markDirtyScene() without the .orchestrator
15
+ // indirection. When names overlap (completeStage, failStage), the orchestrator
16
+ // facade version wins (it's the newer canonical implementation).
17
+ //
18
+ // §32.30: the FSM-writer re-exports in orchestrator.js are LAZY getters
19
+ // (they resolve the stateOps host binding on access). A literal object
20
+ // spread would fire them at require time — before the composition root
21
+ // binds host modules. Enumerate + defineProperties keeps every accessor
22
+ // lazy while preserving the exact surface and the facade-wins precedence.
23
+ const sceneOrchestrator = require('./scene-orchestrator');
24
+ const orchestratorFacade = require('./orchestrator');
25
+
26
+ const FACADE_KEYS = [
27
+ ...new Set([...Object.keys(sceneOrchestrator), ...Object.keys(orchestratorFacade)]),
28
+ ];
29
+
30
+ const LAZY_FACADE_PROPS = Object.fromEntries(FACADE_KEYS.map((key) => [key, {
31
+ enumerable: true,
32
+ get() {
33
+ return key in orchestratorFacade ? orchestratorFacade[key] : sceneOrchestrator[key];
34
+ },
35
+ }]));
36
+
37
+ module.exports = Object.defineProperties(
38
+ { orchestrator: orchestratorFacade },
39
+ LAZY_FACADE_PROPS,
40
+ );