diff --git a/docs/401/401.1.rst b/docs/401/401.1.rst
index ce42ee0..2854fd6 100644
--- a/docs/401/401.1.rst
+++ b/docs/401/401.1.rst
@@ -156,7 +156,7 @@ contractors, etc.)"
 #. Compare counts between `vpn_legacy` and `vpn_authorized`.
    `vpn_authorized`. Why are they different?
 
- 
+
 ----------------------------------------------------
 Exercise 401.1.3 Export `vpn_authorized` to OpenLDAP
 ----------------------------------------------------
@@ -220,7 +220,8 @@ group.
    delegated to a professor.
 #. Add each of these ACLs to `vpn_adhoc`
 
- 
+.. figure:: ../figures/401-vpn-acls-visual.png
+
 +++++++++++++++++++++++++++++++++++
 Professor Johnson's Special Project
 +++++++++++++++++++++++++++++++++++
@@ -252,72 +253,160 @@ we must create a security group and grant it appropriate permissions:
 
 .. figure:: ../figures/401-bsmith458-trace.png
 
-""""""""""""""""""""""
-Put Limits on Policies
-""""""""""""""""""""""
+--------------------------------------------------------
+Exercise 401.1.5 Implement additional policy constraints
+--------------------------------------------------------
 
 It is the IAM team's responsibility to make sure that VPN access is granted to
-the correct subjects.  Putting some limits in place can help make sure improper
-access is not granted.  Attestation makes sure that access which was granted in
+the correct subjects. Putting some limits in place can help make sure improper
+access is not granted. Attestation makes sure that access which was granted in
 the past is still appropriate.
 
-#. Create `ref:iam:global_deny`.  This reference group represents a broad cohort
-   of subjects that should not be granted access to most policies.  Subjects
-   that fall into this category may be:
+#. The `ref:iam:global_deny` reference group represents a broad cohort
+   of subjects that should not be granted access. Subjects that fall into
+   this category may include:
 
-   * Termed "with cause"
+   * Termed with cause
    * Deceased
    * Other reasons
 
-#. Add `ref:iam:global_deny` to the `app:vpn:vpn_deny` policy.
-#. Add attestation requirements to the `app:vpn:ref:vpn_ajohnson409` ACL.
+#. Add `ref:iam:global_deny` to the `vpn_authorized_deny` policy group.
 
-    * Create attestation requirements (30 days).
-    * Review notification settings.
-    * View :guilabel:`home` -> :guilabel:`misc` -> :guilabel:`attestation settings`.
-    * Log in as `ajohnson409` and attest! 
-    * View audit log to see who attested group.
+#. Add 30 day attestation requirements to the `vpn_ajohnson409` ACL.
+   (vpn_ajohnson409 -> More actions -> Attestation -> Attestion actions ->
+   Edit attestion settings...)
 
-#. Add automatic age-off / lifecycle - exceptions only good for 180 days.
-   There are 2 techniques:
+.. figure:: ../figures/401-vpn-attest.png
 
-   * Add member, edit membership, add membership end date.
-   * Better approach, use grouper rule to automatically add end date to
-     members.  See :ref:`the appendix <apdx-401.1.4-auto-end-date>` for
-     details.
+#. Review attestations (Miscellaneous -> Attestation)
 
-#. Use Grouper 2.4 affiliation-based deprovisioning.
+.. figure:: ../figures/401-vpn-misc-attest.png
 
-All access to VPN is now traceable to natural language policy and known
-exceptions! Policy is enforced automatically and kept in sync with changing
-subject attributes. Exceptions are known and managed with a defined
-attestation lifecycle. VPN policy participates in the global deny policy.
+Consultant exceptions should expire automatically after 180 days. There are 2
+techniques to accomplish this in Grouper. The first is to simply edit the
+membership end date after you have added a subject to a group. The second, and
+more reliable, is to have a rule that runs every time a subject is added which
+autotmatically sets the membership end date. Let's implement the second
+approach.
 
-----------------
-Exercise 401.1.5
-----------------
+#. Run `./gte-shell 401.1.1` to get a command prompt.
 
-*CISO is working on a investigation and wants to know if this particular NetID
-"blee172" has access to the VPN now or in the past 90 days?*
+#. Run `./bin/gsh` to start the Grouper shell
 
-#. Navigate to `apps:vpn:vpn_authorized`.
-#. Search for so-and-so.
-#. Open up phpMyAdmin (https://localhost:8443/phpmyadmin/)
-#. Open Views, Go to SQL tab, paste in
-   :ref:`PIT query <apdx-401.1.5-pit-query>`, Go!
+#. Paste in the following gsh script:
+
+.. code-block:: groovy
 
-.. _apdx-401.1.5-pit-query:
+   // Automatically expire vpn_consultant subject memberships in 180 days
+   gs = GrouperSession.startRootSession();
+   numberOfDays = 180;
+   actAs = SubjectFinder.findRootSubject();
+   vpn_consultants = GroupFinder.findByName(gs, "app:vpn:service:ref:vpn_consultants");
+   attribAssign = vpn_consultants.getAttributeDelegate().addAttribute(RuleUtils.ruleAttributeDefName()).getAttributeAssign();
+   attribValueDelegate = attribAssign.getAttributeValueDelegate();
+   attribValueDelegate.assignValue(RuleUtils.ruleActAsSubjectSourceIdName(), actAs.getSourceId());
+   attribValueDelegate.assignValue(RuleUtils.ruleActAsSubjectIdName(), actAs.getId());
+   attribValueDelegate.assignValue(RuleUtils.ruleCheckTypeName(), RuleCheckType.membershipAdd.name());
+   attribValueDelegate.assignValue(RuleUtils.ruleThenEnumName(), RuleThenEnum.assignMembershipDisabledDaysForOwnerGroupId.name());
+   attribValueDelegate.assignValue(RuleUtils.ruleThenEnumArg0Name(), numberOfDays.toString());
+   attribValueDelegate.assignValue(RuleUtils.ruleThenEnumArg1Name(), "T");
 
-----------------
-Exercise 401.1.6
-----------------
+#. Add `jsmith` to `vpn_consultants` and then review the membership end date.
+   (vpn_consultants -> jsmith -> Edit membership and privileges)
 
-*CISO wants to know if anyone on this list of NetIDs has access to the VPN? And
-why?*
+.. figure:: ../figures/401-vpn-add-jsmith.png
 
-#. Import list to a test group.
-#. Intersect with `vpn_authorized`.
-#. Trace membership to determine what level of access and why.
+.. figure:: ../figures/401-vpn-jsmith-end-date.png
+
+---------------------------------------------------
+Exercise 401.1.5 Does "blee172" have access to VPN?
+---------------------------------------------------
+
+The CISO is working on a investigation and wants to know if this particular
+NetID "blee172" has access to the VPN now or in the past 90 days?
+
+#. Navigate to `apps:vpn:vpn_authorized`.
+#. Search for `blee172` and trace membership.
+
+.. figure:: ../figures/401-vpn-trace-blee172.png
+
+Betty currently has access since she is staff.
+The Point-In-Time (PIT) tables know if she's had access in the last 90 days.
+
+3. Log in to phpMyAdmin (https://localhost:8443/phpmyadmin/) with username
+`root` and a blank password.
+
+4. In the database explore, open grouper Views, then go to SQL tab, paste in
+   the following block:
+
+.. code-block:: SQL
+
+   SELECT 
+       gpm.SUBJECT_ID, 
+       gpg.NAME, 
+       FROM_UNIXTIME(gpmav.MEMBERSHIP_START_TIME / 1000000) start_time, 
+       FROM_UNIXTIME(gpmav.MEMBERSHIP_END_TIME / 1000000) end_time 
+   FROM grouper_pit_memberships_all_v gpmav 
+       INNER JOIN grouper_pit_groups gpg 
+           ON gpmav.owner_group_id = gpg.id 
+       INNER JOIN grouper_pit_members gpm 
+           ON gpmav.MEMBER_ID = gpm.id 
+       INNER JOIN grouper_pit_fields gpf 
+           ON gpmav.field_id = gpf.id
+   WHERE gpg.name = 'app:vpn:service:policy:vpn_authorized' 
+   AND gpm.subject_type = 'person'
+   AND gpf.name = 'members'
+   ORDER BY gpmav.MEMBERSHIP_START_TIME DESC 
+   ;
+
+5. Filter rows for `blee172`. This shows Betty's earliest access was
+   2019-06-03.
+
+.. figure:: ../figures/401-vpn-blee172-pit-query.png
+
+---------------------------------------------------
+Exercise 401.1.6 VPN access audit or list of NetIDs
+---------------------------------------------------
+
+CISO wants to know if anyone on this list of NetIDs has access to the VPN? And
+why?
+
+1. Import the following list to `test:vpn:vpn_audit_list`
+
+.. code-block::
+
+  ahenderson36
+  cpeterson37
+  jclark39
+  kbrown62
+  tpeterson63
+  pjohnson64
+  aroberts95
+  sdavis107
+  mhenderson109
+  jvales117
+  sgrady139
+  mprice142
+  mwilliams144
+  lpeterson153
+  mvales154
+  bsmith458
+
+2. Create `test:vpn:vpn_audit`
+
+3. Edit composite on `vpn_audit` and create an interection of `vpn_authorized`
+   and `vpn_audit_list`. This will tell us who in `vpn_audit_list` is also in
+   `vpn_authorized`.
+
+#. Review and trace membership to determine why they have access.
+
+.. figure:: ../figures/401-vpn-audit-list.png
+
+Congrats! All access to VPN is now traceable to natural language policy and
+known exceptions! Policy is enforced automatically and kept in sync with
+changing subject attributes. Exceptions are known and managed with a defined
+attestation lifecycle. Exception managment is distributed and VPN policy
+participates in the global deny policy.
 
 .. _Grouper Deployment Guide: https://spaces.at.internet2.edu/display/Grouper/Grouper+Deployment+Guide+Work+-TIER+Program
 .. _`PSPNG`: https://spaces.at.internet2.edu/x/iwfSBQ
\ No newline at end of file
diff --git a/docs/401/appendix.rst b/docs/401/appendix.rst
index 81a25d4..64f3b6e 100644
--- a/docs/401/appendix.rst
+++ b/docs/401/appendix.rst
@@ -2,71 +2,6 @@
 Appendix
 ========
 
-.. _apdx-401.1.4-auto-end-date:
-
--------------------------------
-401.1.4 Automatic End Date Rule
--------------------------------
-
-To configure the automatic rule end date on the access control list,
-`app:vpn:ref:vpn_adhoc` you must use the Grouper Shell (GSH) to run
-a short script.  To run GSH, you must connect to the GTE container
-that has the Grouper API installed:
-
-.. code-block:: bash
-
-   root# docker exec -it CONTAINER_NAME /bin/bash 
-   bash# cd bin
-   bash# gsh
-
-At this point you can paste in the following script:
-
-.. code-block:: groovy
-   :emphasize-lines: 1,3
-   :linenos:
-
-   numDays = 180;
-   actAs = SubjectFinder.findRootSubject();
-   vpn_adhoc = getGroups("app:vpn:ref:vpn_adhoc")[0];
-   attribAssign = vpn_adhoc.getAttributeDelegate().addAttribute(RuleUtils.ruleAttributeDefName()).getAttributeAssign();
-   attribValueDelegate = attribAssign.getAttributeValueDelegate();
-   attribValueDelegate.assignValue(RuleUtils.ruleActAsSubjectSourceIdName(), actAs.getSourceId());
-   attribValueDelegate.assignValue(RuleUtils.ruleRunDaemonName(), "F");
-   attribValueDelegate.assignValue(RuleUtils.ruleActAsSubjectIdName(), actAs.getId());
-   attribValueDelegate.assignValue(RuleUtils.ruleCheckTypeName(), RuleCheckType.membershipAdd.name());
-   attribValueDelegate.assignValue(RuleUtils.ruleIfConditionEnumName(), RuleIfConditionEnum.thisGroupHasImmediateEnabledNoEndDateMembership.name());
-   attribValueDelegate.assignValue(RuleUtils.ruleThenEnumName(), RuleThenEnum.assignMembershipDisabledDaysForOwnerGroupId.name());
-   attribValueDelegate.assignValue(RuleUtils.ruleThenEnumArg0Name(), numDays.toString());
-   attribValueDelegate.assignValue(RuleUtils.ruleThenEnumArg1Name(), "T");
-
-.. _apdx-401.1.5-pit-query:
-
---------------------------------------
-401.1.5 Point-in-Time Membership Query
---------------------------------------
-
-.. code-block:: sql
-   :linenos:
-
-   SELECT 
-       gpm.SUBJECT_ID, 
-       gpg.NAME, 
-       FROM_UNIXTIME(gpmav.MEMBERSHIP_START_TIME / 1000000) start_time, 
-       FROM_UNIXTIME(gpmav.MEMBERSHIP_END_TIME / 1000000) end_time 
-   FROM grouper_pit_memberships_all_v gpmav 
-       INNER JOIN grouper_pit_groups gpg 
-           ON gpmav.owner_group_id = gpg.id 
-       INNER JOIN grouper_pit_members gpm 
-           ON gpmav.MEMBER_ID = gpm.id 
-       INNER JOIN grouper_pit_fields gpf 
-           ON gpmav.field_id = gpf.id
-   WHERE gpg.name = 'app:vpn:vpn_authorized' 
-   AND gpm.subject_type = 'person'
-   AND gpf.name = 'members'
-   ORDER BY gpmav.MEMBERSHIP_START_TIME DESC 
-   ;
-
-
 .. _apdx-401.2.5-future-memberships-query:
 
 --------------------------------
diff --git a/docs/figures/401-vpn-acls-visual.png b/docs/figures/401-vpn-acls-visual.png
new file mode 100644
index 0000000..a186953
Binary files /dev/null and b/docs/figures/401-vpn-acls-visual.png differ
diff --git a/docs/figures/401-vpn-add-jsmith.png b/docs/figures/401-vpn-add-jsmith.png
new file mode 100644
index 0000000..a828c5a
Binary files /dev/null and b/docs/figures/401-vpn-add-jsmith.png differ
diff --git a/docs/figures/401-vpn-attest.png b/docs/figures/401-vpn-attest.png
new file mode 100644
index 0000000..0e44f5f
Binary files /dev/null and b/docs/figures/401-vpn-attest.png differ
diff --git a/docs/figures/401-vpn-audit-list.png b/docs/figures/401-vpn-audit-list.png
new file mode 100644
index 0000000..ff62416
Binary files /dev/null and b/docs/figures/401-vpn-audit-list.png differ
diff --git a/docs/figures/401-vpn-blee172-pit-query.png b/docs/figures/401-vpn-blee172-pit-query.png
new file mode 100644
index 0000000..a295b01
Binary files /dev/null and b/docs/figures/401-vpn-blee172-pit-query.png differ
diff --git a/docs/figures/401-vpn-misc-attest.png b/docs/figures/401-vpn-misc-attest.png
new file mode 100644
index 0000000..3dab327
Binary files /dev/null and b/docs/figures/401-vpn-misc-attest.png differ
diff --git a/docs/figures/401-vpn-trace-blee172.png b/docs/figures/401-vpn-trace-blee172.png
new file mode 100644
index 0000000..1a28b96
Binary files /dev/null and b/docs/figures/401-vpn-trace-blee172.png differ
diff --git a/ex401/class-files/CisoQuestionalUsers.txt b/ex401/class-files/CisoQuestionalUsers.txt
index 17027ce..1bc1554 100644
--- a/ex401/class-files/CisoQuestionalUsers.txt
+++ b/ex401/class-files/CisoQuestionalUsers.txt
@@ -13,3 +13,4 @@ mprice142
 mwilliams144
 lpeterson153
 mvales154
+bsmith458
diff --git a/ex401/ex401.1.1/container_files/seed-data/bootstrap.gsh b/ex401/ex401.1.1/container_files/seed-data/bootstrap.gsh
index 425716d..eff9434 100644
--- a/ex401/ex401.1.1/container_files/seed-data/bootstrap.gsh
+++ b/ex401/ex401.1.1/container_files/seed-data/bootstrap.gsh
@@ -6,8 +6,7 @@ addRootStem("app", "app");
 addRootStem("org", "org");
 addRootStem("test", "test");
 
-//delStem("401.1.end")
-addRootStem("401.4.1", "401.4.1")
+addRootStem("401.1.1", "401.1.1")
 
 addStem("ref", "iam", "iam");
 addGroup("ref:iam", "global_deny", "global_deny");
@@ -19,7 +18,7 @@ setGroupAttr("etc:rolesLoader", "grouperLoaderType", "SQL_GROUP_LIST");
 setGroupAttr("etc:rolesLoader", "grouperLoaderScheduleType", "CRON");
 setGroupAttr("etc:rolesLoader", "grouperLoaderQuartzCron", "0 * * * * ?");
 setGroupAttr("etc:rolesLoader", "grouperLoaderQuery", "select distinct id as SUBJECT_IDENTIFIER, 'ldap' as SUBJECT_SOURCE_ID, CONCAT('ref:', role) as GROUP_NAME from HR_PEOPLE_ROLES");
-loaderRunOneJob(group);
+// loaderRunOneJob(group);
 
 group = new GroupSave(gs).assignName("etc:deptLoader").assignCreateParentStemsIfNotExist(true).save();
 group.getAttributeDelegate().assignAttribute(LoaderLdapUtils.grouperLoaderLdapAttributeDefName()).getAttributeAssign();
@@ -35,4 +34,4 @@ attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperL
 attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapSubjectExpressionName(), '${subjectAttributes["subjectId"]}');
 attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapGroupNameExpressionName(), 'ref:dept:${groupAttribute}');
 attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapGroupDisplayNameExpressionName(), '${groupAttribute}');
-loaderRunOneJob(group);
+// loaderRunOneJob(group);
diff --git a/ex401/ex401.1.end/Dockerfile b/ex401/ex401.1.end/Dockerfile
index 1bdcb6d..bbaa2e9 100644
--- a/ex401/ex401.1.end/Dockerfile
+++ b/ex401/ex401.1.end/Dockerfile
@@ -1,5 +1,5 @@
 ARG VERSION_TAG
-FROM tier/gte:401.1.6-$VERSION_TAG
+FROM tier/gte:401.1.1-$VERSION_TAG
 
 LABEL author="tier-packaging@internet2.edu <tier-packaging@internet2.edu>" \
       Vendor="TIER" \
diff --git a/ex401/ex401.1.end/container_files/seed-data/bootstrap.gsh b/ex401/ex401.1.end/container_files/seed-data/bootstrap.gsh
index 1bca34b..09630fe 100644
--- a/ex401/ex401.1.end/container_files/seed-data/bootstrap.gsh
+++ b/ex401/ex401.1.end/container_files/seed-data/bootstrap.gsh
@@ -1,6 +1,6 @@
 gs = GrouperSession.startRootSession();
 delStem("401.1.1")
-addRootStem("401.4.end", "401.4.end")
+addRootStem("401.1.end", "401.1.end")
 
 // 401.1.1
 addStem("test", "vpn", "vpn");
@@ -19,7 +19,12 @@ attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperL
 attributeAssign.getAttributeValueDelegate().assignValue(LoaderLdapUtils.grouperLoaderLdapSubjectExpressionName(), "\${loaderLdapElUtils.convertDnToSpecificValue(subjectId)}");
 loaderRunOneJob(group);
 
-//Create the groups that do the grouper math to analyze the tables.
+// stub out loader jobs
+addGroup("ref", "faculty", "faculty");
+addGroup("ref", "staff", "staff");
+addGroup("ref", "student", "student");
+
+// Create the groups that do the grouper math to analyze the tables.
 addGroup("test:vpn", "vpn_faculty", "vpn_faculty");
 addComposite("test:vpn:vpn_faculty", CompositeType.INTERSECTION, "test:vpn:vpn_legacy", "ref:faculty");
 addGroup("test:vpn", "vpn_staff", "vpn_staff");
@@ -79,26 +84,64 @@ addMember("app:vpn:security:vpn_ajohnson409_mgr", "ajohnson409")
 GrouperSession.start(findSubject("ajohnson409"))
 addMember("app:vpn:service:ref:vpn_ajohnson409", "bsmith458")
 
-
-
-
-addGroup("test", "cisoQuestionableVpnUsersList", "CISO VPN Questionable VPN List");
-addMember("test:cisoQuestionableVpnUsersList","ahenderson36");
-addMember("test:cisoQuestionableVpnUsersList","cpeterson37");
-addMember("test:cisoQuestionableVpnUsersList","jclark39");
-addMember("test:cisoQuestionableVpnUsersList","kbrown62");
-addMember("test:cisoQuestionableVpnUsersList","tpeterson63");
-addMember("test:cisoQuestionableVpnUsersList","pjohnson64");
-addMember("test:cisoQuestionableVpnUsersList","aroberts95");
-addMember("test:cisoQuestionableVpnUsersList","sdavis107");
-addMember("test:cisoQuestionableVpnUsersList","mhenderson109");
-addMember("test:cisoQuestionableVpnUsersList","jvales117");
-addMember("test:cisoQuestionableVpnUsersList","sgrady139");
-addMember("test:cisoQuestionableVpnUsersList","mprice142");
-addMember("test:cisoQuestionableVpnUsersList","mwilliams144");
-addMember("test:cisoQuestionableVpnUsersList","lpeterson153");
-addMember("test:cisoQuestionableVpnUsersList","mvales154");
-
-addGroup("test", "whyvpnaccess", "Why Do They Have VPN Access");
-addComposite("test:whyvpnaccess", CompositeType.INTERSECTION, "app:vpn:vpn_authorized", "test:cisoQuestionableVpnUsersList");
+// 401.1.5
+// Attestation requirement
+group = GroupFinder.findByName(gs, "app:vpn:service:ref:vpn_ajohnson409");
+attribute = AttributeDefNameFinder.findByName("etc:attribute:attestation:attestation", true);
+attributeAssignSave = new AttributeAssignSave(gs).assignPrintChangesToSystemOut(true);
+attributeAssignSave.assignAttributeDefName(attribute);
+attributeAssignSave.assignOwnerGroup(group);
+
+attributeAssignOnAssignSave = new AttributeAssignSave(gs);
+attributeAssignOnAssignSave.assignAttributeAssignType(AttributeAssignType.group_asgn);
+attestationSendEmailAttributeDefName = AttributeDefNameFinder.findByName("etc:attribute:attestation:attestationSendEmail", false);
+attributeAssignOnAssignSave.assignAttributeDefName(attestationSendEmailAttributeDefName);
+attributeAssignOnAssignSave.addValue("true");
+attributeAssignSave.addAttributeAssignOnThisAssignment(attributeAssignOnAssignSave);
+
+attributeAssignOnAssignSave = new AttributeAssignSave(gs);
+attributeAssignOnAssignSave.assignAttributeAssignType(AttributeAssignType.group_asgn);
+attributeDefName = AttributeDefNameFinder.findByName("etc:attribute:attestation:attestationDirectAssignment", false);
+attributeAssignOnAssignSave.assignAttributeDefName(attributeDefName);
+attributeAssignOnAssignSave.addValue("true");
+attributeAssignSave.addAttributeAssignOnThisAssignment(attributeAssignOnAssignSave);
+
+attributeAssign = attributeAssignSave.save();
+
+// Automatically expire vpn_consultant subject memberships in 180 days
+numberOfDays = 180;
+actAs = SubjectFinder.findRootSubject();
+vpn_consultants = GroupFinder.findByName(gs, "app:vpn:service:ref:vpn_consultants");
+attribAssign = vpn_consultants.getAttributeDelegate().addAttribute(RuleUtils.ruleAttributeDefName()).getAttributeAssign();
+attribValueDelegate = attribAssign.getAttributeValueDelegate();
+attribValueDelegate.assignValue(RuleUtils.ruleActAsSubjectSourceIdName(), actAs.getSourceId());
+attribValueDelegate.assignValue(RuleUtils.ruleActAsSubjectIdName(), actAs.getId());
+attribValueDelegate.assignValue(RuleUtils.ruleCheckTypeName(), RuleCheckType.membershipAdd.name());
+attribValueDelegate.assignValue(RuleUtils.ruleThenEnumName(), RuleThenEnum.assignMembershipDisabledDaysForOwnerGroupId.name());
+attribValueDelegate.assignValue(RuleUtils.ruleThenEnumArg0Name(), numberOfDays.toString());
+attribValueDelegate.assignValue(RuleUtils.ruleThenEnumArg1Name(), "T");
+
+addMember("app:vpn:service:ref:vpn_consultants", "jsmith")
+
+// 401.1.4 VPN access audit for list of NetIDs
+addGroup("test:vpn", "vpn_audit_list", "vpn_audit_list");
+addMember("vpn_audit_list","ahenderson36");
+addMember("vpn_audit_list","cpeterson37");
+addMember("vpn_audit_list","jclark39");
+addMember("vpn_audit_list","kbrown62");
+addMember("vpn_audit_list","tpeterson63");
+addMember("vpn_audit_list","pjohnson64");
+addMember("vpn_audit_list","aroberts95");
+addMember("vpn_audit_list","sdavis107");
+addMember("vpn_audit_list","mhenderson109");
+addMember("vpn_audit_list","jvales117");
+addMember("vpn_audit_list","sgrady139");
+addMember("vpn_audit_list","mprice142");
+addMember("vpn_audit_list","mwilliams144");
+addMember("vpn_audit_list","lpeterson153");
+addMember("vpn_audit_list","mvales154");
+addMember("vpn_audit_list","bsmith458");
+
+addGroup("test:vpn", "vpn_audit", "vpn_audit");
+addComposite("test:vpn:vpn_audit", CompositeType.INTERSECTION, "app:vpn:service:policy:vpn_authorized", "test:vpn:vpn_audit_list");
 
diff --git a/ex401/manualBuild.sh b/ex401/manualBuild.sh
index 5170708..26bc28c 100755
--- a/ex401/manualBuild.sh
+++ b/ex401/manualBuild.sh
@@ -1,11 +1,6 @@
 source ../buildVersion.sh
 echo "Building gte:401 version ${VERSION_TAG}"
 docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.1.1-${VERSION_TAG} ex401.1.1 \
-&& docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.1.2-${VERSION_TAG} ex401.1.2 \
-&& docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.1.3-${VERSION_TAG} ex401.1.3 \
-&& docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.1.4-${VERSION_TAG} ex401.1.4 \
-&& docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.1.5-${VERSION_TAG} ex401.1.5 \
-&& docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.1.6-${VERSION_TAG} ex401.1.6 \
 && docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.1.end-${VERSION_TAG} ex401.1.end \
 && docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.2.1-${VERSION_TAG} ex401.2.1 \
 && docker build --build-arg VERSION_TAG=${VERSION_TAG} --tag=tier/gte:401.2.2-${VERSION_TAG} ex401.2.2 \