diff --git a/src/error/ChainedError.ts b/src/error/ChainedError.ts
new file mode 100644
index 0000000..efe9b99
--- /dev/null
+++ b/src/error/ChainedError.ts
@@ -0,0 +1,73 @@
+/**
+ * Uniform error handling for Liza: error chaining
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { ___Writable } from 'naughty';
+
+
+/**
+ * An Error augmented to include information about an underlying cause
+ *
+ * To create new chains, use the `chain` function.
+ *
+ * Chaining should be used when an error is caught and transformed into
+ * another, more specific error. By maintaining a reference to an existing
+ * error, context is not lost, which can be helpful for debugging and
+ * logging.
+ *
+ * Chains may be nested to an arbitrary depth, but because of the nature of
+ * JavaScript's errors, recursive chain type checks must be done at runtime.
+ */
+export interface ChainedError extends Error
+{
+ readonly chain: T,
+}
+
+
+/**
+ * Type predicate for `ChainedError`
+ *
+ * This predicate can be used at runtime to determine whether an error is
+ * chained.
+ *
+ * @param e error object
+ *
+ * @return whether `e` is of type ChainedError
+ */
+export const isChained = ( e: Error ): e is ChainedError =>
+ ( e ).chain !== undefined;
+
+
+/**
+ * Chains two `Error`s
+ *
+ * This is intended to be used as if it were a constructor, where the first
+ * argument is a new `Error` instance.
+ *
+ * @param enew new error
+ * @param eprev error to chain
+ *
+ * @return `enew` with `eprev` chained
+ */
+export function chain( enew: Error, eprev: Error ): ChainedError
+{
+ ( <___Writable>enew ).chain = eprev;
+ return enew;
+}
diff --git a/src/error/ContextError.ts b/src/error/ContextError.ts
new file mode 100644
index 0000000..1286a8b
--- /dev/null
+++ b/src/error/ContextError.ts
@@ -0,0 +1,93 @@
+/**
+ * Uniform error handling for Liza: error context
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * This context system is intended to play nicely with how Error objects are
+ * typically used in JavaScript. As such, rather than creating new error
+ * prototypes / subclasses, these rely on structural typing to augment
+ * existing `Error` objects.
+ */
+
+import { ___Writable } from 'naughty';
+
+
+/**
+ * Error with additional context regarding its cause
+ *
+ * Errors may be augmented with key/value data (see `ErrorContext`)
+ * containing data that will be helpful for debugging the cause of the
+ * error. The context should be expected to appear in structured logs, so
+ * it shouldn't include sensitive data without some mitigation layer.
+ *
+ * A context may be optionally typed, but note that such context will
+ * generally be lost any time promises are used, so type predicates will
+ * need to be used to restore the type with information at runtime.
+ *
+ * Since the context is intended primarily for debugging, it shouldn't be
+ * relied on to drive control flow unless absolutely necessary, in which
+ * case an explicit context should be used.
+ */
+export interface ContextError
+ extends Error
+{
+ readonly context: T,
+}
+
+
+/**
+ * Key/value context for an error
+ *
+ * Rather than accepting data of an arbitrary type, we force key/value for
+ * reasons of extensibility and consistency: if more information is needed
+ * in the future, the type will remain unchanged. The values, however, may
+ * include any arbitrary data.
+ */
+export type ErrorContext = { readonly [P: string]: any };
+
+
+/**
+ * Type predicate for `ContextError`
+ *
+ * Note that this is a predicate for a generic `ContextError`, type, which
+ * is equivalent to `ContextError`. Other contexts must
+ * define their own predicates.
+ *
+ * @param e error object
+ *
+ * @return whether `e` is of type `ContextError`
+ */
+export const hasContext = ( e: Error ): e is ContextError =>
+ ( e ).context !== undefined;
+
+
+/**
+ * Adds context to an error
+ *
+ * This is intended to be used as if it were a constructor, where the first
+ * argument is a new `Error` instance.
+ *
+ * @param enew error object to add context to
+ * @param context key/value context information
+ */
+export function context( enew: Error, context: T ):
+ ContextError
+{
+ ( <___Writable>>enew ).context = context;
+ return >enew;
+}
diff --git a/src/types/naugty.d.ts b/src/types/naugty.d.ts
new file mode 100644
index 0000000..cdca847
--- /dev/null
+++ b/src/types/naugty.d.ts
@@ -0,0 +1,48 @@
+/**
+ * Things that should only be used when absolutely necessary
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * DEFINITIONS IN THIS PACKAGE DO NAUGHTY THINGS THAT CIRCUMVENT TYPE
+ * SAFETY; THEY SHOULD BE USED ONLY WHEN NECESSARY, AND ONLY WHEN YOU KNOW
+ * WHAT YOU'RE DOING, SINCE THEY MAY INTRODUCE BUGS!
+ *
+ * The prefix `___` is added to each of the names here so that code can be
+ * easily searched for uses of naughty things.
+ *
+ * These types are also exported, unlike some other `.d.ts` files which are
+ * universally available during complication---this forces the importing of
+ * this file, named `naughty.d.ts`, which should raise some eyebrows and
+ * make people less likely to copy existing code that uses it.
+ */
+
+declare module 'naughty'
+{
+ /**
+ * Make type `T` writable while otherwise maintaining type safety
+ *
+ * _Only use this generic if you are the owner of the object being
+ * manipulated!__
+ *
+ * This should be used when we want types to be readonly, but we need to
+ * be able to modify an existing object to initialize the
+ * properties. This should only be used in situations where it's not
+ * feasible to add those properties when the object is first created.
+ */
+ export type ___Writable = { -readonly [K in keyof T]: T[K] };
+}
diff --git a/test/error/ChainedErrorTest.ts b/test/error/ChainedErrorTest.ts
new file mode 100644
index 0000000..d6d98ed
--- /dev/null
+++ b/test/error/ChainedErrorTest.ts
@@ -0,0 +1,67 @@
+/**
+ * Tests error chaining
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as sut from "../../src/error/ChainedError";
+import { expect } from 'chai';
+
+
+describe( 'ChainedError', () =>
+{
+ it( "can be created with generic error", () =>
+ {
+ const eprev = new Error( "previous error" );
+
+ expect( sut.chain( new Error( "new error" ), eprev ).chain )
+ .to.equal( eprev );
+ } );
+
+
+ it( "can be chained to arbitrary depth", () =>
+ {
+ const e1 = new Error( "lower" );
+ const e2 = sut.chain( new Error( "mid" ), e1 );
+ const e3 = sut.chain( new Error( "outer" ), e2 );
+
+ expect( sut.isChained( e3 ) ).to.be.true;
+ expect( sut.isChained( e2 ) ).to.be.true;
+ expect( sut.isChained( e1 ) ).to.be.false;
+ } );
+
+
+ it( "provides type predicate for TypeScript", () =>
+ {
+ const inner = new Error( "inner" );
+
+ // force to Error to discard ChainedError type
+ const outer: Error = sut.chain( new Error( "outer" ), inner );
+
+ if ( sut.isChained( outer ) )
+ {
+ // if isChained was properly defined, then outer should now
+ // have type ChainedError, and so this should compile
+ expect( outer.chain ).to.equal( inner );
+ }
+ else
+ {
+ expect.fail();
+ }
+ } );
+} );
diff --git a/test/error/ContextErrorTest.ts b/test/error/ContextErrorTest.ts
new file mode 100644
index 0000000..c02c3f2
--- /dev/null
+++ b/test/error/ContextErrorTest.ts
@@ -0,0 +1,68 @@
+/**
+ * Tests error context
+ *
+ * Copyright (C) 2010-2019 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as sut from "../../src/error/ContextError";
+import { expect } from 'chai';
+
+
+describe( 'ContextError', () =>
+{
+ it( "can be created with generic error", () =>
+ {
+ const context = { foo: "context" };
+
+ expect( sut.context( new Error( "test error" ), context ).context )
+ .to.equal( context );
+ } );
+
+
+ it( "provides type predicate for TypeScript", () =>
+ {
+ const context = { bar: "baz context" };
+
+ // force to Error to discard ContextError type
+ const e: Error = sut.context( new Error( "test error" ), context );
+
+ if ( sut.hasContext( e ) )
+ {
+ // if isChained was properly defined, then outer should now
+ // have type ChainedError, and so this should compile
+ expect( e.context ).to.equal( context );
+ }
+ else
+ {
+ expect.fail();
+ }
+ } );
+
+
+ it( "can create typed contexts", () =>
+ {
+ type FooErrorContext = { foo: string };
+
+ // this is the actual test
+ const e: sut.ContextError =
+ sut.context( new Error( "test error" ), { foo: "context" } );
+
+ // contravariance check (would fail to compile)
+ expect( sut.hasContext( e ) ).to.be.true;
+ } );
+} );