break the 22-module seriesAutopilot/creativeDirector import cycle with a project-start sink - #6094
Merged
Conversation
… a project-start sink (#5920) `pipeline/episodeVideo.js` importing `creativeDirector/completionHook.js` was the only static edge running pipeline -> creativeDirector, and the CD tool registry starts a Series Autopilot in the other direction — together they closed a 22-module strongly-connected component, the largest in `server/services`. In a static ESM cycle whichever member evaluates first sees `undefined` for the others' bindings, so any top-level const derived from an import in the ring is a TDZ crash waiting on a module-ordering change. New leaf `creativeDirector/projectStartSink.js` inverts that one edge: the pipeline depends on the sink, the completion hook registers the concrete starter on it at module evaluation, and `server/index.js` imports the hook for that side effect at boot. An unregistered request throws rather than silently dropping the start, because a CD project that never advances is otherwise invisible. Also applied the barrel rule the issue names: `planAdvance.js` and `creative/tools/pipeline.js` now import the modules that DECLARE the autopilot symbols instead of re-entering the `seriesAutopilot.js` barrel from outside the package. Deferring an import with `await import()` would have hidden the cycle from the guard without removing the hazard, so it was not used. The component is gone, not shrunk — `findImportCycleComponents` reports nothing in that half of the graph, and the #5920 baseline entry is deleted from KNOWN_CYCLIC_COMPONENTS in serviceImportCycles.test.js.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #5920.
The 22-module
seriesAutopilot/creativeDirectorstrongly-connected component is gone, not shrunk —findImportCycleComponentsnow reports nothing across that half of the graph, and the#5920entry is deleted fromKNOWN_CYCLIC_COMPONENTSinserviceImportCycles.test.js.pipeline/*→creativeDirector/*:pipeline/episodeVideo.jsimportingcompletionHook.js#startCreativeDirectorProject. Everything else in the ring flowed the other way (CD's tool registry starts a Series Autopilot). Removing that single edge dissolves the whole component.creativeDirector/projectStartSink.jsinverts it: the pipeline depends on the sink, and the completion hook registers the concrete starter on it at module evaluation.server/index.jsimports the hook for that side effect at boot. An unregistered request throws rather than silently dropping the start — a CD project that never advances is otherwise invisible.creativeDirector/planAdvance.jsandcreative/tools/pipeline.jsnow import the modules that DECLARE the autopilot symbols (seriesAutopilot/state.js,seriesAutopilot/session.js,seriesAutopilot/orchestrator.js) instead of re-entering theseriesAutopilot.jsbarrel from outside the package.await import()would have hidden the cycle from the guard without removing the boot-order hazard, so it was not used.The barrel's
__testinglazy getters stay (their comment now records that the cycle they defended against is gone), and the cold-import guard is unchanged.Test plan
services/creativeDirector/projectStartSink.test.js— unregistered request throws and names the project, registered starter is delegated to, idempotent re-registration vs. a conflicting registrant, non-function registrant refused.completionHook.test.jsgains an arming assertion: importing the hook registers its ownstartCreativeDirectorProjecton the sink (identity-checked via the idempotent-re-register path). Without it, a dropped side effect would silently stall every pipeline episode.episodeVideo.test.jsnow asserts the fire-and-forget start actually reaches the sink with the new project id.serviceImportCycles.test.jsis the measurement: its "no baseline entry for a cycle that is already fixed" assertion fails if the component is still live, and its no-new-cycles assertion fails if the change closed a different ring.cd server && npm test: 1910 files passed, 38520 tests passed, 1 file / 24 tests skipped.