Luminescent Dreams

CV

January 01, 0001

Savanni D'Gerinel

512-947-0226
savanni@luminescent-dreams.com

Objective

I am passionate about bringing useful user-facing applications to the Linux desktop.

I seek out engineering leadership positions that involve developing sustainable processes, mentoring junior engineers, fostering collaboration between team members, and building a close relationship with both designers and users. My ideal position allows me to spend at least 30% of my time doing software development.

Interests

  • Application architecture
  • Native graphical applications
  • Photography
  • Cycling
  • Go

Public Repositories

Fitnesstrax A GTK-based application for tracking basic health information around weight and physical activity.

On the Grid A work in progress for a fully featured Go application. I am currently focused on game reviews, allowing the user to easily load game records and traverse both the mainline of the game and any annotations.

Lantern and Lantern Controller A pair of applications for an LED “lantern” with multiple color patterns, controllable via packet radio. I used an Adafruit Feather with an onboard RFM69HCW packet radio for both the lantern and the controller.


Skills

  • Project management, especially in 2 week sprint cycles, including iteration planning meetings, daily standups, and iteration retrospectives.
  • Test-driven Development, Object-oriented design, requirements gathering and project management, Agile methodologies.
  • Programming Languages
    • Rust: 6 years, web services and native GTK development on NixOS
    • Haskell: 12 years, using combinations of Servant, Scotty, Yesod, Happstack, Heist, Persistent, Esqueleto, and GTK.
    • Standard C: 20 years of experience.
    • Javascript, Typescript, Perl, Clojure, Ocaml, Purescript, Go, Python and Nix
  • Devops: Docker, AWS, Terraform, Ansible, Packer, Nix

Experience

1Password: June 2020 - current, Staff Developer

Since my start at 1Password, I have been leading projects for the multiplatform 1Password 8 application, with my focus being primarily on the desktop. My achievements include:

  • Launching 1Password 8 for Linux, Windows, and MacOS to general access
  • Leading development of Quick Access and Import
  • Joining an engineering team in crisis and helping to resolve blockers so that it could start flowing smoothly again
  • Deploying feature flags to our desktop, mobile, and browser extension applications
  • Maintaining our localization framework

My daily work involves project planning, code review, mentorship, and programming.

Cloud City Development: August 2017 - June 2020, Senior Developer

As a consultant here, I have a changing set of software development responsibilities that include network setup and management, developing and managing automatic upgrades for IoT hardware in the field, developing server software to receive and process data from hardware, and developing web applications.

  • Created policies and automation for replaceable infrastructure using Terraform, reducing operational error and improving service reliability for our client, Truveon. This infrastructure included the tools that allow the developers to deploy recent builds to both staging and production infrastructure without intervention from operations.
  • Built an automated software update system for embedded devices in the field, which maximized the safety of deploying a Python application to embedded devices on a potentially unreliable consumer-grade networks.
  • Led the React and D3 integration and developed the animated display for the innovative open source project TSOMI
  • Provided technical leadership and development for a Ruby on Rails project for Resolve to Save Lives. The application helps countries create a plan for improving their readiness to face public health threats, such as epidemics and radiological events.

CollegeVine: December 2016 - June 2017, Software Engineer

My responsibilities involved server and client-side development in the platform our consultants and clients used to coordinate their activities.

Curb: October 2015 - October 2016, Software Engineer

I was responsible for the entire energy monitoring platform infrastructure, including provisioning, load balancing, disaster recovery and performance.

  • Created policies and automation for replaceable infrastructure using Terraform, reducing operational error and improving service reliability.
  • Automated a zero-downtime software deployment using Ansible on a data ingestion cluster.
  • Built ingestion services capable of handling more than 100 megabits/s of metrics in near-realtime using off-the-shelf components and purpose-specific code developed primarily in Haskell and Python.
  • Build a stand-alone OAuth 2.0 server.

Powerhouse Management: June 2014 - August 2015, Software Engineer

I was the lead developer for add-on service offerings for VyprVPN. I lead the design, project management, and development of the VyprVPN for Business product, which provides a turnkey VPN solution for employees to securely connect to corporate intranets.

  • Coached developers on Python programming with Flask, object oriented design, and automated testing.
  • Developed the early interface for the software using Javascript.
  • Helped lead a collaborative design process yielding the system architecture and development plan for VyprVPN for Business.
  • Conducted agile project management to provide a clear view on how far the project had progressed and how much further it had to go.

EMR Technical Solutions: December 2013 - February 2014

I was the lead developer for a web-based medical billing application. I handled design, task setting, and implementation.

Giganews/Goldenfrog/Powerhouse Management: 2005 - 2013

My role shifted over the years between being the sole developer on several projects and a team contributor in others. I introduced test-driven development, began the practice of frequent code reviews, and set up the practice of morning checkin metings.

  • My work formed the backbone of the entire VyprVPN product, providing nearly all aspects of authentication, connection tracking, and VPN management.
  • Built the DMCA response infrastructure for VyprVPN to help the business comply with the common carrier protections provided by the DMCA.
  • Debugged and maintained the Giganews NNTP news servers, written in C.
  • Wrote a connection and download accounting server, which has been in continuous operation, processing thousands of commands per second, with no failures since its creation, including handling unavailability of its database servers while still responding to requests.
  • Core synchronization engine for the a Dropbox-like cloud storage product.

National Instruments: 2001 - 2005

I was the individual contributor to the driver layer that connected our software to our measurement hardware through USB and IEEE1394.

  • Maintenance of the legacy USB and IEEE1394 platform abstraction driver.
  • Development of a new API and communication model for future USB and IEEE1394 devices.
  • Primary tools involved were C++, Windows 2000 DDK

University of Oklahoma Health Sciences Center: 2000

  • Sole designer and developer of a web interface for recording and tracking structured lab notes on genetic sequences

Internships: 1997 - 1998

  • Hewlett-Packard — math sciences automated test engineer
  • Halliburton — system administrator
  • Landmark Graphics — software tester

Education

Bachelor of Science in Computer Science with a minor in Mathematics from the University of Oklahoma. Graduated with honors in December, 2000.

Dogwood Challenge, Week 1, Self-Portait

January 01, 0001

Take a picture that tells us who you are, without actually showing your face.

_DSC2092.jpg

For my self-portrait, I could think of nothing more appropriate than my work desk. Not precisely as I have it day-to-day, but not greatly rearranged, either. The distinction between who I am and what I do is pretty minimal, for better or for worse.

  • trans
  • code on the screen
  • wacom tablet nearby
  • camera parts
  • home-made crochet hand warmers in bi pride colors
  • I Voted!
  • mug of tea in the background

Error Handling in Haskell

January 01, 0001

The Kinds of Error Reports

  • Exceptions
  • Either
  • ErrorT or EitherT

Parsing a File

  • parseImage :: ByteString -> Either ParseError (Image PixelRGB8)
  • readFile :: FilePath -> IO ByteString
  • readImageFile fname = readFile fname »= return . parseImage :: FilePath -> IO (Either ParseError (Image PixelRGB8))

Exceptions

  • throw :: Exception e => e -> a
  • throwIO :: Exception e => e -> IO a
  • catch :: Exception e => IO a -> (e -> IO a) -> IO a
  • handle :: Exception e => (e -> IO a) -> IO a -> IO a
  • try :: Exception e => IO a -> IO (Either e a)
  • throwTo :: Exception e => ThreadId -> e -> IO ()
  • error :: String -> a

Exceptions

\footnotesize

readFileExc :: FilePath -> IO ByteString
readFileExc = handle silenceENoEnt . readFile
  where
    silenceENoEnt :: IOException -> IO ByteString
    silenceENoEnt exc | isDoesNotExistError exc = return empty
                      | otherwise = throw exc

readImageFileExc :: FilePath -> IO Image
readImageFileExc fn = do
    bs <- readFileExc fn
    either throw return (parseImage bs)

\normalsize

Either

\footnotesize

data ReadImageError = ParseError ParseError | ReadError IOException

readFileEither :: FilePath -> IO (Either IOException ByteString)
readFileEither fn = try (readFile fn) >>= return . either silenceENoEnt Right
  where
    silenceENoEnt :: IOException -> Either IOException ByteString
    silenceENoEnt exc | isDoesNotExistError exc = Right empty
                      | otherwise = Left exc

readImageFileEither :: FilePath -> IO (Either ReadImageError Image)
readImageFileEither fn = do
    mBs <- readFileEither fn
    return $ case mBs of
        Left err -> Left (ReadError err)
        Right bs -> either (Left . ParseError) Right (parseImage bs)

\normalsize

ErrorT and EitherT

  • newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }
  • newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) }

ErrorT and EitherT

  • newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }
  • newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) }
  • EitherT :: m (Either e a) -> EitherT e m a
  • runEitherT :: EitherT e m a -> m (Either e a)

EitherT

  • catch :: Exception e => IO a -> (e -> IO a) -> IO a
  • catch :: Exception e => IO (Either e a) -> (e -> IO (Either e’ a)) -> IO (Either e’ a)
  • catchT :: Monad m => EitherT e m a -> (e -> EitherT e’ m a) -> (EitherT e’ m a)

EitherT

  • handle :: Exception e => (e -> IO a) -> IO a -> IO a
  • handleT :: Monad m => (e -> EitherT e’ m a) -> EitherT e m a -> (EitherT e’ m a)

EitherT

  • try :: Exception e => IO a -> IO (Either e a)
  • tryIO :: MonadIO m => IO a -> EitherT IOException m a
  • EitherT . try :: Exception e => IO a -> EitherT e IO a

EitherT

\footnotesize

catchT :: forall e e' m a. Monad m
          => EitherT e m a
          -> (e -> EitherT e' m a)
          -> EitherT e' m a
catchT action handler = EitherT $ do
    res <- runEitherT action :: m (Either e a)
    case res of
        Right val -> return (Right val)
        Left err -> runEitherT (handler err)

\normalsize

MonadError

\footnotesize

class Monad m => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a

\normalsize

Revisiting readImageFileEither

\footnotesize

data ReadImageError = ParseError ParseError | ReadError IOException

readImageFileEither :: FilePath -> IO (Either ReadImageError Image)
readImageFileEither fn = do
    mBs <- readFileEither fn
    return $ case mBs of
        Left err -> Left (ReadError err)
        Right bs -> either (Left . ParseError) Right (parseImage bs)

\normalsize

Revisiting readImageFileEither

\footnotesize

data ReadImageError = ParseError ParseError | ReadError IOException

readImageFileEither :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither fn =

\normalsize

Revisiting readImageFileEither

\footnotesize

readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
    bs <- readFileEither' fn :: EitherT IOException IO ByteString

\normalsize

Revisiting readImageFileEither

\footnotesize

readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
    bs <- handleT (throwError . ReadError) (readFileEither' fn)
                :: EitherT ReadImageError IO ByteString

\normalsize

Revisiting readImageFileEither

\footnotesize

readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
    bs <- handleT (throwError . ReadError) (readFileEither' fn)
    case parseImage bs of
        Right val -> return val
        Left exc -> throwError (ParseError exc)

\normalsize

Revisiting readImageFileEither

\footnotesize

readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
    bs <- handleT (throwError . ReadError) (readFileEither' fn)
    either (throwError . ParseError) return (parseImage bs)

\normalsize

Revisiting readImageFileEither

\footnotesize

readFileExc :: FilePath -> IO ByteString
readFileExc = handle silenceENoEnt . readFile
  where
    silenceENoEnt :: IOException -> IO ByteString
    silenceENoEnt exc | isDoesNotExistError exc = return empty
                      | otherwise = throw exc

readFileEither' :: FilePath -> EitherT IOException IO ByteString
readFileEither' = handleT silenceENoEnt . tryIO . readFile
  where
    silenceENoEnt :: IOException -> EitherT IOException IO ByteString
    silenceENoEnt exc | isDoesNotExistError exc = return empty
                      | otherwise = throwError exc

\normalsize

Revisiting readImageFileEither

\footnotesize

silenceENoEnt' :: (MonadError IOException m, MonadIO m)
               => IOException
               -> m ByteString
silenceENoEnt' exc
    | isDoesNotExistError exc = return empty
    | otherwise = throwError exc

readFileExc :: FilePath -> IO ByteString
readFileExc = handle silenceENoEnt' . readFile

readFileEither' :: FilePath -> EitherT IOException IO ByteString
readFileEither' = handleT silenceENoEnt' . tryIO . readFile

\normalsize

Building an Application Stack

\footnotesize

data DiskStore = DiskStore { root :: FilePath }

newtype DiskStoreM a =
        DSM {
            uDSM :: ReaderT DiskStore
                            (EitherT DataStoreError IO)
                            a }
    deriving ( Functor, Applicative, Monad, MonadIO,
             , MonadReader DiskStore, MonadError DataStoreError )

\normalsize

Put an Object In

\footnotesize

putObject :: DataObject obj
          => Path
          -> obj
          -> Maybe ObjVersion
          -> DiskStoreM ObjVersion
putObject path obj mVer = do
    DiskStore{..} <- ask
    let fsPath = root </> "objects" </> T.unpack (T.intercalate "/" elems)
    createDirectoryIfMissing True (dropFileName fsPath)
    BS.writeFile fsPath (content obj)
    return (ObjVersion T.empty)

\normalsize

  • createDirectoryIfMissing :: Bool -> FilePath -> IO ()
  • writeFile :: FilePath -> ByteString -> IO ()

hoistIO

(IOException -> EitherT DataStoreError IO a) -> IO a -> DiskStoreM a

hoistIO

\footnotesize

hoistIO :: (IOException -> EitherT DataStoreError IO a)
        -> IO a
        -> EitherT IOException IO a
hoistIO handler action =
    tryIO action

\normalsize

hoistIO

\footnotesize

hoistIO :: (IOException -> EitherT DataStoreError IO a)
        -> IO a
        -> EitherT DataStoreError IO a
hoistIO handler action =
    handleT handler (tryIO action)

\normalsize

hoistIO

\footnotesize

hoistIO :: (IOException -> EitherT DataStoreError IO a)
        -> IO a
        -> ReaderT DiskStore (EitherT DataStoreError IO) a
hoistIO handler action =
    ReaderT (\_ -> handleT handler
                           (tryIO action))

hoistIO handler action =
    lift (handleT (throwError . handler)
                  (tryIO action))

\normalsize

hoistIO

\footnotesize

hoistIO :: (IOException -> EitherT DataStoreError IO a)
        -> IO a
        -> DiskStoreM a
hoistIO handler action =
    DSM (lift (handleT handler (tryIO action)))

\normalsize

Back to adding the object

\footnotesize

putObject :: DataObject obj => Path
                            -> obj
                            -> Maybe ObjVersion
                            -> DiskStoreM ObjVersion
putObject (Path elems) obj mVer = do
    DiskStore{..} <- ask
    let fsPath = root </> "objects" </> T.unpack (T.intercalate "/" elems)
    hoistIO (throwError . trIOExc) $ do
        createDirectoryIfMissing True (dropFileName fsPath)
        BS.writeFile fsPath (content obj)
    return (ObjVersion T.empty)

\normalsize

Catching multiple types of Exceptions

This page left blank

Catching and handling inside the monad

\footnotesize

putObject :: DataObject obj => Path
                            -> obj
                            -> Maybe ObjVersion
                            -> DiskStoreM ObjVersion
putObject (Path elems) obj mVer = do
    DiskStore{..} <- ask
    let fsPath = root </> "objects" </> T.unpack (T.intercalate "/" elems)
    res <- liftIO $ do
        createDirectoryIfMissing True (dropFileName fsPath)
        BS.writeFile fsPath (content obj)
    return (ObjVersion T.empty)

\normalsize

Links

Family, Queerness, and Polyamory

January 01, 0001
IMG_20170225_150825.jpg

This day, the feast of St. Valentine (or whatever), is a day for lovers to get together for nice dinner, romantic dates, and possibly some great sex. I generally find it to be a rather gross mix of cisgender, heteronormative performance and unspoken expectation, in which mind reading is a required skill.

So, what happens in queer communities in which most people involved have multiple partners and lovers?

In poly circles, this usually leads to very extensive use of calendars and negotiations. or a lot of group dates. On a normal year, I would probably spend V-day itself with Daria, and then designate another day that I would spend with Cait and Leah. In that sense, this year is no different than others, except in that Daria and I are mourning, not celebrating.

Like I told most people a few months before I moved, I moved to Boston to be with Cait and Leah.

20150717-img_4736e.jpg

IMG_2120.jpg

savanni-leah-shipping-container

I also moved to be with Daria, Aria, and as it turns out, Julia, Jess, Amelia, Ezri, Georgia, and others. Some of these folks I knew in advance, others I met shortly after coming here. The Cambridge/Somerville area has a magical grouping in which a huge number of queer polyamorous folks who exist in a constant state of organic community, meeting and parting in ways that create a deep set of connections between us that cannot be tracked or easily described, but which form a large extended family.

For so many in the Camberville queer community (and, I, presume, the queer communities of other large cities), emphasis on consent and negotiation has created a group that has become incredibly affectionate in both sexual and non-sexual ways. We foster extremely safe spaces, and in that safety affection has been able to grow. Those who cannot deal with touch know that they will not be touched. Asexuals will not be pursued. Sexual relationships will be expected to be conducted with respect and consent. And mistakes… well, we even have room for those and informal means of alleviating harm.

Image uploaded from iOS (2).jpg

When I moved, I knew that moving to be with my girlfriends would not suffice. I needed community, and I got a large extended family.

IMG_1567.jpg

Image uploaded from iOS-8.jpg

For the record, Cait, Leah, Daria, and I will be going to dinner somewhere that does not take reservations, so a litle finger-crossing is in order.

From a Garden Party

January 01, 0001
Glass Angel, 2012-04 Chandalier, 2012-04
Prism, 2012-04 Red, Red Wine, 2012-04

All four of these pictures are some old ones from my previous HDR work. The only thing they have in common is that they are all from a party that I went to some years ago. I wanted to get them back up, though, because I like all of them.

I really miss having a working HDR toolchain.