diff --git a/src/validate/DataValidator.js b/src/validate/DataValidator.js
new file mode 100644
index 0000000..0fead8d
--- /dev/null
+++ b/src/validate/DataValidator.js
@@ -0,0 +1,105 @@
+/**
+ * Data validator
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * This file is part of liza.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License
+ * along with this program. If not, see .
+ */
+
+"use strict";
+
+const Class = require( 'easejs' ).Class;
+
+
+/**
+ * Check data update for failures
+ *
+ * This validator glues together various parts of the system that contribute
+ * to a validation on data change.
+ *
+ * TODO: Remove reliance on ClientDependencyFactory
+ */
+module.exports = Class( 'DataValidator',
+{
+ /**
+ * Bucket data validator
+ * @type {BucketDataValidator}
+ */
+ 'private _bucket_validator': null,
+
+ /**
+ * Bucket field monitor
+ * @type {ValidStateMonitor}
+ */
+ 'private _field_monitor': null,
+
+ /**
+ * Dependency factory
+ *
+ * TODO: remove dependency on this class
+ *
+ * @type {ClientDependencyFactory}
+ */
+ 'private _factory': null,
+
+
+ /**
+ * Initialize validator
+ *
+ * @param {BucketDataValidator} bucket_validator data validator
+ * @param {ValidStateMonitor} field_monitor field state monitor
+ * @param {ClientDependencyFactory} dep_factory REMOVE ME
+ */
+ __construct( bucket_validator, field_monitor, dep_factory )
+ {
+ this._bucket_validator = bucket_validator;
+ this._field_monitor = field_monitor;
+ this._factory = dep_factory;
+ },
+
+
+ /**
+ * Validate diff and update field monitor
+ *
+ * The external validator `validatef` is a kluge while the system
+ * undergoes refactoring.
+ *
+ * @param {Object} diff bucket diff
+ * @param {function(Object,Object)=} validatef external validator
+ *
+ * @return {Promise} accepts with unspecified value once field monitor
+ * has completed its update
+ */
+ 'public validate'( diff, validatef )
+ {
+ const _self = this;
+
+ let failures = {};
+
+ this._bucket_validator.validate( diff, ( name, value, i ) =>
+ {
+ diff[ name ][ i ] = undefined;
+
+ ( failures[ name ] = failures[ name ] || {} )[ i ] =
+ _self._factory.createFieldFailure( name, i, value );
+ }, true );
+
+ validatef && validatef( diff, failures );
+
+ // XXX: this assumes that the above is synchronous
+ return this._field_monitor.update( diff, failures );
+ },
+} );
diff --git a/test/validate/DataValidatorTest.js b/test/validate/DataValidatorTest.js
new file mode 100644
index 0000000..a209486
--- /dev/null
+++ b/test/validate/DataValidatorTest.js
@@ -0,0 +1,183 @@
+/**
+ * Test data validator
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * This file is part of liza.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License
+ * along with this program. If not, see .
+ */
+
+"use strict";
+
+const root = require( '../../' );
+const validate = root.validate;
+const Sut = validate.DataValidator;
+const chai = require( 'chai' );
+const expect = chai.expect;
+const sinon = require( 'sinon' );
+
+const BucketDataValidator = validate.BucketDataValidator,
+ ValidStateMonitor = validate.ValidStateMonitor;
+
+chai.use( require( 'chai-as-promised' ) );
+
+
+describe( 'DataValidator', () =>
+{
+ describe( '#validate', () =>
+ {
+ it( 'validates against bucket validator', () =>
+ {
+ const bvalidator = createMockBucketValidator(
+ function( data, err, inplace )
+ {
+ expect( data ).to.equal( diff );
+ expect( inplace ).to.be.true;
+
+ // since we're mocking #validate, the callback will not
+ // be called; we'll have to do so ourselves (eventually
+ // this will be a promise)
+ err( 'foo', expected_value, 1 );
+ }
+ );
+
+ const vmonitor = ValidStateMonitor();
+ const dep_factory = createMockDependencyFactory();
+
+ const mock_vmonitor = sinon.mock( vmonitor );
+ const mock_dep_factory = sinon.mock( dep_factory );
+
+ const diff = { foo: [ 'a', 'b', 'c' ] };
+ const expected_failure = {};
+ const expected_value = 'errmsg';
+ const expected_failures = {
+ foo: { 1: expected_failure }
+ };
+
+ mock_vmonitor.expects( 'update' )
+ .once()
+ .withExactArgs( diff, expected_failures )
+ .returns( Promise.resolve( undefined ) );
+
+ mock_dep_factory.expects( 'createFieldFailure' )
+ .once()
+ .withExactArgs( 'foo', 1, expected_value )
+ .returns( expected_failure );
+
+ const retp = Sut( bvalidator, vmonitor, dep_factory )
+ .validate( diff );
+
+ // cleared on call to err in above mock validator
+ expect( diff.foo ).to.deep.equal(
+ [ 'a', undefined, 'c' ]
+ );
+
+ mock_vmonitor.verify();
+ mock_dep_factory.verify();
+
+ // the promise
+ return retp;
+ } );
+
+
+ it( 'considers failures from external validator', () =>
+ {
+ const expected_failure = {};
+
+ const bvalidator = createMockBucketValidator(
+ function( data, err, _ )
+ {
+ // see `failures` below
+ err( 'foo', 'moo', 2 );
+ }
+ );
+
+ const vmonitor = ValidStateMonitor();
+ const dep_factory = createMockDependencyFactory();
+
+ const diff = { foo: [ 'a', 'b', 'c' ] };
+ const expected_failures = {
+ foo: {
+ 0: expected_failure,
+ 2: expected_failure,
+ },
+ };
+
+ const validatef = ( given_diff, given_failures ) =>
+ {
+ expect( given_diff ).to.equal( diff );
+ expect( given_failures.foo[ 2 ] )
+ .to.equal( expected_failure );
+
+ given_failures.foo[ 0 ] = expected_failure;
+ };
+
+ // TODO: this is an implementation detail left over from the
+ // good 'ol days; remove it
+ sinon.mock( vmonitor )
+ .expects( 'update' )
+ .once()
+ .withExactArgs( diff, expected_failures )
+ .returns( Promise.resolve( undefined ) );
+
+ sinon.mock( dep_factory )
+ .expects( 'createFieldFailure' )
+ .returns( expected_failure );
+
+ return Sut( bvalidator, vmonitor, dep_factory )
+ .validate( diff, validatef );
+ } );
+
+
+ it( 'rejects if field monitor update rejects', () =>
+ {
+ const bvalidator = createMockBucketValidator( ( x, y, z ) => {} );
+ const vmonitor = ValidStateMonitor();
+ const dep_factory = createMockDependencyFactory();
+
+ const expected_e = Error();
+
+ sinon.mock( vmonitor )
+ .expects( 'update' )
+ .once()
+ .returns( Promise.reject( expected_e ) );
+
+ return expect(
+ Sut( bvalidator, vmonitor, dep_factory )
+ .validate( {} )
+ ).to.eventually.be.rejectedWith( expected_e );
+ } );
+ } );
+} );
+
+
+function createMockBucketValidator( validatef )
+{
+ return BucketDataValidator.extend(
+ {
+ 'override public validate': validatef,
+ } )();
+}
+
+
+// This isn't yet moved into liza (at least at the time of writing this)
+function createMockDependencyFactory( map )
+{
+ // alternative to mocking since the ClientDependencyFactory is not going
+ // to be used in the future
+ return {
+ createFieldFailure: () => {},
+ };
+}