sensorlog_lite: Device and stream VDL types.

Basic VDL types for stream configuration.
Support for types storing data in both key and value.

Change-Id: Ib3a904e27e05128819fdff75e9100c5fe1626b46
diff --git a/go/src/v.io/x/sensorlog/internal/config/defaults.go b/go/src/v.io/x/sensorlog/internal/config/defaults.go
index 1854d83..1ce9f9a 100644
--- a/go/src/v.io/x/sensorlog/internal/config/defaults.go
+++ b/go/src/v.io/x/sensorlog/internal/config/defaults.go
@@ -16,12 +16,8 @@
 	DBName  = "sldb"
 
 	SyncPriority = 42
-
-	StreamDefTable = "strdef"
 )
 
-var AllTables = []string{StreamDefTable}
-
 func SyncgroupName(publishService, devId string) string {
 	return naming.Join(publishService, "%%sync", "sllite", "dev", devId)
 }
diff --git a/go/src/v.io/x/sensorlog/internal/measure/syncgroup.go b/go/src/v.io/x/sensorlog/internal/measure/syncgroup.go
index 871e619..8f58bb0 100644
--- a/go/src/v.io/x/sensorlog/internal/measure/syncgroup.go
+++ b/go/src/v.io/x/sensorlog/internal/measure/syncgroup.go
@@ -16,6 +16,8 @@
 	"v.io/v23/syncbase/nosql"
 	"v.io/v23/verror"
 	"v.io/x/sensorlog_lite/internal/config"
+	"v.io/x/sensorlog_lite/internal/sbmodel"
+	"v.io/x/sensorlog_lite/internal/sbmodel/keyutil"
 	"v.io/x/sensorlog_lite/internal/sbutil"
 )
 
@@ -26,6 +28,10 @@
 // retried with different parameters for the same devId, otherwise behaviour
 // is unspecified.
 func InitSyncgroup(ctx *context.T, db nosql.Database, devId, admin, sgPublishSb string, sgMountTables []string) error {
+	if err := keyutil.ValidateId(devId); err != nil {
+		return fmt.Errorf("invalid devId: %v", err)
+	}
+
 	sgName := config.SyncgroupName(sgPublishSb, devId)
 	// Check for syncgroup. If it already exists, we have nothing to do.
 	if sgs, err := db.GetSyncgroupNames(ctx); err != nil {
@@ -49,7 +55,7 @@
 	// StreamDef : <devId>
 	// Admin client has full permissions, measured drops to readonly.
 	prefixStreamDef := nosql_wire.SyncgroupPrefix{
-		TableName: config.StreamDefTable,
+		TableName: sbmodel.KStreamDef{}.Table(),
 		RowPrefix: devId,
 	}
 	aclStreamDef := access.Permissions{}
diff --git a/go/src/v.io/x/sensorlog/internal/measure/syncgroup_test.go b/go/src/v.io/x/sensorlog/internal/measure/syncgroup_test.go
index 24ba12f..f38ef39 100644
--- a/go/src/v.io/x/sensorlog/internal/measure/syncgroup_test.go
+++ b/go/src/v.io/x/sensorlog/internal/measure/syncgroup_test.go
@@ -5,14 +5,16 @@
 package measure_test
 
 import (
+	"bytes"
 	"reflect"
 	"testing"
 
-	"v.io/v23/verror"
+	"v.io/v23/security/access"
 	_ "v.io/x/ref/runtime/factories/generic"
 	sbtu "v.io/x/ref/services/syncbase/testutil"
 	"v.io/x/sensorlog_lite/internal/config"
 	"v.io/x/sensorlog_lite/internal/measure"
+	"v.io/x/sensorlog_lite/internal/sbmodel"
 	"v.io/x/sensorlog_lite/internal/sbutil"
 )
 
@@ -21,7 +23,7 @@
 	defer cleanup()
 
 	// Open app/db (create both) as measured.
-	db, err := sbutil.CreateOrOpenDB(ctxMeasured, sbName, config.AllTables)
+	db, err := sbutil.CreateOrOpenDB(ctxMeasured, sbName, sbmodel.MeasuredTables)
 	if err != nil {
 		t.Fatalf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
@@ -48,8 +50,20 @@
 	}
 
 	// measured should have dropped privileges on <StreamDefTable>/<devId>.
-	sgDataRow := db.Table(config.StreamDefTable).Row(devId + "$" + "foo")
-	if err := sgDataRow.Put(ctxMeasured, "bar"); verror.ErrorID(err) != verror.ErrNoAccess.ID {
-		t.Errorf("Put by measured should have failed with ErrNoAccess, got: %v", err)
+	expectPerms, err := access.ReadPermissions(bytes.NewBufferString(`{
+		"Admin":{"In":["root/two"]},
+		"Read":{"In":["root/two", "root/one"]},
+		"Write":{"In":["root/two"]},
+		"Debug":{"In":["root/two"]},
+		"Resolve":{"In":["root/two", "root/one"]}
+	}`))
+	if err != nil {
+		t.Fatalf("ReadPermissions should have succeeded, got error: %v", err)
+	}
+	sgDataTable := db.Table(sbmodel.KStreamDef{}.Table())
+	if gotPerms, err := sgDataTable.GetPrefixPermissions(ctxMeasured, devId); err != nil {
+		t.Errorf("GetPrefixPermissions failed: %v", err)
+	} else if got, want := gotPerms[0].Perms.Normalize(), expectPerms.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Unexpected permissions on streamdef/<devId>: got %v, want %v", got, want)
 	}
 }
diff --git a/go/src/v.io/x/sensorlog/internal/sbmodel/keyutil/keyutil.go b/go/src/v.io/x/sensorlog/internal/sbmodel/keyutil/keyutil.go
new file mode 100644
index 0000000..408f8c4
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/sbmodel/keyutil/keyutil.go
@@ -0,0 +1,51 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Utilities for building and parsing Sensor Log data keys.
+package keyutil
+
+import (
+	"crypto/rand"
+	"encoding/hex"
+	"fmt"
+	"strings"
+)
+
+const Sep = '$'
+
+// Join joins key parts into key.
+func Join(parts ...string) string {
+	return strings.Join(parts, string(Sep))
+}
+
+// Split splits key into parts and verifies that the number of parts matches
+// the expected number, returning an error otherwise.
+func Split(key string, expectParts int) ([]string, error) {
+	parts := strings.Split(key, string(Sep))
+	if gotParts := len(parts); expectParts != gotParts {
+		return nil, fmt.Errorf("invalid key %q: expected %d parts, got %d", key, expectParts, gotParts)
+	}
+	return parts, nil
+}
+
+// ValidateId returns an error if the argument is not a valid identifier.
+func ValidateId(id string) error {
+	if id == "" {
+		return fmt.Errorf("cannot be empty")
+	}
+	if strings.Contains(id, string(Sep)) {
+		return fmt.Errorf("cannot contain %q", Sep)
+	}
+	return nil
+}
+
+// UUID generates a random UUID.
+func UUID() string {
+	uuid := make([]byte, 16)
+	if _, err := rand.Read(uuid); err != nil {
+		panic(fmt.Errorf("rng failed: %v", err))
+	}
+	// TODO(ivanpi): Use base64 once Syncbase keys are less restricted.
+	return hex.EncodeToString(uuid)
+}
diff --git a/go/src/v.io/x/sensorlog/internal/sbmodel/types.go b/go/src/v.io/x/sensorlog/internal/sbmodel/types.go
new file mode 100644
index 0000000..a8579c8
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/sbmodel/types.go
@@ -0,0 +1,65 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Implements PersistentDataKey for each K<T> type, as described in types.vdl.
+
+package sbmodel
+
+import (
+	"v.io/x/sensorlog_lite/internal/sbmodel/keyutil"
+)
+
+// PersistentDataKey is a type encapsulating data from the row key of a
+// top-level value type persisted to Syncbase.
+type PersistentDataKey interface {
+	// Table returns the name of the Syncbase table for the data type.
+	Table() string
+	// Key returns the row key for the value.
+	Key() string
+	// Parse parses the row key for the value into self. Returns an error if key
+	// is malformed.
+	Parse(key string) error
+}
+
+// devicecfg : <DevId>
+func (_ KDeviceCfg) Table() string { return "devicecfg" }
+func (k *KDeviceCfg) Key() string  { return keyutil.Join(k.DevId) }
+func (k *KDeviceCfg) Parse(key string) error {
+	parts, err := keyutil.Split(key, 1)
+	if err != nil {
+		return err
+	}
+	k.DevId = parts[0]
+	return nil
+}
+
+// streamdef : <DevId>/<StreamId>
+func (_ KStreamDef) Table() string { return "streamdef" }
+func (k *KStreamDef) Key() string  { return keyutil.Join(k.DevId, k.StreamId) }
+func (k *KStreamDef) Parse(key string) error {
+	parts, err := keyutil.Split(key, 2)
+	if err != nil {
+		return err
+	}
+	k.DevId, k.StreamId = parts[0], parts[1]
+	return nil
+}
+
+// TableSpec defines a Syncbase table, encapsulating a key prototype and
+// permissions.
+type TableSpec struct {
+	Prototype PersistentDataKey
+	ReadOnly  bool
+}
+
+// All top-level types persisted to master device Syncbase.
+var MasterTables = []TableSpec{
+	{Prototype: &KDeviceCfg{}},
+	{Prototype: &KStreamDef{}},
+}
+
+// All top-level types persisted to measured Syncbase.
+var MeasuredTables = []TableSpec{
+	{Prototype: &KStreamDef{}, ReadOnly: true},
+}
diff --git a/go/src/v.io/x/sensorlog/internal/sbmodel/types.vdl b/go/src/v.io/x/sensorlog/internal/sbmodel/types.vdl
new file mode 100644
index 0000000..4ddee43
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/sbmodel/types.vdl
@@ -0,0 +1,39 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Syncbase data model for Sensor Log.
+//
+// Every type <T> stored in Syncbase is defined as a pair of types, K<T> and
+// V<T>, representing data stored in the key and value, respectively, in a
+// single table. K<T> types satisfy the PersistentDataKey interface, supporting
+// conversion to and from the row key.
+package sbmodel
+
+// devicecfg : <DevId>
+// Measuring device handle. Master only.
+type VDeviceCfg struct {
+  // Human-readable, not necessarily unique description of the device.
+  Desc string
+  // Syncbase instance publishing the syncgroup created by the device.
+  SgPublishSb string
+}
+
+type KDeviceCfg struct {
+  DevId string
+}
+
+// streamdef : <DevId>/<StreamId>
+// Configures a stream of data to be measured.
+// TODO(ivanpi): Add actual sampling script and parameters.
+type VStreamDef struct {
+  // Human-readable, not necessarily unique description of the stream.
+  Desc string
+  // Flag to start and stop sampling.
+  Enabled bool
+}
+
+type KStreamDef struct {
+  DevId string
+  StreamId string
+}
diff --git a/go/src/v.io/x/sensorlog/internal/sbmodel/types.vdl.go b/go/src/v.io/x/sensorlog/internal/sbmodel/types.vdl.go
new file mode 100644
index 0000000..355db3c
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/sbmodel/types.vdl.go
@@ -0,0 +1,74 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated by the vanadium vdl tool.
+// Source: types.vdl
+
+// Syncbase data model for Sensor Log.
+//
+// Every type <T> stored in Syncbase is defined as a pair of types, K<T> and
+// V<T>, representing data stored in the key and value, respectively, in a
+// single table. K<T> types satisfy the PersistentDataKey interface, supporting
+// conversion to and from the row key.
+package sbmodel
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+)
+
+// devicecfg : <DevId>
+// Measuring device handle. Master only.
+type VDeviceCfg struct {
+	// Human-readable, not necessarily unique description of the device.
+	Desc string
+	// Syncbase instance publishing the syncgroup created by the device.
+	SgPublishSb string
+}
+
+func (VDeviceCfg) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/sensorlog_lite/internal/sbmodel.VDeviceCfg"`
+}) {
+}
+
+type KDeviceCfg struct {
+	DevId string
+}
+
+func (KDeviceCfg) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/sensorlog_lite/internal/sbmodel.KDeviceCfg"`
+}) {
+}
+
+// streamdef : <DevId>/<StreamId>
+// Configures a stream of data to be measured.
+// TODO(ivanpi): Add actual sampling script and parameters.
+type VStreamDef struct {
+	// Human-readable, not necessarily unique description of the stream.
+	Desc string
+	// Flag to start and stop sampling.
+	Enabled bool
+}
+
+func (VStreamDef) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/sensorlog_lite/internal/sbmodel.VStreamDef"`
+}) {
+}
+
+type KStreamDef struct {
+	DevId    string
+	StreamId string
+}
+
+func (KStreamDef) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/sensorlog_lite/internal/sbmodel.KStreamDef"`
+}) {
+}
+
+func init() {
+	vdl.Register((*VDeviceCfg)(nil))
+	vdl.Register((*KDeviceCfg)(nil))
+	vdl.Register((*VStreamDef)(nil))
+	vdl.Register((*KStreamDef)(nil))
+}
diff --git a/go/src/v.io/x/sensorlog/internal/sbmodel/types_test.go b/go/src/v.io/x/sensorlog/internal/sbmodel/types_test.go
new file mode 100644
index 0000000..12453de
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/sbmodel/types_test.go
@@ -0,0 +1,82 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sbmodel_test
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/x/sensorlog_lite/internal/sbmodel"
+	"v.io/x/sensorlog_lite/internal/sbmodel/keyutil"
+)
+
+type keyTest interface {
+	Run(t *testing.T, keyPrototype sbmodel.PersistentDataKey)
+}
+
+// validKeyTest verifies that the key is correctly parsed and built.
+type validKeyTest struct {
+	keyStr    string
+	keyParsed sbmodel.PersistentDataKey
+}
+
+func (kt *validKeyTest) Run(t *testing.T, keyPrototype sbmodel.PersistentDataKey) {
+	if err := keyPrototype.Parse(kt.keyStr); err != nil {
+		t.Errorf("key %q parse failed: %v", kt.keyStr, err)
+		return
+	}
+	if got, want := keyPrototype, kt.keyParsed; !reflect.DeepEqual(got, want) {
+		t.Errorf("incorrect key parse: got %v, want %v", got, want)
+	}
+	if got, want := kt.keyParsed.Key(), kt.keyStr; got != want {
+		t.Errorf("incorrect key build: got %s, want %s", got, want)
+	}
+}
+
+// invalidKeyTest verifies that malformed key parsing fails.
+type invalidKeyTest struct {
+	keyStr string
+}
+
+func (kt *invalidKeyTest) Run(t *testing.T, keyPrototype sbmodel.PersistentDataKey) {
+	if err := keyPrototype.Parse(kt.keyStr); err == nil {
+		t.Errorf("key %q parse should have failed", kt.keyStr)
+	}
+}
+
+func TestDeviceCfgKeys(t *testing.T) {
+	tests := []keyTest{
+		&validKeyTest{
+			keyStr: keyutil.Join("foo"),
+			keyParsed: &sbmodel.KDeviceCfg{
+				DevId: "foo",
+			},
+		},
+		&invalidKeyTest{
+			keyStr: keyutil.Join("foo", "bar"),
+		},
+	}
+	for _, kt := range tests {
+		kt.Run(t, &sbmodel.KDeviceCfg{})
+	}
+}
+
+func TestStreamDefKeys(t *testing.T) {
+	tests := []keyTest{
+		&validKeyTest{
+			keyStr: keyutil.Join("pc", "cputemp"),
+			keyParsed: &sbmodel.KStreamDef{
+				DevId:    "pc",
+				StreamId: "cputemp",
+			},
+		},
+		&invalidKeyTest{
+			keyStr: keyutil.Join("pc"),
+		},
+	}
+	for _, kt := range tests {
+		kt.Run(t, &sbmodel.KStreamDef{})
+	}
+}
diff --git a/go/src/v.io/x/sensorlog/internal/sbutil/syncbase.go b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase.go
index 8c7c708..1bc1784 100644
--- a/go/src/v.io/x/sensorlog/internal/sbutil/syncbase.go
+++ b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase.go
@@ -14,17 +14,24 @@
 	"v.io/v23/syncbase/nosql"
 	"v.io/v23/verror"
 	"v.io/x/sensorlog_lite/internal/config"
+	"v.io/x/sensorlog_lite/internal/sbmodel"
 )
 
 // CreateOrOpenDB opens the Sensor Log database hosted on specified Syncbase
 // instance, creating it if missing, initializing specified tables.
-func CreateOrOpenDB(ctx *context.T, sbService string, tables []string) (nosql.Database, error) {
+func CreateOrOpenDB(ctx *context.T, sbService string, tables []sbmodel.TableSpec) (nosql.Database, error) {
 	aclFull := access.Permissions{}
-	// Allow everyone to resolve app/database to allow joining syncgroups.
+	// Allow everyone to resolve to allow joining syncgroups.
 	AddPermsForPattern(&aclFull, string(security.AllPrincipals), access.Resolve)
 	// Restrict other permissions to self.
 	AddPermsForPrincipal(&aclFull, v23.GetPrincipal(ctx), access.Read, access.Write, access.Admin, access.Debug)
 
+	aclReadOnly := access.Permissions{}
+	// Allow everyone to resolve to allow joining syncgroups.
+	AddPermsForPattern(&aclReadOnly, string(security.AllPrincipals), access.Resolve)
+	// Restrict other permissions to self, except Write.
+	AddPermsForPrincipal(&aclReadOnly, v23.GetPrincipal(ctx), access.Read, access.Admin, access.Debug)
+
 	sbs := syncbase.NewService(sbService)
 	app := sbs.App(config.AppName)
 	if err := createIfMissing(ctx, app, aclFull); err != nil {
@@ -38,9 +45,13 @@
 	}
 
 	// TODO(ivanpi): Add table schemas when available.
-	for _, tn := range tables {
-		tb := db.Table(tn)
-		if err := createIfMissing(ctx, tb, aclFull); err != nil {
+	for _, ts := range tables {
+		tb := db.Table(ts.Prototype.Table())
+		acl := aclReadOnly
+		if !ts.ReadOnly {
+			acl = aclFull
+		}
+		if err := createIfMissing(ctx, tb, acl); err != nil {
 			return nil, err
 		}
 	}
diff --git a/go/src/v.io/x/sensorlog/internal/sbutil/syncbase_test.go b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase_test.go
index 40c2016..543daa5 100644
--- a/go/src/v.io/x/sensorlog/internal/sbutil/syncbase_test.go
+++ b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase_test.go
@@ -13,6 +13,7 @@
 	"v.io/v23/verror"
 	_ "v.io/x/ref/runtime/factories/generic"
 	sbtu "v.io/x/ref/services/syncbase/testutil"
+	"v.io/x/sensorlog_lite/internal/sbmodel"
 	"v.io/x/sensorlog_lite/internal/sbutil"
 )
 
@@ -21,19 +22,17 @@
 	defer cleanup()
 	ctxGuest := sbtu.NewCtx(ctxOwner, rootPrincipal, "two")
 
-	mockTables := []string{"tfoo", "tbar"}
-
 	// Try to open app/db (create both) as guest, fail with ErrNoAccess.
-	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, sbmodel.MasterTables); verror.ErrorID(err) != verror.ErrNoAccess.ID {
 		t.Errorf("CreateOrOpenDB should have failed with ErrNoAccess, got error: %v", err)
 	}
 	// Open app/db (create both) as owner.
-	dbOwner, err := sbutil.CreateOrOpenDB(ctxOwner, sbName, mockTables)
+	dbOwner, err := sbutil.CreateOrOpenDB(ctxOwner, sbName, sbmodel.MasterTables)
 	if err != nil {
 		t.Fatalf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
 	// Open existing app/db as guest.
-	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables); err != nil {
+	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, sbmodel.MasterTables); err != nil {
 		t.Errorf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
 	// Destroy db (but not app) to simulate interrupted creation.
@@ -41,16 +40,16 @@
 		t.Errorf("dbOwner.Destroy should have succeeded, got error: %v", err)
 	}
 	// Try to open app/db (create db) as guest, fail with ErrNoAccess.
-	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, sbmodel.MasterTables); verror.ErrorID(err) != verror.ErrNoAccess.ID {
 		t.Errorf("CreateOrOpenDB should have failed with ErrNoAccess, got error: %v", err)
 	}
 	// Open app/db (recreate db) as owner.
-	dbOwner, err = sbutil.CreateOrOpenDB(ctxOwner, sbName, mockTables)
+	dbOwner, err = sbutil.CreateOrOpenDB(ctxOwner, sbName, sbmodel.MasterTables)
 	if err != nil {
 		t.Fatalf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
 	// Open recreated app/db as guest.
-	dbGuest, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables)
+	dbGuest, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, sbmodel.MasterTables)
 	if err != nil {
 		t.Errorf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
@@ -71,10 +70,58 @@
 		t.Errorf("Unexpected database permissions: got %v, want %v", got, want)
 	}
 	// Check that all tables exist.
-	for _, tn := range mockTables {
-		tb := dbGuest.Table(tn)
+	for _, ts := range sbmodel.MasterTables {
+		tb := dbGuest.Table(ts.Prototype.Table())
 		if exists, err := tb.Exists(ctxGuest); err != nil || !exists {
 			t.Errorf("Expected table %s to exist, got: %v (error: %v)", tb.Name(), exists, err)
 		}
 	}
 }
+
+func TestTablePermissions(t *testing.T) {
+	_, ctxOwner, sbName, _, cleanup := sbtu.SetupOrDieCustom("one", "one/sb", nil)
+	defer cleanup()
+
+	// Open app/db (create both) as owner.
+	dbOwner, err := sbutil.CreateOrOpenDB(ctxOwner, sbName, sbmodel.MeasuredTables)
+	if err != nil {
+		t.Fatalf("CreateOrOpenDB should have succeeded, got error: %v", err)
+	}
+
+	expectPermsFull, err := access.ReadPermissions(bytes.NewBufferString(`{
+		"Admin":{"In":["root/one"]},
+		"Read":{"In":["root/one"]},
+		"Write":{"In":["root/one"]},
+		"Debug":{"In":["root/one"]},
+		"Resolve":{"In":["..."]}
+	}`))
+	if err != nil {
+		t.Fatalf("ReadPermissions should have succeeded, got error: %v", err)
+	}
+	expectPermsReadOnly, err := access.ReadPermissions(bytes.NewBufferString(`{
+		"Admin":{"In":["root/one"]},
+		"Read":{"In":["root/one"]},
+		"Debug":{"In":["root/one"]},
+		"Resolve":{"In":["..."]}
+	}`))
+	if err != nil {
+		t.Fatalf("ReadPermissions should have succeeded, got error: %v", err)
+	}
+
+	// Check that all tables have correct permissions (full or readonly).
+	for _, ts := range sbmodel.MeasuredTables {
+		tb := dbOwner.Table(ts.Prototype.Table())
+		if exists, err := tb.Exists(ctxOwner); err != nil || !exists {
+			t.Errorf("Expected table %s to exist, got: %v (error: %v)", tb.Name(), exists, err)
+		}
+		want := expectPermsFull
+		if ts.ReadOnly {
+			want = expectPermsReadOnly
+		}
+		if got, err := tb.GetPermissions(ctxOwner); err != nil {
+			t.Errorf("GetPermissions should have succeeded, got error: %v", err)
+		} else if got, want = got.Normalize(), want.Normalize(); !reflect.DeepEqual(got, want) {
+			t.Errorf("Unexpected table %s permissions: got %v, want %v", tb.Name(), got, want)
+		}
+	}
+}
diff --git a/go/src/v.io/x/sensorlog/measured/measured.go b/go/src/v.io/x/sensorlog/measured/measured.go
index ef36609..1e442db 100644
--- a/go/src/v.io/x/sensorlog/measured/measured.go
+++ b/go/src/v.io/x/sensorlog/measured/measured.go
@@ -18,6 +18,7 @@
 	_ "v.io/x/ref/runtime/factories/generic"
 	"v.io/x/sensorlog_lite/internal/config"
 	"v.io/x/sensorlog_lite/internal/measure"
+	"v.io/x/sensorlog_lite/internal/sbmodel"
 	"v.io/x/sensorlog_lite/internal/sbutil"
 )
 
@@ -51,7 +52,7 @@
 	}
 	publishMts := v23.GetNamespace(ctx).Roots()
 
-	db, err := sbutil.CreateOrOpenDB(ctx, *flagSbService, config.AllTables)
+	db, err := sbutil.CreateOrOpenDB(ctx, *flagSbService, sbmodel.MeasuredTables)
 	if err != nil {
 		vlog.Errorf("Failed opening Syncbase db: %v", err)
 		return 1