diff --git a/composer.json b/composer.json
index 397fdf0..e358ab2 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,8 @@
     "symfony-cmf/routing": "1.1.*@alpha",
     "easyrdf/easyrdf": "0.8.*@beta",
     "phpunit/phpunit": "3.7.*",
-    "zendframework/zend-feed": "2.2.*"
+    "zendframework/zend-feed": "2.2.*",
+    "sdboyer/gliph": "0.1.*"
   },
   "autoload": {
     "psr-0": {
diff --git a/composer.lock b/composer.lock
index decd030..3080532 100644
--- a/composer.lock
+++ b/composer.lock
@@ -3,7 +3,7 @@
         "This file locks the dependencies of your project to a known state",
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
     ],
-    "hash": "204b3755db988998bcc618e71b2f235c",
+    "hash": "38591fb50ec8bc7c3f2d2a9ea542246f",
     "packages": [
         {
             "name": "doctrine/annotations",
@@ -1112,6 +1112,50 @@
             "time": "2012-12-21 11:40:51"
         },
         {
+            "name": "sdboyer/gliph",
+            "version": "0.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sdboyer/gliph.git",
+                "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sdboyer/gliph/zipball/c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a",
+                "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Gliph": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Sam Boyer",
+                    "email": "tech@samboyer.org"
+                }
+            ],
+            "description": "A graph library for PHP.",
+            "homepage": "http://github.com/sdboyer/gliph",
+            "keywords": [
+                "gliph",
+                "graph",
+                "library",
+                "php",
+                "spl"
+            ],
+            "time": "2013-09-14 04:16:01"
+        },
+        {
             "name": "symfony-cmf/routing",
             "version": "1.1.0-beta1",
             "target-dir": "Symfony/Cmf/Component/Routing",
diff --git a/core/core.services.yml b/core/core.services.yml
index 7d56112..8b2182b 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -641,3 +641,6 @@ services:
     class: Drupal\Core\Asset\JsCollectionGrouper
   asset.js.dumper:
     class: Drupal\Core\Asset\AssetDumper
+  asset.library_repository:
+    class: Drupal\Core\Asset\AssetLibraryRepository
+    arguments: ['@module_handler']
diff --git a/core/includes/common.inc b/core/includes/common.inc
index be97724..e113493 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1629,6 +1629,9 @@ function drupal_add_css($data = NULL, $options = NULL) {
   // Create an array of CSS files for each media type first, since each type needs to be served
   // to the browser differently.
   if (isset($data)) {
+    $options['type'] = isset($options['type']) ? $options['type'] : 'file';
+    drupal_collect_assets($data, $options, 'css');
+
     $options += array(
       'type' => 'file',
       'group' => CSS_AGGREGATE_DEFAULT,
@@ -1680,6 +1683,29 @@ function drupal_add_css($data = NULL, $options = NULL) {
   return $css;
 }
 
+function drupal_collect_assets($data, $options, $type = '') {
+  $bag = &drupal_static('global_asset_bag', FALSE);
+  $collector = &drupal_static('global_asset_collector', FALSE);
+
+  $bag = ($bag instanceof \Drupal\Core\Asset\Bag\AssetBag) ? $bag : new \Drupal\Core\Asset\Bag\AssetBag();
+  if (!$collector instanceof \Drupal\Core\Asset\Factory\AssetCollector) {
+    $collector = new \Drupal\Core\Asset\Factory\AssetCollector();
+    $collector->setBag($bag);
+  }
+
+  if ($data instanceof \Drupal\Core\Asset\AssetInterface) {
+    $collector->add($data);
+    return;
+  }
+
+  if ($type == 'js-setting') {
+    // TODO handle js settings
+    return;
+  }
+
+  $collector->create($type, $options['type'], $data, $options);
+}
+
 /**
  * Returns a themed representation of all stylesheets to attach to the page.
  *
@@ -2199,6 +2225,11 @@ function drupal_html_id($id) {
 function drupal_add_js($data = NULL, $options = NULL) {
   $javascript = &drupal_static(__FUNCTION__, array());
 
+  if (isset($data)) {
+    $options['type'] = isset($options['type']) ? $options['type'] : 'file';
+    drupal_collect_assets($data, $options, $options['type'] == 'setting' ? 'js-setting' : 'js');
+  }
+
   // Construct the options, taking the defaults into consideration.
   if (isset($options)) {
     if (!is_array($options)) {
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/AssetAggregateInterface.php b/core/lib/Drupal/Core/Asset/Aggregate/AssetAggregateInterface.php
new file mode 100644
index 0000000..8d37137
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/AssetAggregateInterface.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Aggregate\AssetAggregateInterface.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+use Assetic\Asset\AssetCollectionInterface as AsseticAssetCollectionInterface;
+use Drupal\Core\Asset\Collection\AssetCollectionBasicInterface;
+use Drupal\Core\Asset\AssetInterface;
+
+/**
+ * Describes an aggregate asset: a logical asset composed of other assets.
+ *
+ * This interface extends to Assetic's AssetCollectionInterface, but is intended
+ * for a more narrow purpose than it. Whereas Assetic uses AssetCollections as
+ * both a container for assets (a collection in the conventional sense) *and* as
+ * a renderable unit, implementors of AssetAggregateInterface are considered to
+ * be solely the latter.
+ *
+ * This approach was taken because these two are discrete responsibilities, and
+ * while the conflation of the two is not problematic for most contexts in which
+ * Assetic is used, Drupal's complex asset declaration and rendering environment
+ * necessitates a clear differentiation between the two.
+ *
+ * In the end, aggregates are exactly what the interface composition looks like:
+ * a real, functioning asset, and a basic container for other assets.
+ *
+ * @see \Assetic\Asset\AssetCollectionInterface
+ * @see \Drupal\Core\Asset\Collection\AssetCollectionInterface
+ */
+interface AssetAggregateInterface extends AssetInterface, AssetCollectionBasicInterface, AsseticAssetCollectionInterface {
+
+  /**
+   * Replaces an existing asset in the aggregate with a new one.
+   *
+   * This maintains ordering of the assets within the aggregate; the new asset
+   * will occupy the same position as the old asset.
+   *
+   * @param AssetInterface|string $needle
+   *   Either an AssetInterface instance, or the string id of an asset.
+   * @param AssetInterface $replacement
+   *   The new asset to swap into place.
+   * @param bool $graceful
+   *   Whether failure should return FALSE or throw an exception.
+   *
+   * @return bool
+   *
+   * @throws \OutOfBoundsException
+   */
+  public function replace($needle, AssetInterface $replacement, $graceful = FALSE);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php
new file mode 100644
index 0000000..3b39501
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/BaseAggregateAsset.php
@@ -0,0 +1,443 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\BaseAggregateAsset.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+
+use Assetic\Filter\FilterCollection;
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\AsseticAdapterAsset;
+use Drupal\Core\Asset\AssetInterface;
+use Assetic\Asset\AssetInterface as AsseticAssetInterface;
+use Drupal\Core\Asset\Aggregate\AssetAggregateInterface;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Base class for representing aggregate assets.
+ *
+ */
+abstract class BaseAggregateAsset extends AsseticAdapterAsset implements \IteratorAggregate, AssetInterface, AssetAggregateInterface {
+
+  /**
+   * @var \Drupal\Core\Asset\Metadata\AssetMetadataBag
+   */
+  protected $metadata;
+
+  /**
+   * Container for all assets attached to this object.
+   *
+   * @var \SplObjectStorage
+   */
+  protected $assetStorage;
+
+  /**
+   * @var \SplObjectStorage
+   */
+  protected $nestedStorage;
+
+  /**
+   * A string identifier for this aggregate.
+   *
+   * This is calculated based on
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Maintains a map, keyed by id, of all assets.
+   *
+   * This map is also the canonical source for ordering information.
+   *
+   * @var array
+   */
+  protected $assetIdMap = array();
+
+  protected $filters;
+  protected $sourceRoot;
+  protected $targetPath;
+  protected $content;
+
+  /**
+   * @param AssetMetadataBag $metadata
+   *   The metadata bag for this aggregate.
+   * @param array $assets
+   *   Assets to add to this aggregate.
+   * @param array $filters
+   *   Filters to apply to this aggregate.
+   * @param array $sourceRoot TODO get rid of me
+   */
+  public function __construct(AssetMetadataBag $metadata, $assets = array(), $filters = array(), $sourceRoot = array()) {
+    $this->metadata = $metadata;
+    $this->sourceRoot = $sourceRoot;
+    $this->assetStorage = new \SplObjectStorage();
+    $this->nestedStorage = new \SplObjectStorage();
+
+    $this->filters = new FilterCollection($filters);
+
+    foreach ($assets as $asset) {
+      $this->add($asset);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    if (empty($this->id)) {
+      $this->calculateId();
+    }
+
+    return $this->id;
+  }
+
+  /**
+   * Calculates and stores an id for this aggregate from the contained assets.
+   *
+   * @return void
+   */
+  protected function calculateId() {
+    $id = '';
+    foreach ($this->assetStorage as $asset) {
+      // Preserve a little id stability by not composing id from aggregates
+      if (!$asset instanceof AssetAggregateInterface) {
+        $id .= $asset->id();
+      }
+    }
+    // TODO come up with something stabler/more serialization friendly than object hash
+    $this->id = hash('sha256', $id ?: spl_object_hash($this));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadata() {
+    // TODO should this immutable? doable if we further granulate the interfaces
+    return $this->metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(AsseticAssetInterface $asset) {
+    if (!$asset instanceof AssetInterface) {
+      throw new UnsupportedAsseticBehaviorException('Vanilla Assetic asset provided; Drupal aggregates require Drupal-flavored assets.');
+    }
+    $this->ensureCorrectType($asset);
+
+    $this->assetStorage->attach($asset);
+    $this->assetIdMap[$asset->id()] = $asset;
+
+    if ($asset instanceof AssetAggregateInterface) {
+      $this->nestedStorage->attach($asset);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function contains(AssetInterface $asset) {
+    if ($this->assetStorage->contains($asset)) {
+      return TRUE;
+    }
+
+    foreach ($this->nestedStorage as $aggregate) {
+      if ($aggregate->contains($asset)) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function reindex() {
+    $map = array();
+    foreach ($this->assetIdMap as $asset) {
+      $map[$asset->id()] = $asset;
+    }
+    $this->assetIdMap = $map;
+
+    // Recalculate the id, too.
+    $this->calculateId();
+
+    // Recursively reindex contained aggregates.
+    foreach ($this->nestedStorage as $aggregate) {
+      $aggregate->reindex();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getById($id, $graceful = TRUE) {
+    if (isset($this->assetIdMap[$id])) {
+      return $this->assetIdMap[$id];
+    }
+    else {
+      // Recursively search for the id
+      foreach ($this->nestedStorage as $aggregate) {
+        if ($found = $aggregate->getById($id)) {
+          return $found;
+        }
+      }
+    }
+
+    if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException(sprintf('This aggregate does not contain an asset with id %s.', $id));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function remove($needle, $graceful = TRUE) {
+    if (is_string($needle)) {
+      if (!$needle = $this->getById($needle, $graceful)) {
+        return FALSE;
+      }
+    }
+
+    return $this->removeLeaf($needle, $graceful);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeLeaf(AsseticAssetInterface $needle, $graceful = FALSE) {
+    if (!$needle instanceof AssetInterface) {
+      throw new UnsupportedAsseticBehaviorException('Vanilla Assetic asset provided; Drupal aggregates require Drupal-flavored assets.');
+    }
+    $this->ensureCorrectType($needle);
+
+    foreach ($this->assetIdMap as $id => $asset) {
+      if ($asset === $needle) {
+        unset($this->assetStorage[$asset], $this->assetIdMap[$id], $this->nestedStorage[$asset]);
+
+        return TRUE;
+      }
+
+      if ($asset instanceof AssetAggregateInterface && $asset->removeLeaf($needle, $graceful)) {
+        return TRUE;
+      }
+    }
+
+    if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException('Asset not found.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function replace($needle, AssetInterface $replacement, $graceful = TRUE) {
+    if (is_string($needle)) {
+      if (!$needle = $this->getById($needle, $graceful)) {
+        return FALSE;
+      }
+    }
+
+    return $this->replaceLeaf($needle, $replacement, $graceful);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function replaceLeaf(AsseticAssetInterface $needle, AsseticAssetInterface $replacement, $graceful = FALSE) {
+    if (!($needle instanceof AssetInterface && $replacement instanceof AssetInterface)) {
+      throw new UnsupportedAsseticBehaviorException('Vanilla Assetic asset(s) provided; Drupal aggregates require Drupal-flavored assets.');
+    }
+    $this->ensureCorrectType($needle);
+    $this->ensureCorrectType($replacement);
+
+    foreach ($this->assetIdMap as $id => $asset) {
+      if ($asset === $needle) {
+        unset($this->assetStorage[$asset], $this->nestedStorage[$asset]);
+
+        array_splice($this->assetIdMap, $i, 1, array($replacement->id() => $replacement));
+        $this->assetStorage->attach($replacement);
+        if ($replacement instanceof AssetAggregateInterface) {
+          $this->nestedStorage->attach($replacement);
+        }
+
+        return TRUE;
+      }
+
+      if ($asset instanceof AssetAggregateInterface && $asset->replaceLeaf($needle, $replacement, $graceful)) {
+        return TRUE;
+      }
+    }
+
+    if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException('Asset not found.');
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Aggregate assets are inherently eligible for preprocessing, so this is
+   * always true.
+   */
+  public function isPreprocessable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function all() {
+    return $this->assetIdMap;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function ensureFilter(FilterInterface $filter) {
+    $this->filters->ensure($filter);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFilters() {
+    return $this->filters->all();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearFilters() {
+    $this->filters->clear();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(FilterInterface $additionalFilter = NULL) {
+    // loop through leaves and load each asset
+    $parts = array();
+    foreach ($this as $asset) {
+      $asset->load($additionalFilter);
+      $parts[] = $asset->getContent();
+    }
+
+    $this->content = implode("\n", $parts);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dump(FilterInterface $additionalFilter = NULL) {
+    // loop through leaves and dump each asset
+    $parts = array();
+    foreach ($this as $asset) {
+      $parts[] = $asset->dump($additionalFilter);
+    }
+
+    return implode("\n", $parts);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContent() {
+    return $this->content;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContent($content) {
+    $this->content = $content;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSourceRoot() {
+    return $this->sourceRoot;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSourcePath() {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetPath() {
+    return $this->targetPath;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTargetPath($targetPath) {
+    $this->targetPath = $targetPath;
+  }
+
+  /**
+   * Returns the highest last-modified value of all contained assets.
+   *
+   * @return integer|null
+   *   A UNIX timestamp
+   */
+  public function getLastModified() {
+    if (!count($this->assetStorage)) {
+      return;
+    }
+
+    $mtime = 0;
+    foreach ($this->assetStorage as $asset) {
+      $assetMtime = $asset->getLastModified();
+      if ($assetMtime > $mtime) {
+        $mtime = $assetMtime;
+      }
+    }
+
+    return $mtime;
+  }
+
+  /**
+   * TODO Assetic uses their iterator to clone, then populate values and return here; is that a good model for us?
+   */
+  public function getIterator() {
+    // TODO this is totally junk
+    return new \ArrayIterator($this->assetIdMap);
+  }
+
+  /**
+   * Indicates whether this collection contains any assets.
+   *
+   * @return bool
+   *   TRUE if contained assets are present, FALSE otherwise.
+   */
+  public function isEmpty() {
+    return $this->assetStorage->count() === 0;
+  }
+
+
+  /**
+   * Ensures that the asset is of the correct subtype (e.g., css vs. js).
+   *
+   * @param AssetInterface $asset
+   *
+   * @throws \Drupal\Core\Asset\Exception\AssetTypeMismatchException
+   */
+  abstract protected function ensureCorrectType(AssetInterface $asset);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php
new file mode 100644
index 0000000..c400772
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/CssAggregateAsset.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssAggregateAsset.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Exception\AssetTypeMismatchException;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Core\Asset\StylesheetAssetInterface;
+
+/**
+ * A CSS asset that is an aggregate of multiple other CSS assets.
+ */
+class CssAggregateAsset extends BaseAggregateAsset implements StylesheetAssetInterface {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \Drupal\Core\Asset\Exception\AssetTypeMismatchException
+   */
+  public function __construct(AssetMetadataBag $metadata, $assets = array(), $filters = array(), $sourceRoot = array()) {
+    if (!$metadata instanceof CssMetadataBag) {
+      throw new AssetTypeMismatchException('CSS aggregates require CSS metadata bags.');
+    }
+
+    parent::__construct($metadata, $assets, $filters, $sourceRoot); // TODO: Change the autogenerated stub
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function ensureCorrectType(AssetInterface $asset) {
+    if (!($asset instanceof StylesheetAssetInterface || $asset instanceof self)) {
+      throw new AssetTypeMismatchException('CSS aggregates can only work with CSS assets.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php b/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php
new file mode 100644
index 0000000..554e655
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Aggregate/JsAggregateAsset.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Aggregate\JsAggregateAsset.
+ */
+
+namespace Drupal\Core\Asset\Aggregate;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\JavascriptAssetInterface;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+
+/**
+ * A Javascript asset that aggregates together multiple other Javascript assets.
+ */
+class JsAggregateAsset extends BaseAggregateAsset implements JavascriptAssetInterface {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \Drupal\Core\Asset\Exception\AssetTypeMismatchException
+   */
+  public function __construct(AssetMetadataBag $metadata, $assets = array(), $filters = array(), $sourceRoot = array()) {
+    if (!$metadata instanceof JsMetadataBag) {
+      throw new AssetTypeMismatchException('CSS aggregates require CSS metadata bags.');
+    }
+
+    parent::__construct($metadata, $assets, $filters, $sourceRoot); // TODO: Change the autogenerated stub
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function ensureCorrectType(AssetInterface $asset) {
+   if (!($asset instanceof StylesheetAssetInterface || $asset instanceof self)) {
+      throw new AssetTypeMismatchException('CSS aggregates can only work with CSS assets.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/AssetGraph.php b/core/lib/Drupal/Core/Asset/AssetGraph.php
new file mode 100644
index 0000000..f261a0a
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetGraph.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetGraph.
+ */
+
+namespace Drupal\Core\Asset;
+use Gliph\Exception\InvalidVertexTypeException;
+use Gliph\Graph\DirectedAdjacencyGraph;
+
+/**
+ * An extension of the DirectedAdjacencyGraph concept designed specifically for
+ * Drupal's asset management use case.
+ *
+ * Drupal allows for two types of sequencing declarations:
+ *
+ *   - Dependencies, which guarantee that dependent asset must be present and
+ *     that it must precede the asset declaring it as a dependency.
+ *   - Ordering, which can guarantee that asset A will be either preceded or
+ *     succeeded by asset B, but does NOT guarantee that B will be present.
+ *
+ * The impact of a dependency can be calculated myopically (without knowledge of
+ * the full set), as a dependency inherently guarantees the presence of the
+ * other vertex in the set.
+ *
+ * For ordering, however, the full set must be inspected to determine whether or
+ * not the other asset is already present. If it is, a directed edge can be
+ * declared; if it is not.
+ *
+ * This class eases the process of determining what to do with ordering
+ * declarations by implementing a more sophisticated addVertex() mechanism,
+ * which incrementally sets up (and triggers) watches for any ordering
+ * declarations that have not yet been realized.
+ *
+ * TODO add stuff that tracks data about unresolved successors/predecessors
+ */
+class AssetGraph extends DirectedAdjacencyGraph {
+
+  protected $before = array();
+  protected $after = array();
+  protected $verticesById = array();
+
+  public function addVertex($vertex) {
+    if (!$vertex instanceof AssetInterface) {
+      throw new InvalidVertexTypeException('AssetGraph requires vertices to implement AssetInterface.');
+    }
+
+    if (!$this->hasVertex($vertex)) {
+      $this->vertices[$vertex] = new \SplObjectStorage();
+      $this->verticesById[$vertex->id()] = $vertex;
+      $this->processNewVertex($vertex);
+    }
+  }
+
+  /**
+   * Processes all sequencing information for a given vertex.
+   *
+   * @param AssetInterface $vertex
+   */
+  protected function processNewVertex(AssetInterface $vertex) {
+    $id = $vertex->id();
+    // First, check if anything has a watch out for this vertex.
+    if (isset($this->before[$id])) {
+      foreach ($this->before[$id] as $predecessor) {
+        $this->addDirectedEdge($predecessor, $vertex);
+      }
+      unset($this->before[$id]);
+    }
+
+    if (isset($this->after[$id])) {
+      foreach ($this->after[$id] as $successor) {
+        $this->addDirectedEdge($vertex, $successor);
+      }
+      unset($this->after[$id]);
+    }
+
+    // Add watches for this vertex, if it implements the interface.
+    if ($vertex instanceof AssetOrderingInterface) {
+      // TODO this logic assumes collections enforce uniqueness - ensure that's the case.
+      foreach ($vertex->getPredecessors() as $predecessor) {
+        // Normalize to id string.
+        $predecessor = is_string($predecessor) ? $predecessor : $predecessor->id();
+
+        if (isset($this->verticesById[$predecessor])) {
+          $this->addDirectedEdge($this->verticesById[$predecessor], $vertex);
+        }
+        else {
+          if (!isset($this->before[$predecessor])) {
+            $this->before[$predecessor] = array();
+          }
+          $this->before[$predecessor][] = $vertex;
+        }
+      }
+
+      foreach ($vertex->getSuccessors() as $successor) {
+        // Normalize to id string.
+        $successor = is_string($successor) ? $successor : $successor->id();
+
+        if (isset($this->verticesById[$successor])) {
+          $this->addDirectedEdge($vertex, $this->verticesById[$successor]);
+        }
+        else {
+          if (!isset($this->before[$successor])) {
+            $this->after[$successor] = array();
+          }
+          $this->after[$successor][] = $vertex;
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transpose() {
+    // TODO super-important - have to rewrite transpose so that it correctly inverts edge direction
+    return parent::transpose();
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetInterface.php b/core/lib/Drupal/Core/Asset/AssetInterface.php
new file mode 100644
index 0000000..bbfb1ee
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetInterface.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetInterface.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Asset\AssetInterface as AsseticAssetInterface;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Represents a CSS or Javascript asset.
+ *
+ * This interface extends the AssetInterface provided by Assetic to facilitate
+ * different behaviors by individual assets.
+ */
+interface AssetInterface extends AsseticAssetInterface {
+
+  /**
+   * Returns the metadata bag for this asset.
+   *
+   * @return AssetMetadataBag
+   */
+  public function getMetadata();
+
+  /**
+   * Indicates whether or not this asset is eligible for preprocessing.
+   *
+   * Assets that are marked as not preprocessable will always be passed directly
+   * to the browser without aggregation or minification. Assets that are marked
+   * as eligible for preprocessing will be included in any broader aggregation
+   * logic that has been configured.
+   *
+   * @return bool
+   */
+  public function isPreprocessable();
+
+  /**
+   * Returns a unique string identifier that uniquely identifies this asset.
+   *
+   * Note that this id IS subject to change, if certain internal object
+   * properties change.
+   *
+   * // TODO if it's subject to change, 'id' is misleading
+   *
+   * @return string
+   *   The asset id.
+   */
+  public function id();
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetLibraryRepository.php b/core/lib/Drupal/Core/Asset/AssetLibraryRepository.php
new file mode 100644
index 0000000..0790871
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetLibraryRepository.php
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetLibraryRepository.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\Bag\AssetLibrary;
+use Drupal\Core\Asset\Factory\AssetLibraryCollector;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+
+/**
+ * @todo Transform this into a 'lazy' library - serialize & load as needed.
+ */
+class AssetLibraryRepository implements \IteratorAggregate {
+
+  protected $libraries;
+
+  protected $flattened;
+
+  /**
+   * Indicates whether or not the repository has initialized its collection.
+   *
+   * @todo this is very dirty; shift responsibility for populating to something external
+   *
+   * @var bool
+   */
+  protected $initialized = FALSE;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  function __construct($module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+
+  protected function initialize() {
+    if ($this->initialized) {
+      return;
+    }
+    $this->initialized = TRUE;
+
+    $library_collector = new AssetLibraryCollector($this);
+    foreach ($this->moduleHandler->getImplementations('library_info') as $module) {
+      $library_collector->setModule($module);
+      $libraries = call_user_func("{$module}_library_info");
+      foreach ($libraries as $name => $info) {
+        // Normalize - apparently hook_library_info is allowed to be sloppy.
+        $info += array('dependencies' => array(), 'js' => array(), 'css' => array());
+
+        // @todo This works sorta sanely because of the array_intersect_key() hack in AssetLibrary::construct()
+        $asset_collector = $library_collector->buildLibrary($name, $info);
+        foreach (array('js', 'css') as $type) {
+          if (!empty($info[$type])) {
+            foreach ($info[$type] as $data => $options) {
+              if (is_scalar($options)) {
+                $data = $options;
+                $options = array();
+              }
+              // @todo good enough for now to assume these are all file assets
+              $asset_collector->create($type, 'file', $data, $options);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Gets a library by composite key.
+   *
+   * @param string $module
+   *   The module owner that declared the library.
+   *
+   * @param string $name
+   *   The library name.
+   *
+   * @return \Drupal\Core\Asset\Bag\AssetLibrary
+   *   The requested library.
+   *
+   * @throws \InvalidArgumentException If there is no library by that name
+   */
+  public function get($module, $name) {
+    $this->initialize();
+    if (!isset($this->libraries[$module][$name])) {
+      throw new \InvalidArgumentException(sprintf('There is no library identified by "%s/%s" in the manager.', $module, $name));
+    }
+
+    return $this->libraries[$module][$name];
+  }
+
+  /**
+   * Checks if the current library manager has a certain library.
+   *
+   * @param string $module
+   *   The module owner that declared the library.
+   *
+   * @param string $name
+   *   The library name.
+   *
+   * @return bool
+   *   True if the library has been set, false if not
+   */
+  public function has($module, $name) {
+    $this->initialize();
+    return isset($this->libraries[$module][$name]);
+  }
+
+  public function add($module, $name, AssetLibrary $library) {
+    // TODO add validation - alphanum + underscore only
+    if (!isset($this->libraries[$module])) {
+      $this->libraries[$module] = array();
+    }
+
+    $this->libraries[$module][$name] = $library;
+    $this->flattened = NULL;
+  }
+
+  /**
+   * Retrieves the asset objects on which the passed asset depends.
+   *
+   * @param AssetOrderingInterface $asset
+   *   The asset whose dependencies should be retrieved.
+   *
+   * @return array
+   *   An array of AssetInterface objects if any dependencies were found;
+   *   otherwise, an empty array.
+   */
+  public function resolveDependencies(AssetOrderingInterface $asset) {
+    $dependencies = array();
+
+    if ($asset->hasDependencies()) {
+      foreach ($asset->getDependencyInfo() as $info) {
+        try {
+          $dependencies[] = $this->get($info[0], $info[1]);
+        }
+        // TODO should we really try/catch at a potentially high traffic place like this?
+        catch (\InvalidArgumentException $e) {
+          // TODO we're relying on a method that's not in AssetOrderingInterface...
+          watchdog('assets', 'Asset @asset declared a dependency on nonexistent library @module/@name', array($asset->getSourcePath(), $info[0], $info[1]), WATCHDOG_ERROR);
+        }
+      }
+    }
+
+    return $dependencies;
+  }
+
+  /**
+   * Returns an array of library names.
+   *
+   * @return array An array of library names
+   */
+  public function getNames() {
+    $this->initialize();
+    return array_keys($this->libraries);
+  }
+
+  /**
+   * Clears all libraries.
+   */
+  public function clear() {
+    $this->initialize();
+    $this->libraries = array();
+    $this->flattened = NULL;
+  }
+
+  public function getIterator() {
+    $this->initialize();
+    if (is_null($this->flattened)) {
+      $this->flattened = array();
+      foreach ($this->libraries as $module => $set) {
+        foreach ($set as $name => $library) {
+          $this->flattened["$module:$name"] = $library;
+        }
+      }
+    }
+
+    return new \ArrayIterator($this->flattened);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetOrderingInterface.php b/core/lib/Drupal/Core/Asset/AssetOrderingInterface.php
new file mode 100644
index 0000000..7fe5835
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetOrderingInterface.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Core\Asset\AssetOrderingInterface.
+ */
+
+namespace Drupal\Core\Asset;
+
+/**
+ * Describes an asset or asset-like object that can declare dependencies.
+ */
+interface AssetOrderingInterface {
+
+  /**
+   * Indicates whether this asset has one or more library dependencies.
+   *
+   * @return boolean
+   */
+  public function hasDependencies();
+
+  /**
+   * Retrieve this asset's dependencies.
+   *
+   * @return mixed
+   *   An array of dependencies if they exist,
+   */
+  public function getDependencyInfo();
+
+  /**
+   * Add a dependency on a library for this asset.
+   *
+   * @param string $module
+   *   The name of the module declaring the library.
+   * @param string $name
+   *   The name of the library.
+   *
+   * @return void
+   */
+  public function addDependency($module, $name);
+
+  /**
+   * Clears (removes) all library dependencies for this asset.
+   *
+   * This does not affect ordering data.
+   *
+   * @return void
+   */
+  public function clearDependencies();
+
+  /**
+   * Declare that an asset should, if present, succeed this asset on output.
+   *
+   * Either the string identifier for the other asset, or the asset object
+   * itself, should be provided.
+   *
+   * @param string|AssetInterface $asset
+   *   The asset to succeed the current asset.
+   *
+   * @return void
+   */
+  public function before($asset);
+
+  /**
+   * Declare that an asset should, if present, precede this asset on output.
+   *
+   * Either the string identifier for the other asset, or the asset object
+   * itself, should be provided.
+   *
+   * @param string|AssetInterface $asset
+   *   The asset to precede the current asset.
+   *
+   * @return void
+   */
+  public function after($asset);
+
+  /**
+   * Returns ordering info declared by after().
+   *
+   * @return array
+   *   An array of strings or AssetInterface instances that must precede this
+   *   object on final output.
+   */
+  public function getPredecessors();
+
+  /**
+   * Returns ordering info declared by before().
+   *
+   * @return array
+   *   An array of strings or AssetInterface instances that must succeed this
+   *   object on final output.
+   */
+  public function getSuccessors();
+
+  /**
+   * Clears (removes) all ordering info declared by before() for this asset.
+   *
+   * This does not affect dependency data.
+   *
+   * @return void
+   */
+  public function clearSuccessors();
+
+  /**
+   * Clears (removes) all ordering info declared by after() for this asset.
+   *
+   * This does not affect dependency data.
+   *
+   * @return void
+   */
+  public function clearPredecessors();
+}
diff --git a/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php b/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php
new file mode 100644
index 0000000..ec39845
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AsseticAdapterAsset.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AsseticAdapterAsset.
+ */
+
+namespace Drupal\Core\Asset;
+use Assetic\Asset\AssetInterface;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+
+/**
+ * A class that reduces boilerplate code by centrally disabling the Assetic
+ * properties and methods Drupal does not support.
+ */
+abstract class AsseticAdapterAsset implements AssetInterface {
+  /**
+   * @throws \InvalidArgumentException
+   */
+  public function getVars() {
+    throw new UnsupportedAsseticBehaviorException("Drupal does not use or support Assetic's 'vars' concept.");
+  }
+
+  /**
+   * @throws \InvalidArgumentException
+   */
+  public function setValues(array $values) {
+    throw new UnsupportedAsseticBehaviorException("Drupal does not use or support Assetic's 'values' concept.");
+  }
+
+  /**
+   * @throws \InvalidArgumentException
+   */
+  public function getValues() {
+    throw new UnsupportedAsseticBehaviorException("Drupal does not use or support Assetic's 'values' concept.");
+  }
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Bag/AssetBag.php b/core/lib/Drupal/Core/Asset/Bag/AssetBag.php
new file mode 100644
index 0000000..94834b4
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Bag/AssetBag.php
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetBag.
+ */
+
+namespace Drupal\Core\Asset\Bag;
+
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\AssetLibraryRepository;
+use Drupal\Core\Asset\Bag\AssetBagInterface;
+use Drupal\Core\Asset\Collection\CssCollection;
+use Drupal\Core\Asset\Collection\JsCollection;
+use Drupal\Core\Asset\JavascriptAssetInterface;
+use Drupal\Core\Asset\StylesheetAssetInterface;
+
+/**
+ * The default AssetBag, used to declare assets needed for a response.
+ */
+class AssetBag implements AssetBagInterface {
+
+  /**
+   * The assets in this AssetBag.
+   *
+   * @var array
+   */
+  protected $assets = array();
+
+  /**
+   * @var \Drupal\Core\Asset\Collection\CssCollection
+   */
+  protected $css;
+
+  /**
+   * @var \Drupal\Core\Asset\Collection\JsCollection
+   */
+  protected $js;
+
+  /**
+   * Whether this AssetBag is frozen.
+   *
+   * @var bool
+   */
+  protected $frozen = FALSE;
+
+  public function __construct() {
+    $this->js = new JsCollection();
+    $this->css = new CssCollection();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(AssetInterface $asset) {
+    if ($this->isFrozen()) {
+      throw new \LogicException('Assets cannot be added to a frozen AssetBag.', E_ERROR);
+    }
+
+    if ($asset instanceof JavascriptAssetInterface) {
+      $this->js->add($asset);
+    }
+    if ($asset instanceof StylesheetAssetInterface) {
+      $this->css->add($asset);
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addAssetBag(AssetBagInterface $bag, $freeze = TRUE) {
+    if ($this->isFrozen()) {
+      throw new \LogicException('Assets cannot be added to a frozen AssetBag.', E_ERROR);
+    }
+
+    if ($bag->hasCss()) {
+      $this->css->mergeCollection($bag->getCss());
+    }
+    if ($bag->hasJs()) {
+      $this->js->mergeCollection($bag->getJs());
+    }
+
+    if ($freeze) {
+      $bag->freeze();
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasCss() {
+    return !$this->css->isEmpty();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCss() {
+    return $this->css;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function all() {
+    return $this->assets;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * TODO js settings need a complete overhaul
+   */
+  public function addJsSetting($data) {
+    $this->javascript['settings']['data'][] = $data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasJs() {
+    return !$this->js->isEmpty();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getJs() {
+    return $this->js;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function freeze() {
+    $this->frozen = TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFrozen() {
+    return $this->frozen;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resolveDependencies(AssetLibraryRepository $repository) {
+    foreach ($this->css as $asset) {
+      foreach ($repository->resolveDependencies($asset) as $dep) {
+        $this->add($dep);
+      }
+    }
+
+    foreach ($this->js as $asset) {
+      foreach ($repository->resolveDependencies($asset) as $dep) {
+        $this->add($dep);
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/Bag/AssetBagInterface.php b/core/lib/Drupal/Core/Asset/Bag/AssetBagInterface.php
new file mode 100644
index 0000000..21223ec
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Bag/AssetBagInterface.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetBagInterface.
+ */
+
+namespace Drupal\Core\Asset\Bag;
+
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\AssetLibraryRepository;
+
+/**
+ * Defines a common interface for asset bags.
+ *
+ * Asset bags are a mechanism for internal Drupal code to list a set of assets
+ * and provide some metadata about their manner of use. During a normal page
+ * build, a number of these bags are likely to be built and ultimately merged
+ * onto the Response object.
+ *
+ * An AssetBagInterface object's contents are not expected to be in the final,
+ * ordered form in which it will ultimately be delivered as part of the page
+ * response. Rather, a stack of such bags will be combined towards the end of
+ * the page request into the final asset list.
+ */
+interface AssetBagInterface {
+
+  /**
+   * Adds another Asset to this AssetBag.
+   *
+   * @param AssetInterface $asset
+   *
+   * @return AssetBagInterface
+   *   Returns the current AssetBagInterface object for method chaining.
+   */
+  public function add(AssetInterface $asset);
+
+  /**
+   * Adds another AssetBag to this one.
+   *
+   * @param AssetBagInterface $bag
+   *
+   * @return AssetBagInterface
+   *   Returns the current AssetBagInterface object for method chaining.
+   */
+  public function addAssetBag(AssetBagInterface $bag);
+
+  /**
+   * Adds configuration settings for eventual inclusion in drupalSettings.
+   *
+   * TODO refactor & refine to completion
+   *
+   * @param $data
+   *   An associative array containing configuration settings, to be eventually
+   *   merged into drupalSettings. Settings should be be keyed, typically by
+   *   by module name, in order to avoid conflicts in the drupalSettings object.
+   *
+   * @return AssetBagInterface $this
+   *   Returns the current AssetBagInterface object for method chaining.
+   */
+  public function addJsSetting($data);
+
+  /**
+   * Indicates whether this object contains any CSS assets.
+   *
+   * @return bool
+   */
+  public function hasCss();
+
+  /**
+   * Returns the CSS assets in this bag, in the order they were added.
+   *
+   * @return \Drupal\Core\Asset\Collection\AssetCollectionInterface
+   */
+  public function getCss();
+
+  /**
+   * Indicates whether this AssetBagInterface contains any JavaScript assets.
+   *
+   * @return bool
+   */
+  public function hasJs();
+
+  /**
+   * Returns the JavaScript assets in this bag, in the order they were added.
+   *
+   * @return \Drupal\Core\Asset\Collection\AssetCollectionInterface
+   */
+  public function getJs();
+
+  /**
+   * Marks this bag as incapable of receiving new data.
+   *
+   * @return void
+   */
+  public function freeze();
+
+  /**
+   * Indicates whether or not this bag is frozen.
+   *
+   * @return bool
+   */
+  public function isFrozen();
+
+  /**
+   * Resolves all contained asset dependencies and add them into this bag.
+   *
+   * @param AssetLibraryRepository $repository
+   *   The AssetLibraryRepository against which to resolve asset dependencies.
+   *
+   * @return void
+   */
+  public function resolveDependencies(AssetLibraryRepository $repository);
+}
diff --git a/core/lib/Drupal/Core/Asset/Bag/AssetLibrary.php b/core/lib/Drupal/Core/Asset/Bag/AssetLibrary.php
new file mode 100644
index 0000000..14bac54
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Bag/AssetLibrary.php
@@ -0,0 +1,210 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetLibrary.
+ */
+
+namespace Drupal\Core\Asset\Bag;
+
+use Drupal\Core\Asset\AssetOrderingInterface;
+use Drupal\Core\Asset\Bag\AssetBag;
+
+class AssetLibrary extends AssetBag implements AssetOrderingInterface {
+
+  /**
+   * The asset library's title.
+   *
+   * @var string
+   */
+  protected $title = '';
+
+  /**
+   * The asset library's version.
+   *
+   * @var string
+   */
+  protected $version;
+
+  /**
+   * The asset library's website.
+   *
+   * @var string
+   */
+  protected $website = '';
+
+  /**
+   * The asset library's dependencies (on other asset libraries).
+   *
+   * @var array
+   */
+  protected $dependencies = array();
+
+  protected $predecessors = array();
+
+  protected $successors = array();
+
+  public function __construct(array $values = array()) {
+    // TODO do it right.
+    $vals = array_intersect_key($values, array_flip(array('title', 'version', 'website', 'dependencies')));
+    foreach ($vals as $key => $val) {
+      $this->$key = $val;
+    }
+  }
+
+  /**
+   * Set the asset library's title.
+   *
+   * @param string $title
+   *   The title of the asset library.
+   *
+   * @return \Drupal\Core\Asset\AssetLibrary
+   *   The asset library, to allow for chaining.
+   */
+  public function setTitle($title) {
+    $this->attemptWrite();
+    $this->title = $title;
+    return $this;
+  }
+
+  /**
+   * Get the asset library's title.
+   *
+   * @return string
+   *   The title of the asset library.
+   */
+  public function getTitle() {
+    return $this->title;
+  }
+
+  /**
+   * Set the asset library's website.
+   *
+   * @param string $website
+   *   The website of the asset library.
+   *
+   * @return \Drupal\Core\Asset\AssetLibrary
+   *   The asset library, to allow for chaining.
+   */
+  public function setWebsite($website) {
+    $this->attemptWrite();
+    $this->website = $website;
+    return $this;
+  }
+
+  /**
+   * Get the asset library's website.
+   *
+   * @return string
+   *   The website of the asset library.
+   */
+  public function getWebsite() {
+    return $this->website;
+  }
+
+  /**
+   * Set the asset library's version.
+   *
+   * @param string $version
+   *   The version of the asset library.
+   *
+   * @return \Drupal\Core\Asset\AssetLibrary
+   *   The asset library, to allow for chaining.
+   */
+  public function setVersion($version) {
+    $this->attemptWrite();
+    $this->version = $version;
+    return $this;
+  }
+
+  /**
+   * Get the asset library's version.
+   *
+   * @return string
+   *   The version of the asset library.
+   */
+  public function getVersion() {
+    return $this->version;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasDependencies() {
+    return !empty($this->dependencies);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addDependency($module, $name) {
+    $this->attemptWrite();
+    $this->dependencies[] = array($module, $name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearDependencies() {
+    $this->attemptWrite();
+    $this->dependencies = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDependencyInfo() {
+    return $this->dependencies;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function before($asset) {
+    $this->successors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function after($asset) {
+    $this->predecessors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPredecessors() {
+    return $this->predecessors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSuccessors() {
+    return $this->successors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearSuccessors() {
+    $this->successors = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearPredecessors() {
+    $this->predecessors = array();
+  }
+
+  /**
+   * Checks if the asset library is frozen, throws an exception if it is.
+   */
+  protected function attemptWrite() {
+    if ($this->isFrozen()) {
+      throw new \LogicException('Metadata cannot be modified on a frozen AssetLibrary.');
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/BaseAsset.php b/core/lib/Drupal/Core/Asset/BaseAsset.php
new file mode 100644
index 0000000..48a3038
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/BaseAsset.php
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\BaseAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Filter\FilterCollection;
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * A base abstract asset.
+ *
+ * This is an amalgam of Assetic\Asset\BaseAsset (copied directly) with
+ * implementations of the additional methods specified by Drupal's own
+ * Drupal\Core\Asset\AssetInterface.
+ *
+ * The methods load() and getLastModified() are left undefined, although a
+ * reusable doLoad() method is available to child classes.
+ */
+abstract class BaseAsset extends AsseticAdapterAsset implements AssetInterface, AssetOrderingInterface {
+
+  protected $filters;
+
+  protected $sourceRoot;
+
+  protected $sourcePath;
+
+  protected $targetPath;
+
+  protected $content;
+
+  protected $loaded;
+
+  /**
+   * @var AssetMetadataBag
+   */
+  protected $metadata;
+
+  protected $dependencies = array();
+
+  protected $successors = array();
+
+  protected $predecessors = array();
+
+  public function __construct(AssetMetadataBag $metadata, $filters = array(), $sourceRoot = NULL, $sourcePath = NULL) {
+    $this->filters = new FilterCollection($filters);
+    $this->sourceRoot = $sourceRoot;
+    $this->sourcePath = $sourcePath;
+    $this->loaded = FALSE;
+    $this->metadata = $metadata;
+  }
+
+  public function __clone() {
+    $this->filters = clone $this->filters;
+    $this->metadata = clone $this->metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadata() {
+    return $this->metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function ensureFilter(FilterInterface $filter) {
+    $this->filters->ensure($filter);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFilters() {
+    return $this->filters->all();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearFilters() {
+    $this->filters->clear();
+  }
+
+  /**
+   * Encapsulates asset loading logic.
+   *
+   * @param string          $content          The asset content
+   * @param FilterInterface $additionalFilter An additional filter
+   */
+  protected function doLoad($content, FilterInterface $additionalFilter = NULL) {
+    $filter = clone $this->filters;
+    if ($additionalFilter) {
+      $filter->ensure($additionalFilter);
+    }
+
+    $asset = clone $this;
+    $asset->setContent($content);
+
+    $filter->filterLoad($asset);
+    $this->content = $asset->getContent();
+
+    $this->loaded = TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dump(FilterInterface $additionalFilter = NULL) {
+    if (!$this->loaded) {
+      $this->load();
+    }
+
+    $filter = clone $this->filters;
+    if ($additionalFilter) {
+      $filter->ensure($additionalFilter);
+    }
+
+    $asset = clone $this;
+    $filter->filterDump($asset);
+
+    return $asset->getContent();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContent() {
+    return $this->content;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContent($content) {
+    $this->content = $content;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSourceRoot() {
+    return $this->sourceRoot;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSourcePath() {
+    return $this->sourcePath;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetPath() {
+    return $this->targetPath;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTargetPath($targetPath) {
+    $this->targetPath = $targetPath;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isPreprocessable() {
+    return (bool) $this->metadata->get('preprocess');
+  }
+
+  public function setDefaults(array $defaults) {
+    $this->metadataDefaults = $defaults;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasDependencies() {
+    return !empty($this->dependencies);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addDependency($module, $name) {
+    $this->dependencies[] = array($module, $name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearDependencies() {
+    $this->dependencies = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDependencyInfo() {
+    return $this->dependencies;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function before($asset) {
+    $this->successors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function after($asset) {
+    $this->predecessors[] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPredecessors() {
+    return $this->predecessors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSuccessors() {
+    return $this->successors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearSuccessors() {
+    $this->successors = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearPredecessors() {
+    $this->predecessors = array();
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/BaseExternalAsset.php b/core/lib/Drupal/Core/Asset/BaseExternalAsset.php
new file mode 100644
index 0000000..183bd2e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/BaseExternalAsset.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\BaseFileAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Util\PathUtils;
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\BaseAsset;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+
+abstract class BaseExternalAsset extends BaseAsset {
+
+  protected $sourceUrl;
+
+  public function __construct(AssetMetadataBag $metadata, $sourceUrl, $filters = array()) {
+    if (0 === strpos($sourceUrl, '//')) {
+      $sourceUrl = 'http:' . $sourceUrl;
+    }
+    elseif (FALSE === strpos($sourceUrl, '://')) {
+      throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL.', $sourceUrl));
+    }
+
+    $this->sourceUrl = $sourceUrl;
+    $this->ignoreErrors = FALSE; // TODO expose somehow
+
+    list($scheme, $url) = explode('://', $sourceUrl, 2);
+    list($host, $path) = explode('/', $url, 2);
+
+    parent::__construct($metadata, $filters, $scheme.'://'.$host, $path);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->sourceUrl;
+  }
+
+  /**
+   * Returns the time the current asset was last modified.
+   *
+   * @todo copied right from Assetic. needs to be made more Drupalish.
+   *
+   * @return integer|null A UNIX timestamp
+   */
+  public function getLastModified() {
+    if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(array('http' => array('method' => 'HEAD'))))) {
+      foreach ($http_response_header as $header) {
+        if (0 === stripos($header, 'Last-Modified: ')) {
+          list(, $mtime) = explode(':', $header, 2);
+
+          return strtotime(trim($mtime));
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(FilterInterface $additionalFilter = NULL) {
+    // TODO dumb and kinda wrong, decide how to do this right.
+    throw new UnsupportedAsseticBehaviorException('Drupal does not support the retrieval or manipulation of remote assets.');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/BaseFileAsset.php b/core/lib/Drupal/Core/Asset/BaseFileAsset.php
new file mode 100644
index 0000000..49e7352
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/BaseFileAsset.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\BaseFileAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Util\PathUtils;
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\BaseAsset;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+abstract class BaseFileAsset extends BaseAsset {
+
+  protected $source;
+
+  public function __construct(AssetMetadataBag $metadata, $source, $filters = array()) {
+    $sourceRoot = dirname($source);
+    $sourcePath = basename($source);
+    $this->source = $source;
+
+    parent::__construct($metadata, $filters, $sourceRoot, $sourcePath);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->source;
+  }
+
+  /**
+   * Returns the time the current asset was last modified.
+   *
+   * @return integer|null A UNIX timestamp
+   */
+  public function getLastModified() {
+    if (!is_file($this->source)) {
+      throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $this->source));
+    }
+
+    return filemtime($this->source);
+  }
+
+  /**
+   * Loads the asset into memory and applies load filters.
+   *
+   * You may provide an additional filter to apply during load.
+   *
+   * @todo copied right from Assetic. needs to be made more Drupalish.
+   *
+   * @param FilterInterface $additionalFilter An additional filter
+   */
+  public function load(FilterInterface $additionalFilter = NULL) {
+    if (!is_file($this->source)) {
+      throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $this->source));
+    }
+
+    $this->doLoad(file_get_contents($this->source), $additionalFilter);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/BaseStringAsset.php b/core/lib/Drupal/Core/Asset/BaseStringAsset.php
new file mode 100644
index 0000000..ef5ddeb
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/BaseStringAsset.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\BaseInlineAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Assetic\Filter\FilterInterface;
+use Drupal\Core\Asset\BaseAsset;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+abstract class BaseStringAsset extends BaseAsset {
+
+  protected $lastModified;
+
+  public function __construct(AssetMetadataBag $metadata, $content, $filters = array()) {
+    $this->content = $content;
+    $this->lastModified = REQUEST_TIME; // TODO this is terrible
+
+    parent::__construct($metadata, $filters);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    // TODO hashing current content means this id is essentially useless.
+    return md5($this->content);
+  }
+
+  public function setLastModified($last_modified) {
+    $this->lastModified = $last_modified;
+  }
+
+  public function getLastModified() {
+    return $this->lastModified;
+  }
+
+  public function load(FilterInterface $additionalFilter = NULL) {
+    $this->doLoad($this->content, $additionalFilter);
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php
new file mode 100644
index 0000000..f63853c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionBasicInterface.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\AssetCollectionBasicInterface.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\AssetInterface;
+use Assetic\Asset\AssetInterface as AsseticAssetInterface;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+
+/**
+ * Describes an asset collection: a container for assets.
+ *
+ * Asset collections are nothing more than a mechanism for holding and easily
+ * moving a set of a specific type of asset around.
+ *
+ * This interface contains the subset of methods that feasible for
+ * AssetAggregateInterface to share; because certain internal sequencing and
+ * state is important to aggregates, they cannot behave like a full collection.
+ *
+ * @see \Drupal\Core\Asset\Aggregate\AssetAggregateInterface
+ * @see \Drupal\Core\Asset\Collection\AssetCollectionInterface
+ */
+interface AssetCollectionBasicInterface extends \Traversable {
+
+  /**
+   * Removes an asset from the aggregate.
+   *
+   * Wraps Assetic's AssetCollection::removeLeaf() to ease removal of keys.
+   *
+   * @param AssetInterface|string $needle
+   *   Either an AssetInterface instance, or the string id of an asset.
+   * @param bool $graceful
+   *   Whether failure should return FALSE or throw an exception.
+   *
+   * @return bool
+   *
+   * @throws \OutOfBoundsException
+   */
+  public function remove($needle, $graceful = FALSE);
+
+  /**
+   * Indicates whether this collection contains the provided asset.
+  *
+   * @param AssetInterface $asset
+   *   Either an AssetInterface instance, or the string id of an asset.
+   *
+   * @return bool
+   */
+  public function contains(AssetInterface $asset);
+
+  /**
+   * Retrieves a contained asset by its string identifier.
+   *
+   * Call this with $graceful = TRUE as an equivalent to contains() if all you
+   * have is a string id.
+   *
+   * @param string $id
+   *   The id of the asset to retrieve.
+   * @param bool $graceful
+   *   Whether failure should return FALSE or throw an exception.
+   *
+   * @return AssetInterface|bool
+   *   FALSE if no asset could be found with that id, or an AssetInterface.
+   *
+   * @throws \OutOfBoundsException
+   */
+  public function getById($id, $graceful = TRUE);
+
+  /**
+   * Reindexes the ids of all assets contained in the aggregate.
+   *
+   * TODO this is necessary because AssetInterface::id() doesn't guarantee stable output. Fix that, and this can go away
+   *
+   * @return void
+   */
+  public function reindex();
+
+  /**
+   * Indicates whether this collection contains any assets.
+   *
+   * @return bool
+   *   TRUE if contained assets are present, FALSE otherwise.
+   */
+  public function isEmpty();
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php
new file mode 100644
index 0000000..d7a69e9
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/AssetCollectionInterface.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\AssetCollectionInterface.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\AssetInterface;
+
+/**
+ * Describes an asset collection.
+ *
+ * @see \Drupal\Core\Asset\Collection\AssetCollectionBasicInterface
+ */
+interface AssetCollectionInterface extends AssetCollectionBasicInterface {
+
+  /**
+   * Returns all assets contained in this collection.
+   *
+   * @return array
+   *   An array of AssetInterface instances.
+   */
+  public function all();
+
+  /**
+   * Adds an asset to the collection.
+   *
+   * @param \Drupal\Core\Asset\AssetInterface $asset
+   *   The asset to add.
+   *
+   * @return bool
+   *   TRUE if the asset was already added, FALSE if it was already present in
+   *   the collection.
+   */
+  public function add(AssetInterface $asset);
+
+  /**
+   * Merges another asset collection into this one.
+   *
+   * If an asset is present in both collections, as identified by
+   * AssetInterface::id(), the asset from the passed collection will
+   * supercede the asset in this collection.
+   *
+   * @param AssetCollectionInterface $collection
+   *   The collection to merge.
+   *
+   * @return void
+   */
+  public function mergeCollection(AssetCollectionInterface $collection);
+
+  /**
+   * Freeze this asset collection, preventing asset additions or removals.
+   *
+   * This does not prevent modification of assets already contained within the
+   * collection.
+   *
+   * TODO put this on the basic interface so aggregates have it, too?
+   *
+   * @return void
+   */
+  public function freeze();
+
+  /**
+   * Indicates whether or not this collection is frozen.
+   *
+   * @return bool
+   */
+  public function isFrozen();
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Collection/BaseAssetCollection.php b/core/lib/Drupal/Core/Asset/Collection/BaseAssetCollection.php
new file mode 100644
index 0000000..ff55f6e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/BaseAssetCollection.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\BaseAssetCollection.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\Aggregate\AssetAggregateInterface;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Exception\UnsupportedAsseticBehaviorException;
+
+/**
+ * A container for assets.
+ *
+ * @see CssCollection
+ * @see JsCollection
+ */
+abstract class BaseAssetCollection implements \IteratorAggregate, AssetCollectionInterface {
+
+  protected $assetStorage;
+
+  protected $assetIdMap = array();
+
+  protected $frozen = FALSE;
+
+  public function __construct() {
+    $this->assetStorage = new \SplObjectStorage();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function add(AssetInterface $asset) {
+    $this->attemptWrite();
+    $this->ensureCorrectType($asset);
+
+    $this->assetStorage->attach($asset);
+    $this->assetIdMap[$asset->id()] = $asset;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function contains(AssetInterface $asset) {
+    return $this->assetStorage->contains($asset);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getById($id, $graceful = TRUE) {
+    if (isset($this->assetIdMap[$id])) {
+      return $this->assetIdMap[$id];
+    }
+    else if ($graceful) {
+      return FALSE;
+    }
+
+    throw new \OutOfBoundsException(sprintf('This collection does not contain an asset with id %s.', $id));
+  }
+   /**
+   * {@inheritdoc}
+   */
+  public function reindex() {
+    $map = array();
+    foreach ($this->assetIdMap as $asset) {
+      $map[$asset->id()] = $asset;
+    }
+    $this->assetIdMap = $map;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function remove($needle, $graceful = TRUE) {
+    $this->attemptWrite();
+
+    if ((is_string($needle) && $needle = $this->getById($needle, $graceful)) ||
+        $needle instanceof AssetInterface) {
+      unset($this->assetIdMap[$needle->id()], $this->assetStorage[$needle]);
+      return TRUE;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function all() {
+    return $this->assetIdMap;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeCollection(AssetCollectionInterface $collection) {
+    $this->attemptWrite();
+    // TODO subtype mismatch checking
+
+    $other_assets = $collection->all();
+
+    foreach (array_intersect_key($this->assetIdMap, $other_assets) as $id => $asset) {
+      unset($other_assets[$id]);
+    }
+
+    foreach ($other_assets as $asset) {
+      $this->add($asset);
+    }
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function freeze() {
+    $this->frozen = TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFrozen() {
+    return $this->frozen;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->assetIdMap);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return empty($this->assetIdMap);
+  }
+
+  /**
+   * Checks if the asset library is frozen, throws an exception if it is.
+   */
+  protected function attemptWrite() {
+    if ($this->isFrozen()) {
+      throw new \LogicException('Cannot write to a frozen AssetCollection.');
+    }
+  }
+
+  /**
+   * Ensures that the asset is of the correct subtype (e.g., css vs. js).
+   *
+   * @param AssetInterface $asset
+   *
+   * @throws \Drupal\Core\Asset\Exception\AssetTypeMismatchException
+   */
+  abstract protected function ensureCorrectType(AssetInterface $asset);
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Collection/CssCollection.php b/core/lib/Drupal/Core/Asset/Collection/CssCollection.php
new file mode 100644
index 0000000..ef650fe
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/CssCollection.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\CssCollection.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Exception\AssetTypeMismatchException;
+use Drupal\Core\Asset\StylesheetAssetInterface;
+
+/**
+ * A collection of CSS assets.
+ */
+class CssCollection extends BaseAssetCollection {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function ensureCorrectType(AssetInterface $asset) {
+    if (!$asset instanceof StylesheetAssetInterface) {
+      throw new AssetTypeMismatchException('CSS collections can only work with CSS assets.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Collection/JsCollection.php b/core/lib/Drupal/Core/Asset/Collection/JsCollection.php
new file mode 100644
index 0000000..f1093d0
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Collection/JsCollection.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Collection\JsCollection.
+ */
+
+namespace Drupal\Core\Asset\Collection;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Exception\AssetTypeMismatchException;
+use Drupal\Core\Asset\JavascriptAssetInterface;
+
+/**
+ * A collection of JS assets.
+ */
+class JsCollection extends BaseAssetCollection {
+  // TODO implement handling for js "settings"
+  /**
+   * {@inheritdoc}
+   */
+  protected function ensureCorrectType(AssetInterface $asset) {
+    if (!$asset instanceof JavascriptAssetInterface) {
+      throw new AssetTypeMismatchException('JS collections can only work with JS assets.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php b/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php
new file mode 100644
index 0000000..63d8ef0
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/CssCollectionGrouperNouveaux.php
@@ -0,0 +1,210 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssCollectionGrouperNouveaux.
+ */
+
+namespace Drupal\Core\Asset;
+use Drupal\Core\Asset\Aggregate\CssAggregateAsset;
+use Drupal\Core\Asset\Collection\CssCollection;
+use Gliph\Traversal\DepthFirst;
+use Gliph\Visitor\DepthFirstBasicVisitor;
+use Drupal\Core\Asset\AssetGraph;
+
+/**
+ * Groups CSS assets.
+ */
+class CssCollectionGrouperNouveaux {
+
+  /**
+   * @var AssetLibraryRepository
+   */
+  protected $repository;
+
+  /**
+   * An array of optimal groups for the assets currently being processed.
+   *
+   * This is ephemeral state; it is only stored as an object property in order
+   * to avoid doing certain processing twice.
+   *
+   * @var array
+   */
+  protected $optimal;
+
+  /**
+   * @var \SplObjectStorage;
+   */
+  protected $optimal_lookup;
+
+  public function __construct(AssetLibraryRepository $repository) {
+    $this->repository = $repository;
+  }
+
+  /**
+   * Groups a collection of assets into logical groups of asset collections.
+   *
+   * @param array $assets
+   *   An asset collection.
+   *   TODO update the interface to be an AssetCollection, not an array
+   *
+   * @return array
+   *   A sorted array of asset groups.
+   */
+  public function group(CssCollection $assets) {
+    $tsl = $this->getOptimalTSL($assets);
+
+    // TODO replace with CssCollection
+    // TODO ordering suddenly matters here...problem?
+    $processed = new CssCollection();
+    $last_key = FALSE;
+    foreach ($tsl as $asset) {
+      // TODO fix the visitor - this will fail right now because the optimality data got depleted during traversal
+      $key = $this->optimal_lookup->contains($asset) ? $this->optimal_lookup[$asset] : FALSE;
+
+      if ($key !== $last_key) {
+        $processed[] = $aggregate = new CssAggregateAsset($asset->getMetadata());
+      }
+
+      $aggregate->add($asset);
+    }
+
+    return $processed;
+  }
+
+  /**
+   * Gets a topologically sorted list that is optimal for grouping.
+   *
+   * @param array $assets
+   *
+   * @return array
+   *   A linear list of assets that will enable optimal groupings.
+   *
+   * @throws \LogicException
+   */
+  protected function getOptimalTSL(CssCollection $assets) {
+    // We need to define the optimum minimal group set, given metadata
+    // boundaries across which aggregates cannot be safely made.
+    $this->optimal = array();
+
+    // Also create an SplObjectStorage to act as a lookup table on an asset to
+    // its group, if any.
+    $this->optimal_lookup = new \SplObjectStorage();
+
+    // Finally, create a specialized directed adjacency list that will capture
+    // sequencing information.
+    $graph = new AssetGraph();
+
+    foreach ($assets as $asset) {
+      $graph->addVertex($asset);
+
+      $k = $this->getGroupKey($asset);
+
+      if ($k === FALSE) {
+        // Record no optimality information for ungroupable assets; they will
+        // be visited normally and rearranged as needed.
+        continue;
+      }
+
+      if (!isset($this->optimal[$k])) {
+        // Create an SplObjectStorage to represent each set of assets that would
+        // optimally be grouped together.
+        $this->optimal[$k] = new \SplObjectStorage();
+      }
+      $this->optimal[$k]->attach($asset, $k);
+      $this->optimal_lookup->attach($asset, $this->optimal[$k]);
+    }
+
+    // First, transpose the graph in order to get an appropriate answer
+    $transpose = $graph->transpose();
+
+    // Create a queue of start vertices to prime the traversal.
+    $queue = $this->createSourceQueue($graph, $transpose);
+
+    // Now, create the visitor and walk the graph to get an optimal TSL.
+    $visitor = new OptimallyGroupedTSLVisitor($this->optimal, $this->optimal_lookup);
+    DepthFirst::traverse($transpose, $visitor, $queue);
+
+    return $visitor->getTSL();
+  }
+
+  /**
+   * Gets the grouping key for the provided asset.
+   *
+   * @param $asset
+   *
+   * @return bool|string
+   * @throws \UnexpectedValueException
+   */
+  protected function getGroupKey(StylesheetAssetInterface $asset) {
+    $meta = $asset->getMetadata();
+    // The browsers for which the CSS item needs to be loaded is part of the
+    // information that determines when a new group is needed, but the order
+    // of keys in the array doesn't matter, and we don't want a new group if
+    // all that's different is that order.
+    $browsers = $meta->get('browsers');
+    ksort($browsers);
+
+    if ($asset instanceof StylesheetFileAsset) {
+      // Compose a string key out of the set of relevant properties.
+      // TODO - currently ignoring group, which is used in the current implementation. wishful thinking? maybe, maybe not.
+      // TODO media has been pulled out - needs to be handled by the aggregator, wrapping css in media queries
+      $k = $asset->isPreprocessable()
+        ? implode(':', array('file', $meta->get('every_page'), implode('', $browsers)))
+        : FALSE;
+
+      return $k;
+    }
+    else if ($asset instanceof StylesheetStringAsset) {
+      // String items are always grouped.
+      // TODO use the term 'inline' here? do "string" and "inline" necessarily mean the same?
+      $k = implode(':', 'string', implode('', $browsers));
+
+      return $k;
+    }
+    else if ($asset instanceof StylesheetExternalAsset) {
+      // Never group external assets.
+      $k = FALSE;
+
+      return $k;
+    }
+    else {
+      throw new \UnexpectedValueException(sprintf('Unknown CSS asset type "%s" somehow made it into the CSS collection during grouping.', get_class($asset)));
+    }
+  }
+
+  /**
+   * Creates a queue of starting vertices that will facilitate an ideal TSL.
+   *
+   * @param AssetGraph $graph
+   * @param AssetGraph $transpose
+   *
+   * @return \SplQueue $queue
+   *   A queue of vertices
+   */
+  protected function createSourceQueue(AssetGraph $graph, AssetGraph $transpose) {
+    $reach_visitor = new DepthFirstBasicVisitor();
+
+    // Find source vertices (outdegree 0) in the original graph
+    $sources = DepthFirst::find_sources($graph, $reach_visitor);
+
+    // Traverse the transposed graph to get reachability data on each vertex
+    DepthFirst::traverse($transpose, $reach_visitor, clone $sources);
+
+    // Sort vertices via a PriorityQueue based on total reach
+    $pq = new \SplPriorityQueue();
+    foreach ($sources as $vertex) {
+      $pq->insert($vertex, count($reach_visitor->getReachable($vertex)));
+    }
+
+    // Dump the priority queue into a normal queue
+    // TODO maybe gliph should support pq/heaps as a queue type on which to operate?
+    $queue = new \SplQueue();
+    foreach ($pq as $vertex) {
+      $queue->push($vertex);
+    }
+
+    return $queue;
+  }
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerNouveaux.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerNouveaux.php
new file mode 100644
index 0000000..d35d3f9
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerNouveaux.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssCollectionOptimizerNouveaux.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+
+/**
+ * Optimizes a collection of CSS assets.
+ */
+class CssCollectionOptimizerNouveaux implements AssetCollectionOptimizerInterface {
+
+  /**
+   * A CSS asset grouper.
+   *
+   * @var \Drupal\Core\Asset\CssCollectionGrouper
+   */
+  protected $grouper;
+
+  /**
+   * A CSS asset optimizer.
+   *
+   * @var \Drupal\Core\Asset\CssOptimizer
+   */
+  protected $optimizer;
+
+  /**
+   * An asset dumper.
+   *
+   * @var \Drupal\Core\Asset\AssetDumper
+   */
+  protected $dumper;
+
+  /**
+   * The state key/value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs a CssCollectionOptimizerNouveaux.
+   *
+   * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface
+   *   The grouper for CSS assets.
+   * @param \Drupal\Core\Asset\AssetOptimizerInterface
+   *   The optimizer for a single CSS asset.
+   * @param \Drupal\Core\Asset\AssetDumperInterface
+   *   The dumper for optimized CSS assets.
+   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   *   The state key/value store.
+   */
+  public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, KeyValueStoreInterface $state) {
+    $this->grouper = $grouper;
+    $this->optimizer = $optimizer;
+    $this->dumper = $dumper;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function optimize(array $assets) {
+    $tsl = $this->grouper->group($assets);
+  }
+
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Exception/AssetTypeMismatchException.php b/core/lib/Drupal/Core/Asset/Exception/AssetTypeMismatchException.php
new file mode 100644
index 0000000..ed651bf
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Exception/AssetTypeMismatchException.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\Exception\AssetTypeMismatchException.
+ */
+
+namespace Drupal\Core\Asset\Exception;
+
+/**
+ * Thrown when asset subtypes (i.e., CSS vs. JS) are incorrectly mixed.
+ *
+ * For example, if a CSS asset is added to a JS collection, this should be
+ * thrown.
+ */
+class AssetTypeMismatchException extends \InvalidArgumentException {}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Exception/UnsupportedAsseticBehaviorException.php b/core/lib/Drupal/Core/Asset/Exception/UnsupportedAsseticBehaviorException.php
new file mode 100644
index 0000000..c25473c
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Exception/UnsupportedAsseticBehaviorException.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\UnsupportedAsseticBehaviorException.
+ */
+
+namespace Drupal\Core\Asset\Exception;
+
+/**
+ * Assetic supports certain interactions with methods that we do not. This
+ * exception is thrown when such methods are touched.
+ */
+class UnsupportedAsseticBehaviorException extends \LogicException {}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php
new file mode 100644
index 0000000..92dca24
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Factory/AssetCollector.php
@@ -0,0 +1,246 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Core\Asset\AssetCollector.
+ */
+
+namespace Drupal\Core\Asset\Factory;
+use Drupal\Core\Asset\AssetInterface;
+use Drupal\Core\Asset\Bag\AssetBagInterface;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+
+/**
+ * A class that helps to create and collect assets.
+ *
+ * This class should be set with appropriate defaults, injected with an AssetBag
+ * for collection, then injected into an asset-producing segment of code in
+ * order to ease the creation and collection of asset information.
+ */
+class AssetCollector {
+
+  /**
+   * The bag used to store any assets that are added.
+   *
+   * @var \Drupal\Core\Asset\Bag\AssetBagInterface
+   */
+  protected $bag;
+
+  /**
+   * Flag indicating whether or not the object is locked.
+   *
+   * Locking prevents modifying the underlying defaults or the current bag.
+   *
+   * @var bool
+   */
+  protected $locked = FALSE;
+
+  /**
+   * The key with which the lock was set.
+   *
+   * This exact value (===) must be provided in order to unlock the instance.
+   *
+   * There are no type restrictions.
+   *
+   * @var mixed
+   */
+  protected $lockKey;
+
+  protected $defaultCssMetadata;
+
+  protected $defaultJsMetadata;
+
+  protected $classMap = array(
+    'css' => array(
+      'file' => 'Drupal\\Core\\Asset\\StylesheetFileAsset',
+      'external' => 'Drupal\\Core\\Asset\\StylesheetExternalAsset',
+      'string' => 'Drupal\\Core\\Asset\\StylesheetStringAsset',
+    ),
+    'js' => array(
+      'file' => 'Drupal\\Core\\Asset\\JavascriptFileAsset',
+      'external' => 'Drupal\\Core\\Asset\\JavascriptExternalAsset',
+      'string' => 'Drupal\\Core\\Asset\\JavascriptStringAsset',
+     ),
+  );
+
+  public function __construct(AssetBagInterface $bag = NULL) {
+    $this->restoreDefaults();
+
+    if (!is_null($bag)) {
+      $this->setBag($bag);
+    }
+  }
+
+  /**
+   * Adds an asset to the contained AssetBag.
+   *
+   * It is not necessary to call this method on assets that were created via the
+   * create() method.
+   *
+   * @param AssetInterface $asset
+   *   The asset to add to the contained bag.
+   */
+  public function add(AssetInterface $asset) {
+    if (empty($this->bag)) {
+      throw new \Exception('No bag is currently attached to this collector.');
+    }
+    $this->bag->add($asset);
+    return $this;
+  }
+
+  /**
+   * Creates an asset, stores it in the collector's bag, and returns it.
+   *
+   * TODO flesh out these docs to be equivalent to drupal_add_css/js()
+   *
+   * @param string $asset_type
+   *   A string indicating the asset type - 'css' or 'js'.
+   * @param string $source_type
+   *   A string indicating the source type - 'file', 'external' or 'string'.
+   * @param string $data
+   *   A string containing data that defines the asset. Appropriate values vary
+   *   depending on the source_type param:
+   *    - 'file': the relative path to the file, or a stream wrapper URI.
+   *    - 'external': the absolute path to the external asset.
+   *    - 'string': a string containing valid CSS or Javascript to be injected
+   *      directly onto the page.
+   * @param array $options
+   *   An array of metadata to explicitly set on the asset. These will override
+   *   metadata defaults that are injected onto the asset at creation time.
+   * @param array $filters
+   *   An array of filters to apply to the object
+   *   TODO this should, maybe, be removed entirely
+   *
+   * @return \Drupal\Core\Asset\AssetInterface
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if an invalid asset type or source type is passed.
+   */
+  public function create($asset_type, $source_type, $data, $options = array(), $filters = array()) {
+    if (!isset($this->classMap[$asset_type])) {
+      throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are allowed, "%s" requested.', $asset_type));
+    }
+    if (!isset($this->classMap[$asset_type][$source_type])) {
+      throw new \InvalidArgumentException(sprintf('Only sources of type "file", "string", or "external" are allowed, "%s" requested.', $source_type));
+    }
+
+    $metadata = $this->getMetadataDefaults($asset_type);
+    $metadata->replace($options);
+
+    $class = $this->classMap[$asset_type][$source_type];
+    $asset = new $class($metadata, $data, $filters);
+
+    if (!empty($this->bag)) {
+      $this->add($asset);
+    }
+
+    return $asset;
+  }
+
+  public function setBag(AssetBagInterface $bag) {
+    if ($this->isLocked()) {
+      throw new \Exception('The collector instance is locked. A new bag cannot be attached to a locked collector.');
+    }
+    $this->bag = $bag;
+  }
+
+  public function clearBag() {
+    if ($this->isLocked()) {
+      throw new \Exception('The collector instance is locked. Bags cannot be cleared on a locked collector.');
+    }
+    $this->bag = NULL;
+  }
+
+  public function createJavascriptSetting() {
+    // TODO figure out settings
+  }
+
+  public function lock($key) {
+    if ($this->isLocked()) {
+      throw new \Exception('Collector is already locked.', E_WARNING);
+    }
+
+    $this->locked = TRUE;
+    $this->lockKey = $key;
+    return TRUE;
+  }
+
+  public function unlock($key) {
+    if (!$this->isLocked()) {
+      throw new \Exception('Collector is not locked', E_WARNING);
+    }
+
+    if ($this->lockKey !== $key) {
+      throw new \Exception('Attempted to unlock Collector with incorrect key.', E_WARNING);
+    }
+
+    $this->locked = FALSE;
+    $this->lockKey = NULL;
+    return TRUE;
+  }
+
+  public function isLocked() {
+    return $this->locked;
+  }
+
+  public function setDefaultMetadata($type, AssetMetadataBag $metadata) {
+    if ($this->isLocked()) {
+      throw new \Exception('The collector instance is locked. Asset defaults cannot be modified on a locked collector.');
+    }
+
+    if ($type === 'css') {
+      $this->defaultCssMetadata = $metadata;
+    }
+    elseif ($type === 'js') {
+      $this->defaultJsMetadata = $metadata;
+    }
+    else {
+      throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are supported, "%s" requested.', $type));
+    }
+  }
+
+  /**
+   * Gets a clone of the metadata bag for a given asset type.
+   *
+   * Clones are returned in order to ensure there is a unique metadata object
+   * for every asset, and that the default metadata contained in the collector
+   * cannot be modified externally.
+   *
+   * @param $type
+   *   A string, 'css' or 'js', indicating the type of metadata to retrieve.
+   *
+   * @return AssetMetadataBag
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if a type other than 'css' or 'js' is provided.
+   */
+  public function getMetadataDefaults($type) {
+    if ($type === 'css') {
+      return clone $this->defaultCssMetadata;
+    }
+    elseif ($type === 'js') {
+      return clone $this->defaultJsMetadata;
+    }
+    else {
+      throw new \InvalidArgumentException(sprintf('Only assets of type "js" or "css" are supported, "%s" requested.', $type));
+    }
+  }
+
+  /**
+   * Restores metadata default bags to their default state.
+   *
+   * This simply creates new instances of CssMetadataBag and JsMetadataBag, as
+   * those classes have the normal defaults as hardmapped properties.
+   *
+   * @throws \Exception
+   *   Thrown if the collector is locked when this method is called.
+   */
+  public function restoreDefaults() {
+    if ($this->isLocked()) {
+      throw new \Exception('The collector instance is locked. Asset defaults cannot be modified on a locked collector.');
+    }
+    $this->defaultCssMetadata = new CssMetadataBag();
+    $this->defaultJsMetadata = new JsMetadataBag();
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php b/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php
new file mode 100644
index 0000000..84df9ca
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Factory/AssetLibraryCollector.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Core\Asset\AssetLibraryCollector.
+ */
+
+namespace Drupal\Core\Asset\Factory;
+
+use Drupal\Component\Utility\Crypt;
+use \Drupal\Core\Asset\AssetLibraryRepository;
+use Drupal\Core\Asset\Factory\AssetCollector;
+use Drupal\Core\Asset\Bag\AssetLibrary;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+
+class AssetLibraryCollector {
+
+  /**
+   * @var \Drupal\Core\Asset\AssetLibraryRepository
+   */
+  protected $manager;
+
+  protected $module;
+
+  protected $locked;
+
+  protected $lockKey;
+
+  protected $privateKey;
+
+  public function __construct(AssetLibraryRepository $manager) {
+    $this->manager = $manager;
+  }
+
+  public function add($name, AssetLibrary $library) {
+    $this->manager->add($this->module, $name, $library);
+    return $this;
+  }
+
+  public function buildLibrary($name, $values) {
+    $library = $this->createLibrary($name, $values);
+
+    $collector = new AssetCollector();
+    $collector->setBag($library);
+    $collector->setDefaultMetadata('js', new JsMetadataBag(array('group' => JS_LIBRARY)));
+    $collector->lock($this->getPrivateKey()); // TODO is locking here a bad idea?
+
+    return $collector;
+  }
+
+  public function createLibrary($name, $values) {
+    $library = new AssetLibrary($values);
+    $this->add($name, $library);
+
+    return $library;
+  }
+
+  public function setModule($module) {
+    $this->module = $module;
+  }
+
+  public function lock($key) {
+    if ($this->isLocked()) {
+      throw new \Exception('Collector is already locked.', E_WARNING);
+    }
+
+    $this->locked = TRUE;
+    $this->lockKey = $key;
+    return TRUE;
+  }
+
+  public function unlock($key) {
+    if (!$this->isLocked()) {
+      throw new \Exception('Collector is not locked', E_WARNING);
+    }
+
+    if ($this->lockKey !== $key) {
+      throw new \Exception('Attempted to unlock Collector with incorrect key.', E_WARNING);
+    }
+
+    $this->locked = FALSE;
+    $this->lockKey = NULL;
+    return TRUE;
+  }
+
+  public function isLocked() {
+    return $this->locked;
+  }
+
+  protected function getPrivateKey() {
+    if (empty($this->privateKey)) {
+      // This doesn't need to be highly secure, just decently random.
+      $this->privateKey = Crypt::randomStringHashed(8);
+    }
+    return $this->privateKey;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/JavascriptAssetInterface.php b/core/lib/Drupal/Core/Asset/JavascriptAssetInterface.php
new file mode 100644
index 0000000..01f4930
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/JavascriptAssetInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\JavascriptAssetInterface.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\AssetInterface;
+
+/**
+ * Represents a JavaScript asset.
+ */
+interface JavascriptAssetInterface extends AssetInterface {
+
+  public function setScope($scope);
+
+  public function getScope();
+
+  public function getScopeDefault();
+
+//  public function addDependency($name);
+//
+//  public function hasDependencies();
+//
+//  public function getDependencies();
+}
diff --git a/core/lib/Drupal/Core/Asset/JavascriptExternalAsset.php b/core/lib/Drupal/Core/Asset/JavascriptExternalAsset.php
new file mode 100644
index 0000000..0984dd7
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/JavascriptExternalAsset.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\JavascriptExternalAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\JavascriptAssetInterface;
+use Drupal\Core\Asset\BaseExternalAsset;
+
+class JavascriptExternalAsset extends BaseExternalAsset implements JavascriptAssetInterface {
+
+  protected $scope;
+
+  /**
+   * Scope defaults to footer as almost all JavaScript assets can be placed in
+   * the footer.
+   *
+   * @tricky this is a change from the previous behavior!
+   *
+   * @var string
+   */
+  protected $scopeDefault = 'footer';
+
+  public function setScope($scope) {
+    $this->scope = $scope;
+  }
+
+  public function getScope() {
+    return empty($this->scope) ? $this->scopeDefault : $this->scope;
+  }
+
+  public function getScopeDefault() {
+    return $this->scopeDefault;
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/JavascriptFileAsset.php b/core/lib/Drupal/Core/Asset/JavascriptFileAsset.php
new file mode 100644
index 0000000..3235e14
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/JavascriptFileAsset.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\JavascriptFileAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\JavascriptAssetInterface;
+use Drupal\Core\Asset\AssetLibraryReference;
+use Drupal\Core\Asset\BaseFileAsset;
+
+class JavascriptFileAsset extends BaseFileAsset implements JavascriptAssetInterface {
+
+  protected $scope;
+
+  /**
+   * Scope defaults to footer as almost all JavaScript assets can be placed in
+   * the footer.
+   *
+   * @tricky this is a change from the previous behavior!
+   *
+   * @var string
+   */
+  protected $scopeDefault = 'footer';
+
+  public function setScope($scope) {
+    $this->scope = $scope;
+  }
+
+  public function getScope() {
+    return empty($this->scope) ? $this->scopeDefault : $this->scope;
+  }
+
+  public function getScopeDefault() {
+    return $this->scopeDefault;
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/JavascriptStringAsset.php b/core/lib/Drupal/Core/Asset/JavascriptStringAsset.php
new file mode 100644
index 0000000..c8f3767
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/JavascriptStringAsset.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\JavascriptStringAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\JavascriptAssetInterface;
+use Drupal\Core\Asset\BaseStringAsset;
+
+class JavascriptStringAsset extends BaseStringAsset implements JavascriptAssetInterface {
+
+  protected $scope;
+
+  /**
+   * Scope defaults to footer as almost all JavaScript assets can be placed in
+   * the footer.
+   *
+   * @tricky this is a change from the previous behavior!
+   *
+   * @var string
+   */
+  protected $scopeDefault = 'footer';
+
+  public function setScope($scope) {
+    $this->scope = $scope;
+  }
+
+  public function getScope() {
+    return empty($this->scope) ? $this->scopeDefault : $this->scope;
+  }
+
+  public function getScopeDefault() {
+    return $this->scopeDefault;
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/Metadata/AssetMetadataBag.php b/core/lib/Drupal/Core/Asset/Metadata/AssetMetadataBag.php
new file mode 100644
index 0000000..963f7e8
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Metadata/AssetMetadataBag.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\AssetMetadataBag.
+ */
+
+namespace Drupal\Core\Asset\Metadata;
+
+/**
+ * A bag for holding asset metadata.
+ *
+ * For each declared property, this bag keeps track of both a default value and
+ * an explicit value. Defaults can only be set in the constructor, explicit
+ * values can be set at any time. Explicit values are coalesced over default
+ * values.
+ *
+ * TODO this is totally not specific to assets - move it somewhere more generic?
+ * TODO it's maybe not so important to rigorously control access to the defaults data
+ */
+abstract class AssetMetadataBag implements \IteratorAggregate, \Countable {
+
+  /**
+   * Contains default values.
+   *
+   * @var array
+   */
+  protected $default = array();
+
+  /**
+   * Contains explicitly set values.
+   *
+   * @var array
+   */
+  protected $explicit = array();
+
+  public function __construct(array $default = array()) {
+    $this->default = array_replace_recursive($this->default, $default);
+  }
+
+  public function all() {
+    return array_replace_recursive($this->default, $this->explicit);
+  }
+
+  public function keys() {
+    return array_keys($this->all());
+  }
+
+  public function has($key) {
+    return array_key_exists($key, $this->explicit) ||
+      array_key_exists($key, $this->default);
+  }
+
+  /**
+   * Reverts the a back to its default, if one exists.
+   *
+   * @param $key
+   *
+   * @return void
+   */
+  public function revert($key) {
+    unset($this->explicit[$key]);
+  }
+
+  public function isDefault($key) {
+    return !array_key_exists($key, $this->explicit) &&
+      array_key_exists($key, $this->default);
+  }
+
+  public function add(array $values = array()) {
+    $this->explicit = array_replace_recursive($this->explicit, $values);
+  }
+
+  public function replace(array $values = array()) {
+    $this->explicit = $values;
+  }
+
+  public function get($key) {
+    if (array_key_exists($key, $this->explicit)) {
+      return $this->explicit[$key];
+    }
+
+    if (array_key_exists($key, $this->default)) {
+      return $this->default[$key];
+    }
+  }
+
+  public function getIterator() {
+    return new \ArrayIterator($this->all());
+  }
+
+  public function count() {
+    return count($this->all());
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Metadata/CssMetadataBag.php b/core/lib/Drupal/Core/Asset/Metadata/CssMetadataBag.php
new file mode 100644
index 0000000..1f7c410
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Metadata/CssMetadataBag.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\CssMetadataBag.
+ */
+
+namespace Drupal\Core\Asset\Metadata;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Manages CSS asset default and explicit metadata.
+ */
+class CssMetadataBag extends AssetMetadataBag {
+
+  protected $default = array(
+    'group' => CSS_AGGREGATE_DEFAULT, // TODO Just removing this would be *awesome*.
+    'every_page' => FALSE,
+    'media' => 'all',
+    'preprocess' => TRUE,
+    'browsers' => array(
+      'IE' => TRUE,
+      '!IE' => TRUE,
+    ),
+  );
+
+  public function __construct(array $default = array()) {
+    $this->default = array_replace_recursive($this->default, $default);
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/Metadata/JsMetadataBag.php b/core/lib/Drupal/Core/Asset/Metadata/JsMetadataBag.php
new file mode 100644
index 0000000..fde88e2
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/Metadata/JsMetadataBag.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\JsMetadataBag.
+ */
+
+namespace Drupal\Core\Asset\Metadata;
+use Drupal\Core\Asset\Metadata\AssetMetadataBag;
+
+/**
+ * Manages Javascript asset default and explicit metadata.
+ */
+class JsMetadataBag extends AssetMetadataBag {
+
+  protected $default = array(
+    'group' => JS_DEFAULT,
+    'every_page' => FALSE,
+    'scope' => 'header',
+    'cache' => TRUE,
+    'preprocess' => TRUE,
+    'attributes' => array(),
+    'version' => NULL,
+    'browsers' => array(),
+  );
+
+  public function __construct(array $default = array()) {
+    $this->default = array_replace_recursive($this->default, $default);
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/OptimallyGroupedTSLVisitor.php b/core/lib/Drupal/Core/Asset/OptimallyGroupedTSLVisitor.php
new file mode 100644
index 0000000..cc77d0e
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/OptimallyGroupedTSLVisitor.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\OptimallyGroupedTSLVisitor.
+ */
+
+namespace Drupal\Core\Asset;
+use Gliph\Visitor\DepthFirstVisitorInterface;
+
+/**
+ * DepthFirst visitor intended for use with a asset data that will select the
+ * optimal valid TSL, given a preferred grouping of vertices.
+ */
+class OptimallyGroupedTSLVisitor implements DepthFirstVisitorInterface {
+
+  /**
+   * @var array
+   */
+  protected $tsl;
+
+  /**
+   * @var array
+   */
+  protected $groups;
+
+  /**
+   * @var \SplObjectStorage
+   */
+  protected $vertexMap;
+
+  /**
+   * Creates a new optimality visitor.
+   *
+   * @param array $groups
+   *   An array of SplObjectStorage, the contents of each representing an
+   *   optimal grouping.
+   *
+   * @param \SplObjectStorage $vertex_map
+   *   A map of vertices to the group in which they reside, if any.
+   */
+  public function __construct($groups, \SplObjectStorage $vertex_map) {
+    $this->tsl = array();
+    $this->groups = $groups;
+    $this->vertexMap = $vertex_map;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onInitializeVertex($vertex, $source, \SplQueue $queue) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onBackEdge($vertex, \Closure $visit) {
+    // TODO: Implement onBackEdge() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onStartVertex($vertex, \Closure $visit) {
+    $this->active->attach($vertex);
+
+    // If there's a record in the vertex map, it means this vertex has an
+    // optimal group. Remove it from that group, as it being here means it's
+    // been visited.
+    if ($this->vertexMap->contains($vertex)) {
+      $this->vertexMap[$vertex]->detach($vertex);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onExamineEdge($from, $to, \Closure $visit) {}
+
+  /**
+   * Here be the unicorns.
+   *
+   * Once the depth-first traversal is done for a vertex, rather than
+   * simply pushing it onto the TSL and moving on (as in a basic depth-first
+   * traversal), if the finished vertex is a member of an optimality group, then
+   * visit all other (unvisited) members of that optimality group.
+   *
+   * This ensures the final TSL has the tightest possible adherence to the
+   * defined optimal groupings while still respecting the DAG.
+   *
+   */
+  public function onFinishVertex($vertex, \Closure $visit) {
+    // TODO this still isn't quite optimal; it can split groups unnecessarily. tweak a little more.
+    // TODO explore risk of hitting the 100 call stack limit
+    if ($this->vertexMap->contains($vertex)) {
+      foreach ($this->vertexMap[$vertex] as $vertex) {
+        $visit($vertex);
+      }
+    }
+    $this->tsl[] = $vertex;
+  }
+
+  /**
+   * Returns the TSL produced by a depth-first traversal.
+   *
+   * @return array
+   *   A topologically sorted list of vertices.
+   */
+  public function getTSL() {
+    return $this->tsl;
+  }
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Asset/StylesheetAssetInterface.php b/core/lib/Drupal/Core/Asset/StylesheetAssetInterface.php
new file mode 100644
index 0000000..9a81daf
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/StylesheetAssetInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\StylesheetAssetInterface.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\AssetInterface;
+
+/**
+ * Represents a cascading stylesheet (CSS) asset.
+ */
+interface StylesheetAssetInterface extends AssetInterface {
+
+  /**
+   * Sets the media property to be applied on this stylesheet asset.
+   *
+   * @param string $type
+   *   Either a media type, or a media query.
+   *
+   * @return NULL
+   */
+  public function setMedia($type);
+
+  /**
+   * Returns the current value of the media property on this stylesheet asset.
+   *
+   * @return string
+   */
+  public function getMedia();
+
+  /**
+   * Returns the default value of the media property on this stylesheet asset.
+   *
+   * @return mixed
+   */
+  public function getMediaDefault();
+}
diff --git a/core/lib/Drupal/Core/Asset/StylesheetExternalAsset.php b/core/lib/Drupal/Core/Asset/StylesheetExternalAsset.php
new file mode 100644
index 0000000..b7dc800
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/StylesheetExternalAsset.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\StylesheetExternalAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\StylesheetAssetInterface;
+use Drupal\Core\Asset\BaseExternalAsset;
+
+class StylesheetExternalAsset extends BaseExternalAsset implements StylesheetAssetInterface {
+
+  /**
+   * The media query or type to use for this asset. Defaults to 'all'.
+   *
+   * @todo inject the defaults instead of hardcoding them.
+   *
+   * @var string
+   */
+  protected $mediaDefault = 'all';
+
+  protected $media;
+
+  protected $preprocess = FALSE;
+
+  /**
+   * Returns the current value of the media property on this stylesheet asset.
+   *
+   * @return string
+   */
+  public function getMedia() {
+    return $this->media;
+  }
+
+  /**
+   * Returns the default value of the media property on this stylesheet asset.
+   *
+   * @return mixed
+   */
+  public function getMediaDefault() {
+    return $this->mediaDefault;
+  }
+
+  /**
+   * Sets the media property to be applied on this stylesheet asset.
+   *
+   * @param string $type
+   *   Either a media type, or a media query.
+   *
+   * @return NULL
+   */
+  public function setMedia($type) {
+    $this->media = $type;
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/StylesheetFileAsset.php b/core/lib/Drupal/Core/Asset/StylesheetFileAsset.php
new file mode 100644
index 0000000..38b3a6b
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/StylesheetFileAsset.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\StylesheetFileAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\StylesheetAssetInterface;
+use Drupal\Core\Asset\BaseFileAsset;
+
+class StylesheetFileAsset extends BaseFileAsset implements StylesheetAssetInterface {
+
+  /**
+   * The media query or type to use for this asset. Defaults to 'all'.
+   *
+   * @todo inject the defaults instead of hardcoding them.
+   *
+   * @var string
+   */
+  protected $mediaDefault = 'all';
+
+  protected $media;
+
+  /**
+   * Returns the current value of the media property on this stylesheet asset.
+   *
+   * @return string
+   */
+  public function getMedia() {
+    return $this->media;
+  }
+
+  /**
+   * Returns the default value of the media property on this stylesheet asset.
+   *
+   * @return mixed
+   */
+  public function getMediaDefault() {
+    return $this->mediaDefault;
+  }
+
+  /**
+   * Sets the media property to be applied on this stylesheet asset.
+   *
+   * @param string $type
+   *   Either a media type, or a media query.
+   *
+   * @return NULL
+   */
+  public function setMedia($type) {
+    $this->media = $type;
+  }
+}
diff --git a/core/lib/Drupal/Core/Asset/StylesheetStringAsset.php b/core/lib/Drupal/Core/Asset/StylesheetStringAsset.php
new file mode 100644
index 0000000..639f6c1
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/StylesheetStringAsset.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Asset\StylesheetStringAsset.
+ */
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Core\Asset\StylesheetAssetInterface;
+use Drupal\Core\Asset\BaseStringAsset;
+
+class StylesheetStringAsset extends BaseStringAsset implements StylesheetAssetInterface {
+
+  /**
+   * The media query or type to use for this asset. Defaults to 'all'.
+   *
+   * @todo inject the defaults instead of hardcoding them.
+   *
+   * @var string
+   */
+  protected $mediaDefault = 'all';
+
+  protected $media;
+
+  protected $preprocess = FALSE;
+
+  /**
+   * Returns the current value of the media property on this stylesheet asset.
+   *
+   * @return string
+   */
+  public function getMedia() {
+    return $this->media;
+  }
+
+  /**
+   * Returns the default value of the media property on this stylesheet asset.
+   *
+   * @return mixed
+   */
+  public function getMediaDefault() {
+    return $this->mediaDefault;
+  }
+
+  /**
+   * Sets the media property to be applied on this stylesheet asset.
+   *
+   * @param string $type
+   *   Either a media type, or a media query.
+   *
+   * @return NULL
+   */
+  public function setMedia($type) {
+    $this->media = $type;
+  }
+}
diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php
index 579e841..1768991 100644
--- a/core/modules/block/lib/Drupal/block/BlockBase.php
+++ b/core/modules/block/lib/Drupal/block/BlockBase.php
@@ -11,6 +11,7 @@
 use Drupal\block\BlockInterface;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Asset\Factory\AssetCollector;
 
 /**
  * Defines a base block implementation that most blocks plugins will extend.
@@ -181,5 +182,10 @@ public function getMachineNameSuggestion() {
 
     return $transliterated;
   }
-
+  /**
+   * {@inheritdoc}
+   */
+  public function declareAssets(AssetCollector $collector) {}
 }
+
+
diff --git a/core/modules/block/lib/Drupal/block/BlockPluginInterface.php b/core/modules/block/lib/Drupal/block/BlockPluginInterface.php
index b5433e6..d625616 100644
--- a/core/modules/block/lib/Drupal/block/BlockPluginInterface.php
+++ b/core/modules/block/lib/Drupal/block/BlockPluginInterface.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
 use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\Asset\Factory\AssetCollector;
 
 /**
  * Defines the required interface for all block plugins.
@@ -122,4 +123,12 @@ public function blockSubmit($form, &$form_state);
    */
   public function getMachineNameSuggestion();
 
+  /**
+   * Declares the assets required by this block to a collector.
+   *
+   * @param \Drupal\Core\Asset\Factory\AssetCollector $collector
+   *
+   * @return void
+   */
+  public function declareAssets(AssetCollector $collector);
 }
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetAssemblyTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetAssemblyTest.php
new file mode 100644
index 0000000..bb3c6ab
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetAssemblyTest.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Asset\AssetAssemblyTest.
+ */
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Core\Asset\Bag\AssetBag;
+use Drupal\Core\Asset\Bag\AssetLibrary;
+use Drupal\Core\Asset\AssetLibraryRepository;
+use Drupal\Core\Asset\AssetLibraryReference;
+use Drupal\Core\Asset\Collection\CssCollection;
+use Drupal\Core\Asset\Collection\JsCollection;
+use Drupal\Core\Asset\JavascriptFileAsset;
+use Drupal\Core\Asset\JavascriptStringAsset;
+use Drupal\Core\Asset\JavascriptExternalAsset;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+use Drupal\Core\Asset\StylesheetFileAsset;
+use Drupal\Core\Asset\StylesheetStringAsset;
+use Drupal\Core\Asset\StylesheetExternalAsset;
+
+use Drupal\Core\Extension\ModuleHandler;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests assorted collection and assembly related behaviors for assets.
+ *
+ * TODO refactor all of this into proper unit tests.
+ *
+ * @group Asset
+ */
+class AssetAssemblyTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset assembly tests',
+      'description' => 'Tests to ensure assets declared via the various possible approaches come out with the correct properties, in the proper order.',
+      'group' => 'Asset',
+    );
+  }
+
+  public function createJQueryAssetLibrary() {
+    $library = new AssetLibrary(array(new JavascriptFileAsset(new JsMetadataBag(), 'core/misc/jquery.js')));
+    return $library->setTitle('jQuery')
+      ->setVersion('1.8.2')
+      ->setWebsite('http://jquery.com');
+  }
+
+  /**
+   * Tests various simple single-bag asset assembly scenarios.
+   *
+   * Much of the real complexity of asset ordering in AssetBags comes from
+   * nesting them, but these tests are focused on the basic mechanics of
+   * assembly within a single bag.
+   */
+  public function testSingleBagAssetAssemblies() {
+    // Dead-simple bag - contains just one css and one js assets, both local files.
+    $bag = new AssetBag();
+
+    $css1 = new StylesheetFileAsset(new CssMetadataBag(), 'foo');
+    $js1 = new JavascriptFileAsset(new JsMetadataBag(), 'baz');
+
+    $bag->add($css1);
+    $bag->add($js1);
+
+    $this->assertTrue($bag->hasCss(), 'AssetBag correctly reports that it contains CSS assets.');
+    $this->assertTrue($bag->hasJs(), 'AssetBag correctly reports that it contains javascript assets.');
+
+    $css_collection = new CssCollection();
+    $css_collection->add($css1);
+
+    $js_collection = new JsCollection();
+    $js_collection->add($js1);
+
+    $this->assertEquals($css_collection, $bag->getCss());
+    $this->assertEquals($js_collection, $bag->getJs());
+
+    $css2 = new StylesheetFileAsset(new CssMetadataBag(), 'bing');
+    $bag->add($css2);
+    $css_collection->add($css2);
+
+    $this->assertEquals($css_collection, $bag->getCss());
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetBagTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetBagTest.php
new file mode 100644
index 0000000..5d5ea1e
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetBagTest.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Asset\AssetBagTest.
+ */
+
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Core\Asset\Bag\AssetBag;
+use Drupal\Core\Asset\Collection\CssCollection;
+use Drupal\Core\Asset\Collection\JsCollection;
+use Drupal\Core\Asset\JavascriptFileAsset;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+use Drupal\Core\Asset\StylesheetFileAsset;
+use Drupal\Tests\UnitTestCase;
+
+
+/**
+ * @group Asset
+ */
+class AssetBagTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset bag unit tests',
+      'description' => 'Unit tests on AssetBag',
+      'group' => 'Asset',
+    );
+  }
+
+  public function testAddValidAsset() {
+    // Dead-simple bag - contains just one css and one js assets, both local files.
+    $bag = new AssetBag();
+
+    $css1 = $this->getMock('Drupal\\Core\\Asset\\StylesheetFileAsset', array(), array(), '', FALSE);
+    $js1 = $this->getMock('Drupal\\Core\\Asset\\JavascriptFileAsset', array(), array(), '', FALSE);
+
+    $bag->add($css1);
+    $bag->add($js1);
+
+    $this->assertTrue($bag->hasCss(), 'AssetBag correctly reports that it contains CSS assets.');
+    $this->assertTrue($bag->hasJs(), 'AssetBag correctly reports that it contains javascript assets.');
+
+    $css_collection = new CssCollection();
+    $css_collection->add($css1);
+
+    $js_collection = new JsCollection();
+    $js_collection->add($js1);
+
+    $this->assertEquals($css_collection, $bag->getCss());
+    $this->assertEquals($js_collection, $bag->getJs());
+
+    $css2 = $this->getMock('Drupal\\Core\\Asset\\StylesheetFileAsset', array(), array(), '', FALSE);
+
+    $bag->add($css2);
+    $css_collection->add($css2);
+
+    $this->assertEquals($css_collection, $bag->getCss());
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php
new file mode 100644
index 0000000..20d4535
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetCollectorTest.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Asset\AssetCollectorTest.
+ */
+
+namespace Drupal\Tests\Core\Asset;
+
+if (!defined('CSS_AGGREGATE_THEME')) {
+  define('CSS_AGGREGATE_THEME', 100);
+}
+
+if (!defined('CSS_AGGREGATE_DEFAULT')) {
+  define('CSS_AGGREGATE_DEFAULT', 0);
+}
+
+if (!defined('JS_DEFAULT')) {
+  define('JS_DEFAULT', 0);
+}
+
+use Drupal\Core\Asset\Bag\AssetBag;
+use Drupal\Core\Asset\Factory\AssetCollector;
+use Drupal\Core\Asset\Metadata\CssMetadataBag;
+use Drupal\Core\Asset\Metadata\JsMetadataBag;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Unit tests for AssetCollector.
+ *
+ * @group Asset
+ */
+class AssetCollectorTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\Core\Asset\Factory\AssetCollector
+   */
+  protected $collector;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset Collector tests',
+      'description' => 'Tests that the AssetCollector system works correctly.',
+      'group' => 'Asset',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    $this->collector = new AssetCollector();
+  }
+
+  /**
+   * Tests that the collector injects provided metadata to created assets.
+   */
+  public function testMetadataInjection() {
+    $asset = $this->collector->create('css', 'file', 'foo', array('group' => CSS_AGGREGATE_THEME));
+    $meta = $asset->getMetadata();
+    $this->assertEquals(CSS_AGGREGATE_THEME, $meta->get('group'), 'Collector injected user-passed parameters into the created asset.');
+    $this->assertFalse($meta->isDefault('group'));
+  }
+
+  public function testDefaultPropagation() {
+    // Test that defaults are correctly applied by the factory.
+    $meta = new CssMetadataBag(array('every_page' => TRUE, 'group' => CSS_AGGREGATE_THEME));
+    $this->collector->setDefaultMetadata('css', $meta);
+    $css1 = $this->collector->create('css', 'file', 'foo');
+
+    $asset_meta = $css1->getMetadata();
+    $this->assertTrue($asset_meta->get('every_page'));
+    $this->assertEquals(CSS_AGGREGATE_THEME, $asset_meta->get('group'));
+  }
+
+  /**
+   * @expectedException Exception
+   */
+  public function testExceptionOnAddingAssetWithoutBagPresent() {
+    $asset = $this->collector->create('css', 'string', 'foo');
+    $this->collector->add($asset);
+  }
+
+  /**
+   * TODO separate test for an explicit add() call.
+   */
+  public function testAssetsImplicitlyArriveInInjectedBag() {
+    $bag = new AssetBag();
+    $this->collector->setBag($bag);
+
+    $asset = $this->collector->create('css', 'file', 'bar');
+    $this->assertContains($asset, $bag->getCss(), 'Created asset was implicitly added to bag.');
+  }
+
+  public function testAddAssetExplicitly() {
+    $bag = new AssetBag();
+    $this->collector->setBag($bag);
+
+    $asset = $this->getMock('Drupal\\Core\\Asset\\StylesheetFileAsset', array(), array(), '', FALSE);
+    $this->collector->add($asset);
+
+    $this->assertContains($asset, $bag->getCss());
+  }
+
+  /**
+   * @expectedException Exception
+   */
+  public function testClearBag() {
+    $bag = new AssetBag();
+    $this->collector->setBag($bag);
+    $this->collector->clearBag();
+
+    $this->collector->add($this->collector->create('css', 'file', 'bar'));
+  }
+
+  public function testLock() {
+    $this->assertTrue($this->collector->lock($this), 'Collector locked successfully.');
+    $this->assertTrue($this->collector->isLocked(), 'Collector accurately reports that it is locked via isLocked() method.');
+  }
+
+  public function testUnlock() {
+    $this->collector->lock($this);
+    $this->assertTrue($this->collector->unlock($this), 'Collector unlocked successfully when appropriate key was provided.');
+    $this->assertFalse($this->collector->isLocked(), 'Collector correctly reported unlocked state via isLocked() method after unlocking.');
+  }
+
+  /**
+   * @expectedException Exception
+   */
+  public function testUnlockFailsWithoutCorrectSecret() {
+    $this->collector->lock('foo');
+    $this->collector->unlock('bar');
+  }
+
+  /**
+   * @expectedException Exception
+   */
+  public function testLockingPreventsSettingDefaults() {
+    $this->collector->lock($this);
+    $this->collector->setDefaultMetadata('css', new CssMetadataBag());
+  }
+
+  /**
+   * @expectedException Exception
+   */
+  public function testLockingPreventsRestoringDefaults() {
+    $this->collector->lock($this);
+    $this->collector->restoreDefaults();
+  }
+
+  /**
+   * @expectedException Exception
+   */
+  public function testLockingPreventsClearingBag() {
+    $this->collector->lock($this);
+    $this->collector->clearBag();
+  }
+
+  /**
+   * @expectedException Exception
+   */
+  public function testLockingPreventsSettingBag() {
+    $this->collector->lock($this);
+    $this->collector->setBag(new AssetBag());
+  }
+
+  public function testBuiltinDefaultAreTheSame() {
+    $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css'));
+    $this->assertEquals(new JsMetadataBag(), $this->collector->getMetadataDefaults('js'));
+  }
+
+  public function testChangeAndRestoreDefaults() {
+    $changed_css = new CssMetadataBag(array('foo' => 'bar', 'every_page' => TRUE));
+    $this->collector->setDefaultMetadata('css', $changed_css);
+
+    $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css'));
+    $this->assertNotSame($changed_css, $this->collector->getMetadataDefaults('css'), 'Metadata is cloned on retrieval from collector.');
+
+    $this->collector->restoreDefaults();
+    $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css'));
+
+    // Do another check to ensure that both metadata bags are correctly reset
+    $changed_js = new JsMetadataBag(array('scope' => 'footer', 'fizzbuzz' => 'llama'));
+    $this->collector->setDefaultMetadata('css', $changed_css);
+    $this->collector->setDefaultMetadata('js', $changed_js);
+
+    $this->assertEquals($changed_css, $this->collector->getMetadataDefaults('css'));
+    $this->assertEquals($changed_js, $this->collector->getMetadataDefaults('js'));
+
+    $this->collector->restoreDefaults();
+    $this->assertEquals(new CssMetadataBag(), $this->collector->getMetadataDefaults('css'));
+    $this->assertEquals(new JsMetadataBag(), $this->collector->getMetadataDefaults('js'));
+  }
+
+  /**
+   * @expectedException InvalidArgumentException
+   */
+  public function testGetNonexistentDefault() {
+    $this->collector->getMetadataDefaults('foo');
+  }
+
+
+  public function testCreateStylesheetFileAsset() {
+    $css_file1 = $this->collector->create('css', 'file', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\StylesheetFileAsset', $css_file1, 'Collector correctly created a StylesheetFileAsset instance.');
+  }
+
+  public function testCreateStylesheetExternalAsset() {
+    $css_external1 = $this->collector->create('css', 'external', 'http://foo.bar/path/to/asset.css');
+    $this->assertInstanceOf('\Drupal\Core\Asset\StylesheetExternalAsset', $css_external1, 'Collector correctly created a StylesheetExternalAsset instance.');
+  }
+
+  public function testCreateStylesheetStringAsset() {
+    $css_string1 = $this->collector->create('css', 'string', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\StylesheetStringAsset', $css_string1, 'Collector correctly created a StylesheetStringAsset instance .');
+  }
+
+  public function testCreateJavascriptFileAsset() {
+    $js_file1 = $this->collector->create('js', 'file', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\JavascriptFileAsset', $js_file1, 'Collector correctly created a JavascriptFileAsset instance .');
+  }
+
+  public function testCreateJavascriptExternalAsset() {
+    $js_external1 = $this->collector->create('js', 'external', 'http://foo.bar/path/to/asset.js');
+    $this->assertInstanceOf('\Drupal\Core\Asset\JavascriptExternalAsset', $js_external1, 'Collector correctly created a JavascriptExternalAsset instance .');
+  }
+
+  public function testCreateJavascriptStringAsset() {
+    $js_string1 = $this->collector->create('js', 'string', 'foo');
+    $this->assertInstanceOf('\Drupal\Core\Asset\JavascriptStringAsset', $js_string1, 'Collector correctly created a JavascriptStringAsset instance .');
+  }
+}
\ No newline at end of file
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetLibraryManagerTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetLibraryManagerTest.php
new file mode 100644
index 0000000..59a7b11
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetLibraryManagerTest.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ */
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Tests\UnitTestCase;
+
+
+/**
+ *
+ * @group TODO add a group
+ */
+class AssetLibraryManagerTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => '', // TODO give me a name
+      'description' => '', // TODO give me a description
+      'group' => '', // TODO give me the same group as above
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+  }
+
+  public function testStub() {
+    // TODO anything. without this, phpunit blows up.
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetLibraryTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetLibraryTest.php
new file mode 100644
index 0000000..3be2f3d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetLibraryTest.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Asset\AssetLibraryTest.
+ */
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Core\Asset\Bag\AssetLibrary;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ *
+ * @group Asset
+ */
+class AssetLibraryTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset Library tests',
+      'description' => 'Tests that the AssetLibrary behaves correctly.',
+      'group' => 'Asset',
+    );
+  }
+
+  public function getLibraryFixture() {
+    $library = new AssetLibrary();
+    $library->setTitle('foo')
+      ->setVersion('1.2.3')
+      ->setWebsite('http://foo.bar')
+      ->addDependency('foo', 'bar');
+    return $library;
+  }
+
+  public function testConstructorValueInjection() {
+    $values = array(
+      'title' => 'foo',
+      'version' => '1.2.3',
+      'website' => 'http://foo.bar',
+      'dependencies' => array(array('foo', 'bar')),
+    );
+    $library = new AssetLibrary($values);
+
+    $fixture = $this->getLibraryFixture();
+    $this->assertEquals($fixture->getTitle(), $library->getTitle(), 'Title passed correctly through the constructor.');
+    $this->assertEquals($fixture->getVersion(), $library->getVersion(), 'Version passed correctly through the constructor.');
+    $this->assertEquals($fixture->getWebsite(), $library->getWebsite(), 'Website passed correctly through the constructor.');
+    $this->assertEquals($fixture->getDependencyInfo(), $library->getDependencyInfo(), 'Dependencies information passed correctly through the constructor.');
+  }
+
+  public function testAddDependency() {
+    $library = $this->getLibraryFixture();
+    $library->addDependency('baz', 'bing');
+    $this->assertEquals($library->getDependencyInfo(), array(array('foo', 'bar'), array('baz', 'bing')), 'Dependencies added to library successfully.');
+  }
+
+  public function testClearDependencies() {
+    $library = $this->getLibraryFixture();
+    $library->clearDependencies();
+    $this->assertEmpty($library->getDependencyInfo(), 'Dependencies recorded in the library were cleared correctly.');
+  }
+
+  public function testFrozenNonwriteability() {
+    $library = $this->getLibraryFixture();
+    $library->freeze();
+    try {
+      $library->setTitle('bar');
+      $this->fail('No exception thrown when attempting to set a new title on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->setVersion('2.3.4');
+      $this->fail('No exception thrown when attempting to set a new version on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->setWebsite('http://bar.baz');
+      $this->fail('No exception thrown when attempting to set a new website on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->addDependency('bing', 'bang');
+      $this->fail('No exception thrown when attempting to add a new dependency on a frozen library.');
+    }
+    catch (\LogicException $e) {}
+
+    try {
+      $library->clearDependencies();
+      $this->fail('No exception thrown when attempting to clear dependencies from a frozen library.');
+    }
+    catch (\LogicException $e) {}
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetTest.php
new file mode 100644
index 0000000..2752d8c
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetTest.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @file
+ *
+ * Contains Drupal\Tests\Core\Asset\AssetTest.
+ */
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Tests\UnitTestCase;
+
+
+/**
+ * Tests for the base asset classes.
+ *
+ * TODO all of it.
+ *
+ * @group Asset
+ */
+class AssetTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Asset tests',
+      'description' => 'Unit tests for all base asset classes.',
+      'group' => 'Asset',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+  }
+
+  public function testStub() {
+    // TODO anything. without this, phpunit blows up.
+  }
+}
diff --git a/core/vendor/autoload.php b/core/vendor/autoload.php
index a391d4e..a525121 100644
--- a/core/vendor/autoload.php
+++ b/core/vendor/autoload.php
@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer' . '/autoload_real.php';
 
-return ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9::getLoader();
+return ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2::getLoader();
diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php
index 926818b..f6dbb7c 100644
--- a/core/vendor/composer/autoload_namespaces.php
+++ b/core/vendor/composer/autoload_namespaces.php
@@ -28,6 +28,7 @@
     'Guzzle\\Parser' => array($vendorDir . '/guzzle/parser'),
     'Guzzle\\Http' => array($vendorDir . '/guzzle/http'),
     'Guzzle\\Common' => array($vendorDir . '/guzzle/common'),
+    'Gliph' => array($vendorDir . '/sdboyer/gliph/src'),
     'EasyRdf_' => array($vendorDir . '/easyrdf/easyrdf/lib'),
     'Drupal\\Driver' => array($baseDir . '/drivers/lib'),
     'Drupal\\Core' => array($baseDir . '/core/lib'),
diff --git a/core/vendor/composer/autoload_real.php b/core/vendor/composer/autoload_real.php
index 6bb9081..52043fb 100644
--- a/core/vendor/composer/autoload_real.php
+++ b/core/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9
+class ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2
 {
     private static $loader;
 
@@ -19,9 +19,9 @@ public static function getLoader()
             return self::$loader;
         }
 
-        spl_autoload_register(array('ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        spl_autoload_unregister(array('ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit7816b7cb10809286c097e9dcbf7023e2', 'loadClassLoader'));
 
         $vendorDir = dirname(__DIR__);
         $baseDir = dirname(dirname($vendorDir));
diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json
index f5d9d72..be27d13 100644
--- a/core/vendor/composer/installed.json
+++ b/core/vendor/composer/installed.json
@@ -2064,5 +2064,51 @@
         ],
         "description": "Symfony Process Component",
         "homepage": "http://symfony.com"
+    },
+    {
+        "name": "sdboyer/gliph",
+        "version": "0.1.1",
+        "version_normalized": "0.1.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/sdboyer/gliph.git",
+            "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/sdboyer/gliph/zipball/c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a",
+            "reference": "c7bd13eb2e6b51b017f025e24a7a676a4eed1a4a",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3"
+        },
+        "time": "2013-09-14 04:16:01",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "Gliph": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Sam Boyer",
+                "email": "tech@samboyer.org"
+            }
+        ],
+        "description": "A graph library for PHP.",
+        "homepage": "http://github.com/sdboyer/gliph",
+        "keywords": [
+            "gliph",
+            "graph",
+            "library",
+            "php",
+            "spl"
+        ]
     }
 ]
diff --git a/core/vendor/sdboyer/gliph/README.md b/core/vendor/sdboyer/gliph/README.md
new file mode 100644
index 0000000..4415ef0
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/README.md
@@ -0,0 +1,38 @@
+# Gliph
+
+[![Build Status](https://travis-ci.org/sdboyer/gliph.png?branch=php53)](https://travis-ci.org/sdboyer/gliph)
+[![Latest Stable Version](https://poser.pugx.org/sdboyer/gliph/v/stable.png)](https://packagist.org/packages/sdboyer/gliph)
+
+Gliph is a **g**raph **li**brary for **PH**P. It provides graph building blocks and datastructures for use by other PHP applications. It is (currently) designed for use with in-memory graphs, not for interaction with a graph database like [Neo4J](http://neo4j.org/).
+
+Gliph is designed with performance in mind, but primarily to provide a sane interface. Graphs are hard enough without an arcane API making it worse.
+
+## Core Concepts
+
+Gliph has several components that work together: graph classes, algorithms, and visitors. Generally speaking, Gliph is patterned after the [C++ Boost Graph Library](http://www.boost.org/libs/graph/doc); reading their documentation can yield a lot of insight into how Gliph is intended to work.
+
+Note that Gliph is currently written for compatibility with PHP 5.3, but it is intended to port the library to PHP 5.5. The availability of traits, non-scalar/object keys returnable from iterators, and generators will considerably change both the internal and public-facing implementations.
+
+### Graphs
+
+There are a number of different strategies for representing graphs; these strategies are more or less efficient depending on certain properties the graph, and what needs to be done to the graph. The approach taken in Gliph is to offer a roughly consistent 'Graph' interface that is common to all these different strategies. The strategies will have varying levels of efficiency at meeting this common interface, so it is the responsibility of the user to select a graph implementation that is appropriate for their use case. This approach draws heavily from the [taxonomy of graphs](http://www.boost.org/doc/libs/1_54_0/libs/graph/doc/graph_concepts.html) established by the BGL.
+
+Gliph currently implements only an adjacency list graph strategy, in both directed and undirected flavors. Adjacency lists offer efficient access to out-edges, but inefficient access to in-edges (in a directed graph - in an undirected graph, in-edges and out-edges are the same). Adjacency lists and are generally more space-efficient for sparse graphs.
+
+## TODOs
+
+Lots. But, to start with:
+
+- Port to, or provide a parallel implementation in, PHP 5.5. Generators and non-scalar keys from iterators make this all SO much better. In doing that, also shift as much over to traits as possible.
+- Implement a generic breadth-first algorithm and its corresponding visitors.
+- Implement a generic iterative deepening depth-first algorithm, and its corresponding visitors.
+- Implement other popular connected components algorithms, as well as some shortest path algorithms (starting with Dijkstra)
+- Write up some examples showing how to actually use the library.
+
+## Acknowledgements
+
+This library draws heavy inspiration from the [C++ Boost Graph Library](http://www.boost.org/libs/graph/doc).
+
+## License
+
+MIT
diff --git a/core/vendor/sdboyer/gliph/composer.json b/core/vendor/sdboyer/gliph/composer.json
new file mode 100644
index 0000000..1434d37
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/composer.json
@@ -0,0 +1,20 @@
+{
+    "name": "sdboyer/gliph",
+    "description": "A graph library for PHP.",
+    "license": "MIT",
+    "keywords": ["gliph", "library", "php", "spl", "graph"],
+    "homepage": "http://github.com/sdboyer/gliph",
+    "type": "library",
+    "authors": [
+        {
+            "name": "Sam Boyer",
+            "email": "tech@samboyer.org"
+        }
+    ],
+    "require": {
+        "php": ">=5.3"
+    },
+    "autoload": {
+        "psr-0": { "Gliph": "src/" }
+    }
+}
diff --git a/core/vendor/sdboyer/gliph/composer.lock b/core/vendor/sdboyer/gliph/composer.lock
new file mode 100644
index 0000000..bc1108a
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/composer.lock
@@ -0,0 +1,439 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
+    ],
+    "hash": "c2c349f17b3e09198ed1a8335e431197",
+    "packages": [
+
+    ],
+    "packages-dev": [
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "1.2.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "1.2.12"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12",
+                "reference": "1.2.12",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-file-iterator": ">=1.3.0@stable",
+                "phpunit/php-text-template": ">=1.1.1@stable",
+                "phpunit/php-token-stream": ">=1.1.3@stable"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "3.7.*@dev"
+            },
+            "suggest": {
+                "ext-dom": "*",
+                "ext-xdebug": ">=2.0.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "PHP/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "time": "2013-07-06 06:26:16"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.3.3",
+            "source": {
+                "type": "git",
+                "url": "git://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "1.3.3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3",
+                "reference": "1.3.3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "File/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "http://www.phpunit.de/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2012-10-11 04:44:38"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.1.4",
+            "source": {
+                "type": "git",
+                "url": "git://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "1.1.4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4",
+                "reference": "1.1.4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "Text/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2012-10-31 11:15:28"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "1.0.5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1.0.5",
+                "reference": "1.0.5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "PHP/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "time": "2013-08-02 07:42:54"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "1.2.0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1.2.0",
+                "reference": "1.2.0",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "PHP/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2013-08-04 05:57:48"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "3.7.24",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "3.7.24"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.24",
+                "reference": "3.7.24",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=5.3.3",
+                "phpunit/php-code-coverage": "~1.2.1",
+                "phpunit/php-file-iterator": ">=1.3.1",
+                "phpunit/php-text-template": ">=1.1.1",
+                "phpunit/php-timer": ">=1.0.4",
+                "phpunit/phpunit-mock-objects": "~1.2.0",
+                "symfony/yaml": "~2.0"
+            },
+            "require-dev": {
+                "pear-pear/pear": "1.9.4"
+            },
+            "suggest": {
+                "ext-json": "*",
+                "ext-simplexml": "*",
+                "ext-tokenizer": "*",
+                "phpunit/php-invoker": ">=1.1.0,<1.2.0"
+            },
+            "bin": [
+                "composer/bin/phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.7.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "PHPUnit/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                "",
+                "../../symfony/yaml/"
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "http://www.phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2013-08-09 06:58:24"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "1.2.3",
+            "source": {
+                "type": "git",
+                "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "1.2.3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip",
+                "reference": "1.2.3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-text-template": ">=1.1.1@stable"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "PHPUnit/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Mock Object library for PHPUnit",
+            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+            "keywords": [
+                "mock",
+                "xunit"
+            ],
+            "time": "2013-01-13 10:24:48"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v2.3.3",
+            "target-dir": "Symfony/Component/Yaml",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/Yaml.git",
+                "reference": "v2.3.3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.3",
+                "reference": "v2.3.3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Symfony\\Component\\Yaml\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "http://symfony.com",
+            "time": "2013-07-21 12:12:18"
+        }
+    ],
+    "aliases": [
+
+    ],
+    "minimum-stability": "stable",
+    "stability-flags": [
+
+    ],
+    "platform": {
+        "php": ">=5.3"
+    },
+    "platform-dev": [
+
+    ]
+}
diff --git a/core/vendor/sdboyer/gliph/phpunit.xml.dist b/core/vendor/sdboyer/gliph/phpunit.xml.dist
new file mode 100644
index 0000000..b847773
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/phpunit.xml.dist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/bootstrap.php" colors="true">
+    <testsuites>
+        <testsuite name="PHPUnit">
+          <directory>tests/</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist addUncoveredFilesFromWhitelist="true">
+          <directory>src/Gliph</directory>
+            <exclude>
+                <file>src/Gliph/Visitor/DepthFirstNoOpVisitor.php</file>
+            </exclude>
+        </whitelist>
+    </filter>
+
+    <logging>
+        <log
+            type="coverage-html"
+            target="build/coverage"
+            charset="UTF-8"
+            yui="true"
+            highlight="true"
+            lowUpperBound="35"
+            highLowerBound="70"
+            showUncoveredFiles="true"
+            />
+          <log type="coverage-clover" target="build/logs/clover.xml"/>
+    </logging>
+</phpunit>
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Exception/InvalidVertexTypeException.php b/core/vendor/sdboyer/gliph/src/Gliph/Exception/InvalidVertexTypeException.php
new file mode 100644
index 0000000..4b33c81
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Exception/InvalidVertexTypeException.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Contains \Gliph\Exception\InvalidVertexTypeException.
+ */
+
+namespace Gliph\Exception;
+
+/**
+ * Error thrown when attempting to add a vertex of an invalid type.
+ */
+class InvalidVertexTypeException extends \Exception {}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/AdjacencyGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/AdjacencyGraph.php
new file mode 100644
index 0000000..b06f9e9
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/AdjacencyGraph.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Gliph\Graph;
+
+use Gliph\Exception\InvalidVertexTypeException;
+
+abstract class AdjacencyGraph {
+
+    protected $vertices;
+
+    public function __construct() {
+        $this->vertices = new \SplObjectStorage();
+    }
+
+    public function addVertex($vertex) {
+        if (!is_object($vertex)) {
+            throw new InvalidVertexTypeException('Vertices must be objects; non-object provided.');
+        }
+
+        if (!$this->hasVertex($vertex)) {
+            $this->vertices[$vertex] = new \SplObjectStorage();
+        }
+    }
+
+    public function eachAdjacent($vertex, $callback) {
+        foreach ($this->vertices[$vertex] as $e) {
+            call_user_func($callback, $e);
+        }
+    }
+
+    public function eachVertex($callback) {
+        $this->fev(function ($v, $outgoing) use ($callback) {
+            call_user_func($callback, $v, $outgoing);
+        });
+    }
+
+    public function hasVertex($vertex) {
+        return $this->vertices->contains($vertex);
+    }
+
+    protected function fev($callback) {
+        foreach ($this->vertices as $vertex) {
+            $outgoing = $this->vertices->getInfo();
+            $callback($vertex, $outgoing);
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyGraph.php
new file mode 100644
index 0000000..81f0440
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyGraph.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Gliph\Graph;
+
+use Gliph\Tarjan;
+
+class DirectedAdjacencyGraph extends AdjacencyGraph {
+
+    public function addDirectedEdge($from, $to) {
+        if (!$this->hasVertex($from)) {
+            $this->addVertex(($from));
+        }
+
+        if (!$this->hasVertex($to)) {
+            $this->addVertex($to);
+        }
+
+        $this->vertices[$from]->attach($to);
+    }
+
+    public function removeVertex($vertex) {
+        if (!$this->hasVertex($vertex)) {
+            throw new \OutOfBoundsException('Vertex is not in the graph, it cannot be removed.', E_WARNING);
+        }
+
+        $this->eachVertex(function($v, $outgoing) use ($vertex) {
+            if ($outgoing->contains($vertex)) {
+                $outgoing->detach($vertex);
+            }
+        });
+        unset($this->vertices[$vertex]);
+    }
+
+    public function removeEdge($from, $to) {
+        $this->vertices[$from]->detach($to);
+    }
+
+    public function eachEdge($callback) {
+        $edges = array();
+        $this->fev(function ($from, $outgoing) use (&$edges) {
+            foreach ($outgoing as $to) {
+                $edges[] = array($from, $to);
+            }
+        });
+
+        foreach ($edges as $edge) {
+            call_user_func($callback, $edge);
+        }
+    }
+
+    /**
+     * Returns the transpose of this graph.
+     *
+     * A transpose is identical to the current graph, except that
+     * its edges have had their directionality reversed.
+     *
+     * Also sometimes known as the 'reverse' or 'converse'.
+     *
+     * @return \Gliph\Graph\DirectedAdjacencyGraph
+     */
+    public function transpose() {
+        $graph = new self();
+        $this->eachEdge(function($edge) use (&$graph) {
+            $graph->addDirectedEdge($edge[1], $edge[0]);
+        });
+
+        return $graph;
+    }
+
+    public function getCycles() {
+        $tarjan = new Tarjan();
+        $scc = $tarjan->getCycles($this);
+        return $scc->count() > 0 ? $scc : FALSE;
+    }
+}
+
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedAdjacencyGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedAdjacencyGraph.php
new file mode 100644
index 0000000..d9764e2
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedAdjacencyGraph.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Gliph\Graph;
+
+class UndirectedAdjacencyGraph extends AdjacencyGraph {
+
+    public function addEdge($from, $to) {
+        if (!$this->hasVertex($from)) {
+            $this->addVertex(($from));
+        }
+
+        if (!$this->hasVertex($to)) {
+            $this->addVertex($to);
+        }
+
+        $this->vertices[$from]->attach($to);
+        $this->vertices[$to]->attach($from);
+    }
+
+    public function removeVertex($vertex) {
+        if (!$this->hasVertex($vertex)) {
+            throw new \OutOfBoundsException('Vertex is not in the graph, it cannot be removed.', E_WARNING);
+        }
+
+        foreach ($this->vertices[$vertex] as $adjacent) {
+            $this->vertices[$adjacent]->detach($vertex);
+        }
+        unset($this->vertices[$vertex]);
+    }
+
+    public function removeEdge($from, $to) {
+        $this->vertices[$from]->detach($to);
+        $this->vertices[$to]->detach($from);
+    }
+
+    public function eachEdge($callback) {
+        $edges = array();
+        $complete = new \SplObjectStorage();
+        $this->fev(function ($a, $adjacent) use (&$edges, &$complete) {
+            foreach ($adjacent as $b) {
+                if (!$complete->contains($b)) {
+                    $edges[] = array($a, $b);
+                }
+            }
+            $complete->attach($a);
+        });
+
+        foreach ($edges as $edge) {
+            call_user_func($callback, $edge);
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Tarjan.php b/core/vendor/sdboyer/gliph/src/Gliph/Tarjan.php
new file mode 100644
index 0000000..9e37db9
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Tarjan.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Gliph;
+
+use Gliph\Graph\DirectedAdjacencyGraph;
+use Gliph\Util\HashMap;
+
+class Tarjan {
+    /**
+     * @var \SplObjectStorage;
+     */
+    public $vertexIndices;
+    public $vertexLowLimits;
+
+    /**
+     * @var \SplQueue
+     */
+    protected $scc;
+
+    public $stack;
+    protected $index;
+
+    /**
+     * @var DirectedAdjacencyGraph
+     */
+    protected $graph;
+
+    protected $storeNonCycles = FALSE;
+
+    public function getCycles(DirectedAdjacencyGraph $graph) {
+        $this->index = 0;
+        $this->scc = new \SplQueue();
+        $this->stack = array();
+
+        $this->graph = $graph;
+        if ($graph->getVertexTypes() == DirectedAdjacencyGraph::OBJECT_VERTICES) {
+            $this->vertexIndices = new \SplObjectStorage();
+            $this->vertexLowLimits = new \SplObjectStorage();
+        }
+        else {
+            $this->vertexIndices = new HashMap();
+            $this->vertexLowLimits = new HashMap();
+        }
+
+        $that = $this;
+        $graph->eachVertex(function($vertex) use (&$that, &$graph) {
+            if (!$that->vertexIndices->contains($vertex)) {
+                $that->strongconnect($vertex);
+            }
+        });
+
+        return $this->scc;
+    }
+
+    public function strongconnect($vertex) {
+        $this->vertexIndices[$vertex] = $this->index;
+        $this->vertexLowLimits[$vertex] = $this->index;
+        $this->index++;
+        $this->stack[] = $vertex;
+
+        $that = $this;
+        $this->graph->eachAdjacent($vertex, function($to) use (&$vertex, &$that) {
+            if (!$that->vertexIndices->contains($to)) {
+                $that->strongconnect($to);
+                $ll = min($that->vertexLowLimits[$vertex], $that->vertexLowLimits[$to]);
+                $that->vertexLowLimits[$vertex] = $ll;
+            }
+            // FIXME Tarjan dictates this search should be constant time. ruh roh.
+            else if (array_search($to, $that->stack, TRUE) !== FALSE) {
+                $min = min($that->vertexLowLimits[$vertex], $that->vertexIndices[$to]);
+                $that->vertexLowLimits[$vertex] = $min;
+            }
+        });
+
+        if ($this->vertexIndices[$vertex] == $this->vertexLowLimits[$vertex]) {
+            $component = new \SplQueue();
+            do {
+                $popped = array_pop($this->stack);
+                $component->push($popped);
+            } while ($vertex !== $popped);
+
+            if ($component->count() > 1 || $this->storeNonCycles) {
+                $this->scc->push($component);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php b/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php
new file mode 100644
index 0000000..3879924
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Gliph\Traversal;
+
+use Gliph\Graph\DirectedAdjacencyGraph;
+use Gliph\Visitor\DepthFirstVisitorInterface;
+
+class DepthFirst {
+
+    /**
+     * Perform a depth-first traversal on the provided graph.
+     *
+     * @param DirectedAdjacencyGraph $graph
+     *   The graph on which to perform the depth-first search.
+     * @param DepthFirstVisitorInterface $visitor
+     *   The visitor object to use during the traversal.
+     * @param mixed $start
+     *   A queue of vertices to ensure are visited. The traversal will deque
+     *   them in order and visit them.
+     *
+     * @throws \OutOfBoundsException
+     *   Thrown if an invalid $start parameter is provided.
+     */
+    public static function traverse(DirectedAdjacencyGraph $graph, DepthFirstVisitorInterface $visitor, $start = NULL) {
+        if ($start === NULL) {
+            $queue = self::find_sources($graph, $visitor);
+        }
+        else if ($start instanceof \SplDoublyLinkedList) {
+            $queue = $start;
+        }
+        else if (is_object($start)) {
+            $queue = new \SplDoublyLinkedList();
+            $queue->push($start);
+        }
+
+        if ($queue->isEmpty()) {
+            throw new \RuntimeException('No start vertex or vertices were provided, and no source vertices could be found in the provided graph.', E_WARNING);
+        }
+
+        $visiting = new \SplObjectStorage();
+        $visited = new \SplObjectStorage();
+
+        $visit = function($vertex) use ($graph, $visitor, &$visit, $visiting, $visited) {
+            if ($visiting->contains($vertex)) {
+                $visitor->onBackEdge($vertex, $visit);
+            }
+            else if (!$visited->contains($vertex)) {
+                $visiting->attach($vertex);
+
+                $visitor->onStartVertex($vertex, $visit);
+
+                $graph->eachAdjacent($vertex, function($to) use ($vertex, &$visit, $visitor) {
+                    $visitor->onExamineEdge($vertex, $to, $visit);
+                    $visit($to);
+                });
+
+                $visitor->onFinishVertex($vertex, $visit);
+
+                $visiting->detach($vertex);
+                $visited->attach($vertex);
+            }
+        };
+
+        while (!$queue->isEmpty()) {
+            $vertex = $queue->shift();
+            $visit($vertex);
+        }
+    }
+
+    /**
+     * Finds source vertices in a DirectedAdjacencyGraph, then enqueues them.
+     *
+     * @param DirectedAdjacencyGraph $graph
+     * @param DepthFirstVisitorInterface $visitor
+     *
+     * @return \SplQueue
+     */
+    public static function find_sources(DirectedAdjacencyGraph $graph, DepthFirstVisitorInterface $visitor) {
+        $incomings = new \SplObjectStorage();
+        $queue = new \SplQueue();
+
+        $graph->eachEdge(function ($edge) use (&$incomings) {
+            if (!isset($incomings[$edge[1]])) {
+                $incomings[$edge[1]] = new \SplObjectStorage();
+            }
+            $incomings[$edge[1]]->attach($edge[0]);
+        });
+
+        // Prime the queue with vertices that have no incoming edges.
+        $graph->eachVertex(function($vertex) use ($queue, $incomings, $visitor) {
+            if (!$incomings->contains($vertex)) {
+                $queue->push($vertex);
+                // TRUE second param indicates source vertex
+                $visitor->onInitializeVertex($vertex, TRUE, $queue);
+            }
+            else {
+                $visitor->onInitializeVertex($vertex, FALSE, $queue);
+            }
+        });
+
+        return $queue;
+    }
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php
new file mode 100644
index 0000000..4c409fa
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Gliph\Visitor;
+
+/**
+ * Basic depth-first visitor.
+ *
+ * This visitor records reachability data for each vertex and creates a
+ * topologically sorted list.
+ */
+class DepthFirstBasicVisitor implements DepthFirstVisitorInterface {
+
+    /**
+     * @var \SplObjectStorage
+     */
+    public $active;
+
+    /**
+     * @var \SplObjectStorage
+     */
+    protected $paths;
+
+    /**
+     * @var array
+     */
+    protected $tsl;
+
+    public function __construct() {
+        $this->active = new \SplObjectStorage();
+        $this->paths = new \SplObjectStorage();
+        $this->tsl = array();
+    }
+
+    public function onBackEdge($vertex, \Closure $visit) {
+        throw new \RuntimeException(sprintf('Cycle detected in provided graph.'));
+    }
+
+    public function onInitializeVertex($vertex, $source, \SplQueue $queue) {
+        $this->paths[$vertex] = array();
+    }
+
+    public function onStartVertex($vertex, \Closure $visit) {
+        $this->active->attach($vertex);
+        if (!isset($this->paths[$vertex])) {
+            $this->paths[$vertex] = array();
+        }
+    }
+
+    public function onExamineEdge($from, $to, \Closure $visit) {
+        foreach ($this->active as $vertex) {
+            // TODO this check makes this much less efficient - find a better algo
+            if (!in_array($to, $this->paths[$vertex])) {
+                $path = $this->paths[$vertex];
+                $path[] = $to;
+                $this->paths[$vertex] = $path;
+            }
+        }
+    }
+
+    public function onFinishVertex($vertex, \Closure $visit) {
+        $this->active->detach($vertex);
+        $this->tsl[] = $vertex;
+    }
+
+    /**
+     * Returns valid topological sort of the visited graph as an array.
+     *
+     * @return array
+     */
+    public function getTsl() {
+        return $this->tsl;
+    }
+
+    /**
+     * Returns a queue of all vertices reachable from the given vertex.
+     *
+     * This should only be called after the visitor has been used in a
+     * depth-first traversal.
+     *
+     * @param object $vertex
+     *   A vertex present in the graph for
+     *
+     * @return array
+     *
+     * @throws \OutOfRangeException
+     */
+    public function getReachable($vertex) {
+        if (!isset($this->paths[$vertex])) {
+            throw new \OutOfRangeException('Unknown vertex provided.');
+        }
+
+        return $this->paths[$vertex];
+    }
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstNoOpVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstNoOpVisitor.php
new file mode 100644
index 0000000..3f83d8a
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstNoOpVisitor.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Gliph\Visitor;
+
+/**
+ * A no-op visitor for depth first traversal algorithms.
+ */
+class DepthFirstNoOpVisitor implements DepthFirstVisitorInterface {
+    public function onInitializeVertex($vertex, $source, \SplQueue $queue) {}
+    public function onBackEdge($vertex, \Closure $visit) {}
+    public function onStartVertex($vertex, \Closure $visit) {}
+    public function onExamineEdge($from, $to, \Closure $visit) {}
+    public function onFinishVertex($vertex, \Closure $visit) {}
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstVisitorInterface.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstVisitorInterface.php
new file mode 100644
index 0000000..de9cd77
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstVisitorInterface.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Gliph\Visitor;
+
+interface DepthFirstVisitorInterface {
+    public function onInitializeVertex($vertex, $source, \SplQueue $queue);
+    public function onBackEdge($vertex, \Closure $visit);
+    public function onStartVertex($vertex, \Closure $visit);
+    public function onExamineEdge($from, $to, \Closure $visit);
+    public function onFinishVertex($vertex, \Closure $visit);
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyGraphTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyGraphTest.php
new file mode 100644
index 0000000..438ebbc
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyGraphTest.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Gliph\Graph;
+
+
+use Gliph\TestVertex;
+
+abstract class AdjacencyGraphTest extends \PHPUnit_Framework_TestCase {
+
+    protected $v = array();
+
+    /**
+     * @var AdjacencyGraph
+     */
+    protected $g;
+
+    /**
+     * Creates a set of vertices and an empty graph for testing.
+     */
+    public function setUp() {
+        $this->v = array(
+            'a' => new TestVertex('a'),
+            'b' => new TestVertex('b'),
+            'c' => new TestVertex('c'),
+            'd' => new TestVertex('d'),
+            'e' => new TestVertex('e'),
+            'f' => new TestVertex('f'),
+            'g' => new TestVertex('g'),
+        );
+    }
+
+    public function doCheckVerticesEqual($vertices, AdjacencyGraph $graph = NULL) {
+        $found = array();
+        $graph = is_null($graph) ? $this->g : $graph;
+
+        $graph->eachVertex(function ($vertex) use (&$found) {
+            $found[] = $vertex;
+        });
+
+        $this->assertEquals($vertices, $found);
+    }
+
+    public function doCheckVertexCount($count, AdjacencyGraph $graph = NULL) {
+        $found = array();
+        $graph = is_null($graph) ? $this->g : $graph;
+
+        $graph->eachVertex(function ($vertex) use (&$found) {
+            $found[] = $vertex;
+        });
+
+        $this->assertCount($count, $found);
+    }
+
+    /**
+     * Tests that an exception is thrown if a string vertex is provided.
+     *
+     * @expectedException \Gliph\Exception\InvalidVertexTypeException
+     */
+    public function testAddStringVertex() {
+        $this->g->addVertex('a');
+    }
+
+    /**
+     * Tests that an exception is thrown if an integer vertex is provided.
+     *
+     * @expectedException \Gliph\Exception\InvalidVertexTypeException
+     */
+    public function testAddIntegerVertex() {
+        $this->g->addVertex(1);
+    }
+
+    /**
+     * Tests that an exception is thrown if a float vertex is provided.
+     *
+     * @expectedException \Gliph\Exception\InvalidVertexTypeException
+     */
+    public function testAddFloatVertex() {
+        $this->g->addVertex((float) 1);
+    }
+
+    /**
+     * Tests that an exception is thrown if an array vertex is provided.
+     *
+     * @expectedException \Gliph\Exception\InvalidVertexTypeException
+     */
+    public function testAddArrayVertex() {
+        $this->g->addVertex(array());
+    }
+
+    /**
+     * Tests that an exception is thrown if a resource vertex is provided.
+     *
+     * @expectedException \Gliph\Exception\InvalidVertexTypeException
+     */
+    public function testAddResourceVertex() {
+        $this->g->addVertex(fopen(__FILE__, 'r'));
+    }
+
+    public function testAddVertex() {
+        $this->g->addVertex($this->v['a']);
+
+        $this->assertTrue($this->g->hasVertex($this->v['a']));
+        $this->doCheckVertexCount(1, $this->g);
+    }
+
+    public function testAddVertexTwice() {
+        // Adding a vertex twice should be a no-op.
+        $this->g->addVertex($this->v['a']);
+        $this->g->addVertex($this->v['a']);
+
+        $this->assertTrue($this->g->hasVertex($this->v['a']));
+        $this->doCheckVertexCount(1, $this->g);
+    }
+
+    /**
+     * @expectedException OutOfBoundsException
+     */
+    public function testRemoveNonexistentVertex() {
+        $this->g->removeVertex($this->v['a']);
+    }
+}
diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyGraphTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyGraphTest.php
new file mode 100644
index 0000000..c48c3c2
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyGraphTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Gliph\Graph;
+
+class DirectedAdjacencyGraphTest extends AdjacencyGraphTest {
+
+    /**
+     * @var DirectedAdjacencyGraph
+     */
+    protected $g;
+
+    public function setUp() {
+        parent::setUp();
+        $this->g = new DirectedAdjacencyGraph();
+    }
+
+
+    public function testAddDirectedEdge() {
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+
+        $this->doCheckVerticesEqual(array($this->v['a'], $this->v['b']), $this->g);
+    }
+
+    public function testRemoveVertex() {
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+        $this->doCheckVertexCount(2);
+
+        $this->g->removeVertex($this->v['b']);
+        $this->doCheckVertexCount(1);
+
+        // Ensure that b was correctly removed from a's outgoing edges
+        $found = array();
+        $this->g->eachAdjacent($this->v['a'], function($to) use (&$found) {
+            $found[] = $to;
+        });
+
+        $this->assertEquals(array(), $found);
+    }
+
+
+    public function testRemoveEdge() {
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+        $this->doCheckVerticesEqual(array($this->v['a'], $this->v['b']), $this->g);
+
+        $this->g->removeEdge($this->v['a'], $this->v['b']);
+        $this->doCheckVertexCount(2);
+
+        $this->assertTrue($this->g->hasVertex($this->v['a']));
+        $this->assertTrue($this->g->hasVertex($this->v['b']));
+    }
+
+    public function testEachAdjacent() {
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+        $this->g->addDirectedEdge($this->v['a'], $this->v['c']);
+
+        $found = array();
+        $this->g->eachAdjacent($this->v['a'], function($to) use (&$found) {
+            $found[] = $to;
+        });
+
+        $this->assertEquals(array($this->v['b'], $this->v['c']), $found);
+    }
+
+    public function testEachEdge() {
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+        $this->g->addDirectedEdge($this->v['a'], $this->v['c']);
+
+        $found = array();
+        $this->g->eachEdge(function($edge) use (&$found) {
+            $found[] = $edge;
+        });
+
+        $this->assertCount(2, $found);
+        $this->assertEquals(array($this->v['a'], $this->v['b']), $found[0]);
+        $this->assertEquals(array($this->v['a'], $this->v['c']), $found[1]);
+    }
+
+    public function testTranspose() {
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+        $this->g->addDirectedEdge($this->v['a'], $this->v['c']);
+
+        $transpose = $this->g->transpose();
+
+        $this->doCheckVertexCount(3, $transpose);
+        $this->doCheckVerticesEqual(array($this->v['b'], $this->v['a'], $this->v['c']), $transpose);
+    }
+}
diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyGraphTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyGraphTest.php
new file mode 100644
index 0000000..7aacca4
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyGraphTest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Gliph\Graph;
+
+
+class UndirectedAdjacencyGraphTest extends AdjacencyGraphTest {
+
+    /**
+     * @var UndirectedAdjacencyGraph
+     */
+    protected $g;
+
+    /**
+     * Creates a set of vertices and an empty graph for testing.
+     */
+    public function setUp() {
+        parent::setUp();
+        $this->g = new UndirectedAdjacencyGraph();
+    }
+
+    public function testAddUndirectedEdge() {
+        $this->g->addEdge($this->v['a'], $this->v['b']);
+
+        $this->doCheckVerticesEqual(array($this->v['a'], $this->v['b']));
+    }
+
+    public function testRemoveVertex() {
+        $this->g->addEdge($this->v['a'], $this->v['b']);
+
+        $this->g->removeVertex(($this->v['a']));
+        $this->doCheckVertexCount(1);
+    }
+
+    public function testRemoveEdge() {
+        $this->g->addEdge($this->v['a'], $this->v['b']);
+        $this->g->addEdge($this->v['b'], $this->v['c']);
+
+        $this->g->removeEdge($this->v['b'], $this->v['c']);
+        $this->doCheckVertexCount(3);
+
+        $found = array();
+        $this->g->eachAdjacent($this->v['a'], function($adjacent) use (&$found) {
+            $found[] = $adjacent;
+        });
+
+        $this->assertEquals(array($this->v['b']), $found);
+    }
+
+    public function testEachEdge() {
+        $this->g->addEdge($this->v['a'], $this->v['b']);
+        $this->g->addEdge($this->v['b'], $this->v['c']);
+
+        $found = array();
+        $this->g->eachEdge(function ($edge) use (&$found) {
+            $found[] = $edge;
+        });
+
+        $this->assertCount(2, $found);
+        $this->assertEquals(array($this->v['a'], $this->v['b']), $found[0]);
+        $this->assertEquals(array($this->v['b'], $this->v['c']), $found[1]);
+
+        // Ensure bidirectionality of created edges
+        $found = array();
+        $this->g->eachAdjacent($this->v['b'], function($adjacent) use (&$found) {
+            $found[] = $adjacent;
+        });
+
+        $this->assertCount(2, $found);
+    }
+}
diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/TestVertex.php b/core/vendor/sdboyer/gliph/tests/Gliph/TestVertex.php
new file mode 100644
index 0000000..7a8f484
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/tests/Gliph/TestVertex.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Gliph;
+
+/**
+ * A class that acts as a vertex for more convenient use in tests.
+ */
+class TestVertex {
+
+    protected $name;
+
+    public function __construct($name) {
+        $this->name = $name;
+    }
+
+    public function __toString() {
+        return $this->name;
+    }
+}
\ No newline at end of file
diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php
new file mode 100644
index 0000000..47c384d
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Gliph\Traversal;
+
+
+use Gliph\Graph\DirectedAdjacencyGraph;
+use Gliph\TestVertex;
+use Gliph\Visitor\DepthFirstNoOpVisitor;
+
+class DepthFirstTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @var DirectedAdjacencyGraph
+     */
+    protected $g;
+    protected $v;
+
+    public function setUp() {
+        $this->g = new DirectedAdjacencyGraph();
+        $this->v = array(
+            'a' => new TestVertex('a'),
+            'b' => new TestVertex('b'),
+            'c' => new TestVertex('c'),
+            'd' => new TestVertex('d'),
+            'e' => new TestVertex('e'),
+            'f' => new TestVertex('f'),
+            'g' => new TestVertex('g'),
+        );
+
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+        $this->g->addDirectedEdge($this->v['b'], $this->v['c']);
+        $this->g->addDirectedEdge($this->v['a'], $this->v['c']);
+        $this->g->addDirectedEdge($this->v['b'], $this->v['d']);
+    }
+
+    public function testBasicAcyclicDepthFirstTraversal() {
+        $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor');
+        $visitor->expects($this->exactly(4))->method('onInitializeVertex');
+        $visitor->expects($this->exactly(0))->method('onBackEdge');
+        $visitor->expects($this->exactly(4))->method('onStartVertex');
+        $visitor->expects($this->exactly(4))->method('onExamineEdge');
+        $visitor->expects($this->exactly(4))->method('onFinishVertex');
+
+        DepthFirst::traverse($this->g, $visitor);
+    }
+
+    public function testDirectCycleDepthFirstTraversal() {
+        $this->g->addDirectedEdge($this->v['d'], $this->v['b']);
+
+        $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor');
+        $visitor->expects($this->exactly(1))->method('onBackEdge');
+
+        DepthFirst::traverse($this->g, $visitor);
+    }
+
+    public function testIndirectCycleDepthFirstTraversal() {
+        $this->g->addDirectedEdge($this->v['d'], $this->v['a']);
+
+        $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor');
+        $visitor->expects($this->exactly(1))->method('onBackEdge');
+
+        DepthFirst::traverse($this->g, $visitor, $this->v['a']);
+    }
+
+    /**
+     * @covers Gliph\Traversal\DepthFirst::traverse
+     * @expectedException RuntimeException
+     */
+    public function testExceptionOnEmptyTraversalQueue() {
+        // Create a cycle that ensures there are no source vertices
+        $this->g->addDirectedEdge($this->v['d'], $this->v['a']);
+        DepthFirst::traverse($this->g, new DepthFirstNoOpVisitor());
+    }
+
+    /**
+     * @covers Gliph\Traversal\DepthFirst::traverse
+     * @expectedException UnexpectedValueException
+     *
+     * This relies on the graph class to internally throw an exception
+     * when in attempt is made to visit a vertex that is not in the graph.
+     */
+    public function testProvideQueueAsStartPoint() {
+        $queue = new \SplQueue();
+        $queue->push($this->v['a']);
+        $queue->push($this->v['e']);
+        DepthFirst::traverse($this->g, new DepthFirstNoOpVisitor(), $queue);
+    }
+}
diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php
new file mode 100644
index 0000000..00f6a1d
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Gliph\Visitor;
+
+use Gliph\Graph\DirectedAdjacencyGraph;
+use Gliph\TestVertex;
+use Gliph\Traversal\DepthFirst;
+
+class DepthFirstBasicVisitorTest extends \PHPUnit_Framework_TestCase {
+
+    protected $v;
+
+    /**
+     * @var DepthFirstBasicVisitor
+     */
+    protected $vis;
+
+    /**
+     * @var DirectedAdjacencyGraph
+     */
+    protected $g;
+
+    public function setUp() {
+        $this->v = array(
+            'a' => new TestVertex('a'),
+            'b' => new TestVertex('b'),
+            'c' => new TestVertex('c'),
+            'd' => new TestVertex('d'),
+            'e' => new TestVertex('e'),
+            'f' => new TestVertex('f'),
+            'g' => new TestVertex('g'),
+        );
+
+        $this->g = new DirectedAdjacencyGraph();
+        $this->vis = new DepthFirstBasicVisitor();
+
+        $this->g->addDirectedEdge($this->v['a'], $this->v['b']);
+        $this->g->addDirectedEdge($this->v['b'], $this->v['c']);
+        $this->g->addDirectedEdge($this->v['a'], $this->v['c']);
+        $this->g->addDirectedEdge($this->v['b'], $this->v['d']);
+    }
+
+    /**
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::__construct
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::onInitializeVertex
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::onStartVertex
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::onExamineEdge
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::onFinishVertex
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::getReachable
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::getTsl
+     */
+    public function testTraversalWithStartPoint() {
+        DepthFirst::traverse($this->g, $this->vis, $this->v['a']);
+        $this->assertCount(3, $this->vis->getReachable($this->v['a']));
+        $this->assertCount(2, $this->vis->getReachable($this->v['b']));
+        $this->assertCount(0, $this->vis->getReachable($this->v['c']));
+        $this->assertCount(0, $this->vis->getReachable($this->v['d']));
+
+        // Not the greatest test since we're implicitly locking in to one of
+        // two valid TSL solutions - but that's linked to the determinism in
+        // the ordering of how the graph class stores vertices, which is a
+        // much bigger problem than can be solved right here. So, good enough.
+        $this->assertEquals(array($this->v['c'], $this->v['d'], $this->v['b'], $this->v['a']), $this->vis->getTsl());
+    }
+
+    /**
+     * @expectedException RuntimeException
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::onBackEdge
+     * @covers Gliph\Visitor\DepthFirstBasicVisitor::onInitializeVertex
+     */
+    public function testErrorOnCycle() {
+        $this->g->addDirectedEdge($this->v['d'], $this->v['b']);
+        DepthFirst::traverse($this->g, $this->vis);
+    }
+
+    /**
+     * @expectedException OutOfRangeException
+     * @covers
+     */
+    public function testReachableExceptionOnUnknownVertex() {
+        DepthFirst::traverse($this->g, $this->vis, $this->v['a']);
+        $this->vis->getReachable($this->v['e']);
+    }
+}
diff --git a/core/vendor/sdboyer/gliph/tests/bootstrap.php b/core/vendor/sdboyer/gliph/tests/bootstrap.php
new file mode 100644
index 0000000..1324983
--- /dev/null
+++ b/core/vendor/sdboyer/gliph/tests/bootstrap.php
@@ -0,0 +1,4 @@
+<?php
+
+$loader = require_once __DIR__ . "/../vendor/autoload.php";
+$loader->add('Gliph\\', __DIR__);
\ No newline at end of file
