[sessionmgr][refactor] Use StoryModel/Systems for StoryVisibilityState.

Also:
* Use pass-through NoopStoryModelStorage, which will be in use
until we implement a ledger-backed system and after that whenever we
don't want a particular story in the ledger.
* Plumb everything into StoryControllerImpl and StoryProviderImpl.
* Simplify StoryProviderImpl.NotifyStoryStateChange()

In this change you can see how trivial policy logic (when a Module
requests to change the story visibility state, should we honor it?) is
broken out into its own system and unit-tested in isolation of other
classes.

TEST=story_runtime_unittest,story_visibility_system_unittest,integration tests

MF-89 #comment [sessionmgr][refactor] Use StoryModel for StoryVisibilityState.
MF-106 #comment [sessionmgr][refactor] Use StoryModel for StoryVisibilityState.
MF-106 #done

Change-Id: I8263288cb442ee27a7e3a297947a4ef4399a1cf8
diff --git a/bin/sessionmgr/story/BUILD.gn b/bin/sessionmgr/story/BUILD.gn
new file mode 100644
index 0000000..ac799f4
--- /dev/null
+++ b/bin/sessionmgr/story/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//peridot/build/executable_package.gni")
+import("//peridot/build/tests_package.gni")
+
+source_set("system") {
+  sources = [
+    "system.cc",
+    "system.h",
+  ]
+
+  deps = [
+    "//garnet/public/lib/fxl",
+  ]
+}
diff --git a/bin/sessionmgr/story/system.cc b/bin/sessionmgr/story/system.cc
new file mode 100644
index 0000000..e740e48
--- /dev/null
+++ b/bin/sessionmgr/story/system.cc
@@ -0,0 +1,11 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "peridot/bin/sessionmgr/story/system.h"
+
+namespace modular {
+
+System::~System() {}
+
+}  // namespace modular
diff --git a/bin/sessionmgr/story/system.h b/bin/sessionmgr/story/system.h
new file mode 100644
index 0000000..371c8e9
--- /dev/null
+++ b/bin/sessionmgr/story/system.h
@@ -0,0 +1,27 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PERIDOT_BIN_SESSIONMGR_STORY_SYSTEM_H_
+#define PERIDOT_BIN_SESSIONMGR_STORY_SYSTEM_H_
+
+#include <lib/fxl/macros.h>
+
+namespace modular {
+
+// Common interface for all story runtime systems.
+class System {
+ public:
+  System() {}
+  virtual ~System();
+
+  // TODO(thatguy): Add lifecycle methods Initialize() and Teardown().
+  // TODO(thatguy): Add Inspect API hooks for debug output.
+
+ private:
+  FXL_DISALLOW_COPY_AND_ASSIGN(System);
+};
+
+}  // namespace modular
+
+#endif  // PERIDOT_BIN_SESSIONMGR_STORY_SYSTEM_H_
diff --git a/bin/sessionmgr/story/systems/BUILD.gn b/bin/sessionmgr/story/systems/BUILD.gn
new file mode 100644
index 0000000..c128adf
--- /dev/null
+++ b/bin/sessionmgr/story/systems/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//peridot/build/tests_package.gni")
+
+hermetic_tests_package("story_systems_unittests") {
+  deps = [
+    ":story_visibility_system_unittest",
+  ]
+}
+
+source_set("story_visibility_system") {
+  sources = [
+    "story_visibility_system.cc",
+    "story_visibility_system.h",
+  ]
+
+  public_deps = [
+    "//peridot/public/fidl/fuchsia.modular",
+  ]
+
+  deps = [
+    "//peridot/public/fidl/fuchsia.modular.storymodel",
+    "//peridot/bin/sessionmgr/story:system",
+    "//peridot/bin/sessionmgr/story/model",
+  ]
+}
+
+executable("story_visibility_system_unittest") {
+  testonly = true
+
+  sources = [
+    "story_visibility_system_unittest.cc",
+  ]
+
+  deps = [
+    ":story_visibility_system",
+    "//peridot/bin/sessionmgr/story/model",
+    "//peridot/public/lib/fostr/fidl/fuchsia.modular.storymodel",
+    "//third_party/googletest:gtest",
+    "//third_party/googletest:gmock",
+    "//third_party/googletest:gtest_main",
+  ]
+}
diff --git a/bin/sessionmgr/story/systems/meta/story_visibility_system_unittest.cmx b/bin/sessionmgr/story/systems/meta/story_visibility_system_unittest.cmx
new file mode 100644
index 0000000..7a3c73c
--- /dev/null
+++ b/bin/sessionmgr/story/systems/meta/story_visibility_system_unittest.cmx
@@ -0,0 +1,8 @@
+{
+  "program": {
+    "binary": "test/story_visibility_system_unittest"
+  },
+  "sandbox": {
+    "services": []
+  }
+}
diff --git a/bin/sessionmgr/story/systems/story_visibility_system.cc b/bin/sessionmgr/story/systems/story_visibility_system.cc
new file mode 100644
index 0000000..42f3d7d
--- /dev/null
+++ b/bin/sessionmgr/story/systems/story_visibility_system.cc
@@ -0,0 +1,29 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/modular/storymodel/cpp/fidl.h>
+
+#include "peridot/bin/sessionmgr/story/systems/story_visibility_system.h"
+
+#include "peridot/bin/sessionmgr/story/model/story_mutator.h"
+
+namespace modular {
+
+using fuchsia::modular::StoryVisibilityState;
+using fuchsia::modular::storymodel::StoryModel;
+using fuchsia::modular::storymodel::StoryModelMutation;
+
+StoryVisibilitySystem::StoryVisibilitySystem(
+    std::unique_ptr<StoryMutator> mutator)
+    : mutator_(std::move(mutator)) {}
+
+StoryVisibilitySystem::~StoryVisibilitySystem() {}
+
+void StoryVisibilitySystem::RequestStoryVisibilityStateChange(
+    const StoryVisibilityState visibility_state) {
+  // Ignore any error resulting from this operation.
+  mutator_->set_visibility_state(visibility_state);
+}
+
+}  // namespace modular
diff --git a/bin/sessionmgr/story/systems/story_visibility_system.h b/bin/sessionmgr/story/systems/story_visibility_system.h
new file mode 100644
index 0000000..5b33c29
--- /dev/null
+++ b/bin/sessionmgr/story/systems/story_visibility_system.h
@@ -0,0 +1,36 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PERIDOT_BIN_SESSIONMGR_STORY_SYSTEMS_STORY_VISIBILITY_SYSTEM_H_
+#define PERIDOT_BIN_SESSIONMGR_STORY_SYSTEMS_STORY_VISIBILITY_SYSTEM_H_
+
+#include <fuchsia/modular/cpp/fidl.h>
+#include <memory>
+
+#include "peridot/bin/sessionmgr/story/system.h"
+
+namespace modular {
+
+class StoryMutator;
+
+// This sytem implements the policy for translating requests from Modules to
+// change the visibility state of the story into the final visibility state of
+// the story.
+//
+// The current policy is "allow all".
+class StoryVisibilitySystem : public System {
+ public:
+  StoryVisibilitySystem(std::unique_ptr<StoryMutator> model_mutator);
+  ~StoryVisibilitySystem() override;
+
+  void RequestStoryVisibilityStateChange(
+      const fuchsia::modular::StoryVisibilityState visibility_state);
+
+ private:
+  std::unique_ptr<StoryMutator> mutator_;
+};
+
+}  // namespace modular
+
+#endif  // PERIDOT_BIN_SESSIONMGR_STORY_SYSTEMS_STORY_VISIBILITY_SYSTEM_H_
diff --git a/bin/sessionmgr/story/systems/story_visibility_system_unittest.cc b/bin/sessionmgr/story/systems/story_visibility_system_unittest.cc
new file mode 100644
index 0000000..d0ae83f
--- /dev/null
+++ b/bin/sessionmgr/story/systems/story_visibility_system_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/modular/storymodel/cpp/fidl.h>
+
+#include "gmock/gmock.h"  // For EXPECT_THAT and matchers.
+#include "gtest/gtest.h"
+#include "lib/fostr/fidl/fuchsia/modular/storymodel/formatting.h"
+#include "peridot/bin/sessionmgr/story/model/story_mutator.h"
+#include "peridot/bin/sessionmgr/story/systems/story_visibility_system.h"
+
+using fuchsia::modular::storymodel::StoryModel;
+using fuchsia::modular::storymodel::StoryModelMutation;
+
+namespace modular {
+namespace {
+
+// TODO(thatguy): Move these matchers into a shared file.
+
+// |arg| is a StoryModelMutation |expected| is a StoryVisibilitystate.
+MATCHER_P(IsSetVisibilityMutation, expected, "") {
+  *result_listener << "is set_visibility_state { "
+                   << fidl::ToUnderlying(expected) << "}";
+  if (!arg.is_set_visibility_state())
+    return false;
+  return expected == arg.set_visibility_state();
+}
+
+// TODO(thatguy): Move this test mutator into a shared file.
+class TestMutator : public StoryMutator {
+ public:
+  fit::consumer<> ExecuteInternal(
+      std::vector<StoryModelMutation> commands) override {
+    fit::bridge<> bridge;
+    ExecuteCall call{.completer = std::move(bridge.completer),
+                     .commands = std::move(commands)};
+    execute_calls.push_back(std::move(call));
+    return std::move(bridge.consumer);
+  }
+
+  struct ExecuteCall {
+    fit::completer<> completer;
+    std::vector<StoryModelMutation> commands;
+  };
+  std::vector<ExecuteCall> execute_calls;
+};
+
+class StoryVisibilitySystemTest : public ::testing::Test {
+ protected:
+  StoryVisibilitySystemTest() {
+    auto mutator = std::make_unique<TestMutator>();
+    mutator_ = mutator.get();
+    system_ = std::make_unique<StoryVisibilitySystem>(std::move(mutator));
+  }
+
+  std::unique_ptr<StoryVisibilitySystem> system_;
+  TestMutator* mutator_;
+};
+
+TEST_F(StoryVisibilitySystemTest, All) {
+  system_->RequestStoryVisibilityStateChange(
+      fuchsia::modular::StoryVisibilityState::IMMERSIVE);
+
+  EXPECT_EQ(1lu, mutator_->execute_calls.size());
+  EXPECT_THAT(mutator_->execute_calls[0].commands,
+              testing::ElementsAre(IsSetVisibilityMutation(
+                  fuchsia::modular::StoryVisibilityState::IMMERSIVE)));
+}
+
+}  // namespace
+}  // namespace modular
diff --git a/bin/sessionmgr/story_runner/BUILD.gn b/bin/sessionmgr/story_runner/BUILD.gn
index 79eb765..8b6d296 100644
--- a/bin/sessionmgr/story_runner/BUILD.gn
+++ b/bin/sessionmgr/story_runner/BUILD.gn
@@ -12,6 +12,8 @@
   ]
 }
 
+# NOTE: We are in the process of deconstructing story_runner into its
+# constituent components. Please see MF-85.
 source_set("story_runner") {
   sources = [
     "link_impl.cc",
@@ -51,6 +53,8 @@
     "//peridot/bin/sessionmgr/puppet_master/command_runners/operation_calls:get_types_from_entity_call",
     "//peridot/bin/sessionmgr/puppet_master/command_runners/operation_calls:initialize_chain_call",
     "//peridot/bin/sessionmgr/storage:constants_and_utils",
+    "//peridot/bin/sessionmgr/story/model",
+    "//peridot/bin/sessionmgr/story/systems:story_visibility_system",
     "//peridot/lib/common:names",
     "//peridot/lib/common:teardown",
     "//peridot/lib/fidl:app_client",
@@ -76,6 +80,10 @@
 
   deps = [
     "//peridot/bin/sessionmgr/storage",
+    "//peridot/bin/sessionmgr/story:system",
+    "//peridot/bin/sessionmgr/story/model",
+    "//peridot/bin/sessionmgr/story/model:story_model_owner",
+    "//peridot/bin/sessionmgr/story/model:noop_story_model_storage",
     "//peridot/lib/fidl:clone",
     "//peridot/public/fidl/fuchsia.modular",
     "//zircon/public/lib/async-loop-cpp",
diff --git a/bin/sessionmgr/story_runner/module_context_impl.cc b/bin/sessionmgr/story_runner/module_context_impl.cc
index 8804f54..7e93b54 100644
--- a/bin/sessionmgr/story_runner/module_context_impl.cc
+++ b/bin/sessionmgr/story_runner/module_context_impl.cc
@@ -12,6 +12,7 @@
 
 #include "peridot/bin/sessionmgr/storage/constants_and_utils.h"
 #include "peridot/bin/sessionmgr/story_runner/story_controller_impl.h"
+#include "peridot/bin/sessionmgr/story/systems/story_visibility_system.h"
 #include "peridot/lib/fidl/clone.h"
 
 namespace modular {
@@ -23,6 +24,7 @@
         service_provider_request)
     : module_data_(module_data),
       story_controller_impl_(info.story_controller_impl),
+      story_visibility_system_(info.story_visibility_system),
       component_context_impl_(info.component_context_info,
                               EncodeModuleComponentNamespace(
                                   info.story_controller_impl->GetStoryId()),
@@ -130,7 +132,7 @@
 
 void ModuleContextImpl::RequestStoryVisibilityState(
     fuchsia::modular::StoryVisibilityState visibility_state) {
-  story_controller_impl_->HandleStoryVisibilityStateRequest(visibility_state);
+  story_visibility_system_->RequestStoryVisibilityStateChange(visibility_state);
 }
 
 void ModuleContextImpl::StartOngoingActivity(
diff --git a/bin/sessionmgr/story_runner/module_context_impl.h b/bin/sessionmgr/story_runner/module_context_impl.h
index 858abec..42f6839 100644
--- a/bin/sessionmgr/story_runner/module_context_impl.h
+++ b/bin/sessionmgr/story_runner/module_context_impl.h
@@ -21,11 +21,13 @@
 namespace modular {
 
 class StoryControllerImpl;
+class StoryVisibilitySystem;
 
 // The dependencies of ModuleContextImpl common to all instances.
 struct ModuleContextInfo {
   const ComponentContextInfo component_context_info;
   StoryControllerImpl* const story_controller_impl;
+  StoryVisibilitySystem* const story_visibility_system;
   fuchsia::modular::UserIntelligenceProvider* const user_intelligence_provider;
 };
 
@@ -115,6 +117,8 @@
   // lives.
   StoryControllerImpl* const story_controller_impl_;
 
+  StoryVisibilitySystem* const story_visibility_system_;  // Not owned.
+
   ComponentContextImpl component_context_impl_;
 
   fuchsia::modular::UserIntelligenceProvider* const
diff --git a/bin/sessionmgr/story_runner/story_controller_impl.cc b/bin/sessionmgr/story_runner/story_controller_impl.cc
index d14876d..426297d 100644
--- a/bin/sessionmgr/story_runner/story_controller_impl.cc
+++ b/bin/sessionmgr/story_runner/story_controller_impl.cc
@@ -11,6 +11,7 @@
 #include <fuchsia/ledger/cpp/fidl.h>
 #include <fuchsia/modular/cpp/fidl.h>
 #include <fuchsia/modular/internal/cpp/fidl.h>
+#include <fuchsia/modular/storymodel/cpp/fidl.h>
 #include <fuchsia/sys/cpp/fidl.h>
 #include <fuchsia/ui/policy/cpp/fidl.h>
 #include <fuchsia/ui/viewsv1/cpp/fidl.h>
@@ -39,6 +40,7 @@
 #include "peridot/bin/sessionmgr/puppet_master/command_runners/operation_calls/initialize_chain_call.h"
 #include "peridot/bin/sessionmgr/storage/constants_and_utils.h"
 #include "peridot/bin/sessionmgr/storage/story_storage.h"
+#include "peridot/bin/sessionmgr/story/model/story_observer.h"
 #include "peridot/bin/sessionmgr/story_runner/link_impl.h"
 #include "peridot/bin/sessionmgr/story_runner/module_context_impl.h"
 #include "peridot/bin/sessionmgr/story_runner/module_controller_impl.h"
@@ -221,6 +223,7 @@
     ModuleContextInfo module_context_info = {
         story_controller_impl_->story_provider_impl_->component_context_info(),
         story_controller_impl_,
+        story_controller_impl_->story_visibility_system_,
         story_controller_impl_->story_provider_impl_
             ->user_intelligence_provider()};
 
@@ -1184,11 +1187,13 @@
 StoryControllerImpl::StoryControllerImpl(
     fidl::StringPtr story_id, SessionStorage* const session_storage,
     StoryStorage* const story_storage,
+    StoryVisibilitySystem* const story_visibility_system,
     StoryProviderImpl* const story_provider_impl)
     : story_id_(story_id),
       story_provider_impl_(story_provider_impl),
       session_storage_(session_storage),
       story_storage_(story_storage),
+      story_visibility_system_(story_visibility_system),
       story_shell_context_impl_{story_id, story_provider_impl, this},
       weak_factory_(this) {
   auto story_scope = fuchsia::modular::StoryScope::New();
@@ -1225,11 +1230,6 @@
   return state_;
 }
 
-fuchsia::modular::StoryVisibilityState
-StoryControllerImpl::GetStoryVisibilityState() const {
-  return visibility_state_;
-}
-
 fidl::VectorPtr<fuchsia::modular::OngoingActivityType>
 StoryControllerImpl::GetOngoingActivities() {
   fidl::VectorPtr<fuchsia::modular::OngoingActivityType> ongoing_activities;
@@ -1613,8 +1613,7 @@
     (*i)->OnStateChange(state_);
   }
 
-  story_provider_impl_->NotifyStoryStateChange(story_id_, state_,
-                                               visibility_state_);
+  story_provider_impl_->NotifyStoryStateChange(story_id_);
 }
 
 bool StoryControllerImpl::IsExternalModule(
@@ -1664,19 +1663,6 @@
       new StopModuleAndStoryIfEmptyCall(this, module_path, [] {}));
 }
 
-void StoryControllerImpl::HandleStoryVisibilityStateRequest(
-    const fuchsia::modular::StoryVisibilityState visibility_state) {
-  if (visibility_state == visibility_state_) {
-    // no-op.
-    return;
-  }
-
-  visibility_state_ = visibility_state;
-
-  story_provider_impl_->NotifyStoryStateChange(story_id_, state_,
-                                               visibility_state_);
-}
-
 void StoryControllerImpl::InitStoryEnvironment() {
   FXL_DCHECK(!story_environment_)
       << "Story scope already running for story_id = " << story_id_;
diff --git a/bin/sessionmgr/story_runner/story_controller_impl.h b/bin/sessionmgr/story_runner/story_controller_impl.h
index e4f3b26..5c9210c 100644
--- a/bin/sessionmgr/story_runner/story_controller_impl.h
+++ b/bin/sessionmgr/story_runner/story_controller_impl.h
@@ -42,10 +42,12 @@
 
 namespace modular {
 
-class ModuleControllerImpl;
 class ModuleContextImpl;
+class ModuleControllerImpl;
+class StoryModelMutator;
 class StoryProviderImpl;
 class StoryStorage;
+class StoryVisibilitySystem;
 
 // The story runner, which holds all the links and runs all the modules as well
 // as the story shell. It also implements the StoryController service to give
@@ -54,6 +56,7 @@
  public:
   StoryControllerImpl(fidl::StringPtr story_id, SessionStorage* session_storage,
                       StoryStorage* story_storage,
+                      StoryVisibilitySystem* story_visibility_system,
                       StoryProviderImpl* story_provider_impl);
   ~StoryControllerImpl() override;
 
@@ -68,9 +71,6 @@
   fuchsia::modular::StoryState GetStoryState() const;
 
   // Called by StoryProviderImpl.
-  fuchsia::modular::StoryVisibilityState GetStoryVisibilityState() const;
-
-  // Called by StoryProviderImpl.
   //
   // Returns a list of the ongoing activities in this story.
   fidl::VectorPtr<fuchsia::modular::OngoingActivityType> GetOngoingActivities();
@@ -144,10 +144,6 @@
       const fidl::VectorPtr<fidl::StringPtr>& module_path);
 
   // Called by ModuleContextImpl.
-  void HandleStoryVisibilityStateRequest(
-      const fuchsia::modular::StoryVisibilityState visibility_state);
-
-  // Called by ModuleContextImpl.
   void StartOngoingActivity(
       const fuchsia::modular::OngoingActivityType ongoing_activity_type,
       fidl::InterfaceRequest<fuchsia::modular::OngoingActivity> request);
@@ -236,15 +232,12 @@
   // kept in memory.
   fuchsia::modular::StoryState state_{fuchsia::modular::StoryState::STOPPED};
 
-  // This is the canonical source for a story's visibility state within user
-  // shell. This state is per device and only kept in memory.
-  fuchsia::modular::StoryVisibilityState visibility_state_{
-      fuchsia::modular::StoryVisibilityState::DEFAULT};
+  StoryProviderImpl* const story_provider_impl_;  // Not owned.
 
-  StoryProviderImpl* const story_provider_impl_;
+  SessionStorage* const session_storage_;  // Not owned.
+  StoryStorage* const story_storage_;  // Not owned.
 
-  SessionStorage* const session_storage_;
-  StoryStorage* const story_storage_;
+  StoryVisibilitySystem* const story_visibility_system_;  // Not owned.
 
   // The application environment (which abstracts a zx::job) in which the
   // modules within this story run. This environment is only valid (not null) if
diff --git a/bin/sessionmgr/story_runner/story_provider_impl.cc b/bin/sessionmgr/story_runner/story_provider_impl.cc
index 1bd1f88..c0aa96b 100644
--- a/bin/sessionmgr/story_runner/story_provider_impl.cc
+++ b/bin/sessionmgr/story_runner/story_provider_impl.cc
@@ -28,6 +28,7 @@
 #include "peridot/bin/sessionmgr/storage/constants_and_utils.h"
 #include "peridot/bin/sessionmgr/storage/session_storage.h"
 #include "peridot/bin/sessionmgr/storage/story_storage.h"
+#include "peridot/bin/sessionmgr/story/systems/story_visibility_system.h"
 #include "peridot/bin/sessionmgr/story_runner/link_impl.h"
 #include "peridot/bin/sessionmgr/story_runner/story_controller_impl.h"
 #include "peridot/lib/common/names.h"
@@ -101,8 +102,9 @@
   FXL_DISALLOW_COPY_AND_ASSIGN(StopStoryCall);
 };
 
-// Loads a StoryRuntimeContainer object so that the given story is ready to be
-// run.
+// Loads a StoryRuntimeContainer object and stores it in
+// |story_provider_impl.story_runtime_containers_| so that the story is ready
+// to be run.
 class StoryProviderImpl::LoadStoryRuntimeCall
     : public Operation<StoryRuntimeContainer*> {
  public:
@@ -117,14 +119,14 @@
 
  private:
   void Run() override {
-    FlowToken flow{this, &story_controller_container_};
+    FlowToken flow{this, &story_runtime_container_};
 
     // Use the existing controller, if possible.
     // This won't race against itself because it's managed by an operation
     // queue.
     auto i = story_provider_impl_->story_runtime_containers_.find(story_id_);
     if (i != story_provider_impl_->story_runtime_containers_.end()) {
-      story_controller_container_ = &i->second;
+      story_runtime_container_ = &i->second;
       return;
     }
 
@@ -146,17 +148,43 @@
         fxl::MakeCopyable(
             [this, story_data = std::move(story_data),
              flow](std::unique_ptr<StoryStorage> story_storage) mutable {
-              struct StoryRuntimeContainer container;
-              container.storage = std::move(story_storage);
+              struct StoryRuntimeContainer container {
+                .executor = std::make_unique<async::Executor>(
+                    async_get_default_dispatcher()),
+                .storage = std::move(story_storage),
+                .current_data = std::move(story_data),
+              };
+
+              container.model_owner = std::make_unique<StoryModelOwner>(
+                  story_id_, container.executor.get(),
+                  std::make_unique<NoopStoryModelStorage>());
+              container.model_observer = container.model_owner->NewObserver();
+
+              // Create systems that are part of this story.
+              auto story_visibility_system =
+                  std::make_unique<StoryVisibilitySystem>(
+                      container.model_owner->NewMutator());
+
               container.controller_impl = std::make_unique<StoryControllerImpl>(
                   story_id_, session_storage_, container.storage.get(),
-                  story_provider_impl_);
-              container.current_data = std::move(story_data);
+                  story_visibility_system.get(), story_provider_impl_);
               container.entity_provider = std::make_unique<StoryEntityProvider>(
                   container.storage.get());
+
+              // Hand ownership of systems over to |container|.
+              container.systems.push_back(std::move(story_visibility_system));
+
+              // Register a listener on the StoryModel so that we can signal
+              // our watchers when relevant data changes.
+              container.model_observer->RegisterListener(
+                  [id = story_id_, story_provider = story_provider_impl_](
+                      const fuchsia::modular::storymodel::StoryModel& model) {
+                    story_provider->NotifyStoryStateChange(id);
+                  });
+
               auto it = story_provider_impl_->story_runtime_containers_.emplace(
                   story_id_, std::move(container));
-              story_controller_container_ = &it.first->second;
+              story_runtime_container_ = &it.first->second;
             }));
   }
 
@@ -164,7 +192,8 @@
   SessionStorage* const session_storage_;         // not owned
   const fidl::StringPtr story_id_;
 
-  StoryRuntimeContainer* story_controller_container_ = nullptr;
+  // Return value.
+  StoryRuntimeContainer* story_runtime_container_ = nullptr;
 
   // Sub operations run in this queue.
   OperationQueue operation_queue_;
@@ -369,9 +398,10 @@
   auto watcher_ptr = watcher.Bind();
   for (const auto& item : story_runtime_containers_) {
     const auto& container = item.second;
-    watcher_ptr->OnChange(CloneStruct(container.current_data->story_info),
-                          container.controller_impl->GetStoryState(),
-                          container.controller_impl->GetStoryVisibilityState());
+    watcher_ptr->OnChange(
+        CloneStruct(container.current_data->story_info),
+        container.controller_impl->GetStoryState(),
+        *container.model_observer->model().visibility_state());
   }
   watchers_.AddInterfacePtr(std::move(watcher_ptr));
 }
@@ -495,9 +525,7 @@
   session_shell_->DetachView(std::move(view_id), std::move(done));
 }
 
-void StoryProviderImpl::NotifyStoryStateChange(
-    fidl::StringPtr story_id, const fuchsia::modular::StoryState story_state,
-    const fuchsia::modular::StoryVisibilityState story_visibility_state) {
+void StoryProviderImpl::NotifyStoryStateChange(fidl::StringPtr story_id) {
   auto it = story_runtime_containers_.find(story_id);
   if (it == story_runtime_containers_.end()) {
     // If this call arrives while DeleteStory() is in
@@ -505,8 +533,9 @@
     // from here.
     return;
   }
-  NotifyStoryWatchers(it->second.current_data.get(), story_state,
-                      story_visibility_state);
+  NotifyStoryWatchers(it->second.current_data.get(),
+                      it->second.controller_impl->GetStoryState(),
+                      *it->second.model_observer->model().visibility_state());
 }
 
 void StoryProviderImpl::NotifyStoryActivityChange(
@@ -599,7 +628,7 @@
   auto i = story_runtime_containers_.find(story_data.story_info.id);
   if (i != story_runtime_containers_.end()) {
     state = i->second.controller_impl->GetStoryState();
-    visibility_state = i->second.controller_impl->GetStoryVisibilityState();
+    visibility_state = *i->second.model_observer->model().visibility_state();
     i->second.current_data = CloneOptional(story_data);
   }
   NotifyStoryWatchers(&story_data, state, visibility_state);
@@ -672,8 +701,8 @@
                          callback = std::move(callback),
                          entity_request = std::move(entity_request)](
                             StoryEntityProvider* entity_provider) mutable {
-        // Once the entity provider for the given story is available, create the
-        // entity.
+        // Once the entity provider for the given story is available, create
+        // the entity.
         entity_provider->CreateEntity(
             type, std::move(data),
             fxl::MakeCopyable([this, entity_request = std::move(entity_request),
diff --git a/bin/sessionmgr/story_runner/story_provider_impl.h b/bin/sessionmgr/story_runner/story_provider_impl.h
index 42c1d0e..dcb535e 100644
--- a/bin/sessionmgr/story_runner/story_provider_impl.h
+++ b/bin/sessionmgr/story_runner/story_provider_impl.h
@@ -28,6 +28,9 @@
 #include "peridot/bin/sessionmgr/agent_runner/agent_runner.h"
 #include "peridot/bin/sessionmgr/component_context_impl.h"
 #include "peridot/bin/sessionmgr/message_queue/message_queue_manager.h"
+#include "peridot/bin/sessionmgr/story/system.h"
+#include "peridot/bin/sessionmgr/story/model/story_model_owner.h"
+#include "peridot/bin/sessionmgr/story/model/noop_story_model_storage.h"
 #include "peridot/bin/sessionmgr/story_runner/story_entity_provider.h"
 #include "peridot/lib/fidl/app_client.h"
 #include "peridot/lib/fidl/environment.h"
@@ -132,8 +135,8 @@
 
   // Called by StoryControllerImpl. Sends, using AttachView(), the view of the
   // story identified by |story_id| to the current session shell.
-  void AttachView(
-      fidl::StringPtr story_id, fuchsia::ui::viewsv1token::ViewOwnerPtr view_owner);
+  void AttachView(fidl::StringPtr story_id,
+                  fuchsia::ui::viewsv1token::ViewOwnerPtr view_owner);
 
   // Called by StoryControllerImpl. Notifies, using DetachView(), the current
   // session shell that the view of the story identified by |story_id| is about
@@ -141,9 +144,7 @@
   void DetachView(fidl::StringPtr story_id, std::function<void()> done);
 
   // Called by StoryControllerImpl.
-  void NotifyStoryStateChange(
-      fidl::StringPtr story_id, fuchsia::modular::StoryState story_state,
-      fuchsia::modular::StoryVisibilityState story_visibility_state);
+  void NotifyStoryStateChange(fidl::StringPtr story_id);
 
   // Called by StoryControllerImpl.
   void NotifyStoryActivityChange(
@@ -271,6 +272,28 @@
   // Also keeps a cached version of the StoryData for every story so it does
   // not have to be loaded from disk when querying about this story.
   struct StoryRuntimeContainer {
+    // The executor on which asynchronous tasks are scheduled for this story.
+    //
+    // TODO(thatguy): Migrate all operations under |controller_impl| to use
+    // fit::promise and |executor|. MF-117
+    // TODO(thatguy): Once fit::scope is complete, share one executor for the
+    // whole process and take advantage of fit::scope to auto-cancel tasks when
+    // |this| dies.
+    std::unique_ptr<fit::executor> executor;
+
+    // StoryRuntime itself contains a StoryModelOwner and manages systems with
+    // asynchronous initialization and teardown operations.
+    std::unique_ptr<StoryModelOwner> model_owner;
+
+    // For ease of memory management, we store all runtime systems in
+    // |systems|.
+    std::vector<std::unique_ptr<System>> systems;
+
+    // This allows us to observe changes to the StoryModel owned by |runtime|.
+    std::unique_ptr<StoryObserver> model_observer;
+
+    // NOTE: The following are transitioning to StoryModel and associated
+    // classes above, as outlined in MF-85.
     std::unique_ptr<StoryControllerImpl> controller_impl;
     std::unique_ptr<StoryStorage> storage;
     std::unique_ptr<StoryEntityProvider> entity_provider;
diff --git a/packages/tests/modular_unittests b/packages/tests/modular_unittests
index 1725f22..2b18fdb 100644
--- a/packages/tests/modular_unittests
+++ b/packages/tests/modular_unittests
@@ -1,13 +1,14 @@
 {
     "packages": [
-        "//peridot/bin/sessionmgr/agent_runner:agent_runner_unittests",
         "//peridot/bin/agents/clipboard:clipboard_unittests",
         "//peridot/bin/basemgr:basemgr_unittests",
+        "//peridot/bin/sessionctl:sessionctl_unittests",
+        "//peridot/bin/sessionmgr/agent_runner:agent_runner_unittests",
         "//peridot/bin/sessionmgr/entity_provider_runner:entity_provider_runner_unittests",
         "//peridot/bin/sessionmgr/story/model:story_model_unittests",
+        "//peridot/bin/sessionmgr/story/systems:story_systems_unittests",
         "//peridot/bin/sessionmgr/puppet_master:puppet_master_unittests",
-        "//peridot/bin/sessionmgr/story_runner:story_runner_unittests",
         "//peridot/bin/sessionmgr/storage:storage_unittests",
-        "//peridot/bin/sessionctl:sessionctl_unittests"
+        "//peridot/bin/sessionmgr/story_runner:story_runner_unittests"
     ]
 }