Repository: zimeon/ocfl-py (language: python)

Issue:
Fix handling of spec version in standalone inventory check
To replicate example:
```
(py38) simeon@RottenApple ocfl-py> ./ocfl-validate.py extra_fixtures/1.1/good-objects/empty_fixity
INFO:ocfl.object:OCFL object at extra_fixtures/1.1/good-objects/empty_fixity is VALID
(py38) simeon@RottenApple ocfl-py> ./ocfl-validate.py extra_fixtures/1.1/good-objects/empty_fixity/inventory.json
[E038] OCFL Object standalone inventory `type` attribute has wrong value (see https://ocfl.io/1.0/spec/#E038)
INFO:ocfl.object:Standalone OCFL inventory at extra_fixtures/1.1/good-objects/empty_fixity/inventory.json is INVALID
```

Inventory is good, it is just v1.1
Add test for E103, bad spec version sequence
Fixes #96
Tidy and deal with new pylint 2.14.1



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml
new file mode 100644
index 00000000..1f549bc9
--- /dev/null
+++ b/.github/workflows/action.yml
@@ -0,0 +1,38 @@
+name: Tests for ocfl-py
+
+on: [push]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.6", "3.7", "3.8", "3.9"]
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: true
+          recursive: true
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+          cache: 'pip'
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements.txt
+          pip install pytest pycodestyle pydocstyle pylint  # For testing and linting
+      - name: Lint with pycodestyle
+        run: |
+          pycodestyle --ignore=E501,W503 *.py ocfl tests
+      - name: Lint with pydocstyle
+        run: |
+          pydocstyle *.py ocfl tests
+      - name: Lint with pyliny
+        run: |
+          pylint --disable=unneeded-not,line-too-long,unnecessary-semicolon,trailg-whitespace,missing-final-newline,bad-indentation,multiple-statements,bare-except,missing-module-docstring,missing-class-docstring,missing-function-docstring,W0511,W0622,W0707,C0103,C0209,R0902,R0911,R0912,R0913,R0914,R0915,R1702 *.py ocfl tests
+      - name: Test with pytest
+        run: |
+          pytest
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 71a1c7a4..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-dist: bionic
-language: python
-python:
-  - "3.6"
-  - "3.7"
-  - "3.8"
-  - "3.9"
-# Install sub-module using public HTTPS not SSH 
-git:
-  submodules: false
-before_install:
-  - sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
-  - git submodule update --init --recursive
-install:
-  - pip install bagit  #pylint is unhappy with bagit install via setup.py, not sure why
-  - pip install coveralls pycodestyle pydocstyle pylint
-  - python setup.py install
-script:
-  - pycodestyle --ignore=E501,W503 *.py ocfl tests
-  - pydocstyle *.py ocfl tests
-  - pylint --disable=unneeded-not,line-too-long,unnecessary-semicolon,trailg-whitespace,missing-final-newline,bad-indentation,multiple-statements,bare-except,missing-module-docstring,missing-class-docstring,missing-function-docstring,W0511,W0622,W0707,C0103,R0902,R0911,R0912,R0913,R0914,R0915,R1702 *.py ocfl tests
-  - python setup.py test
-after_success:
-  - python setup.py coverage
-  - coveralls
diff --git a/CHANGES.md b/CHANGES.md
index d7167bfc..70c764be 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,13 +1,17 @@
 # ocfl-py changelog
 
-## 2021-??-?? v1.2.3
+## 2022-04-26 v1.3.0
 
+  * Add preliminary handling of OCFL v1.1 (https://github.com/zimeon/ocfl-py/issues/81)
   * Additional validation improvements:
     * Checks between version state in different version inventories
     * Check to see is extra directories look like version directories
     * Fix URI scheme syntax check (https://github.com/zimeon/ocfl-py/issues/75)
     * Check extensions directory in storage root (https://github.com/zimeon/ocfl-py/issues/76)
   * Use additional fixtures in https://github.com/OCFL/fixtures for tests
+  * Improve handling of inconsistent state between version inventories errors (E066, https://github.com/zimeon/ocfl-py/issues/85)
+  * Improve handling of fixity block issues (E056/E111, https://github.com/zimeon/ocfl-py/issues/79, https://github.com/zimeon/ocfl-py/issues/87)
+  * Running tests on Python 3.6, 3.7, 3.8, 3.9
 
 ## 2021-04-26 v1.2.2
 
diff --git a/README b/README
index 8ea77c19..009c584a 100644
--- a/README
+++ b/README
@@ -2,24 +2,27 @@
 ocfl-py - An OCFL implementation in Python
 ==========================================
 
-.. image:: https://travis-ci.com/zimeon/ocfl-py.svg?branch=main
-  :target: https://travis-ci.com/zimeon/ocfl-py
-.. image:: https://coveralls.io/repos/github/zimeon/ocfl-py/badge.svg?branch=main
-  :target: https://coveralls.io/github/zimeon/ocfl-py?branch=main
-
-Attempts to follow `OCFL specification v1.0
-<https://ocfl.io/1.0/spec/>`_, see `implementation status for errors and warnings
+`ocfl-py` is a Python reference implementation of the Oxford Common File Layout
+(OCFL). It also provides a number of command-line tools that may be useful for
+validating and manipulating OCFL Objects and OCFL Storage Roots. The code
+attempts to follow the current `Draft OCFL specification v1.1
+<https://ocfl.io/draft/spec/>`_ while retaining support for `v1.0
+<https://ocfl.io/1.0/spec/>`_. See `implementation status for errors and warnings
 <https://github.com/zimeon/ocfl-py/blob/main/docs/validation_status.md>`_ for
 details of validator support.
 
+See also `OCFL-Core
+<https://github.com/inveniosoftware/ocflcore>`_ which is another Python implementation
+of OCFL, designed to support the InvenioRDM repository, and `other OCFL implementations
+<https://github.com/OCFL/spec/wiki/Implementations>`_.
+
 Installing
 ----------
 
 This code requires Python 3.
 
-This code attempts to support the OCFL specification v1.0 and additional
-developments. To get the most up to date version check out the ``main``
-branch from github (or if you are reckless you can try the ``develop`` branch).
+This code attempts to support the OCFL specifications v1.1 draft and v1.0. To get
+the most up to date version check out the ``main`` branch from github.
 
 I hope to keep a fairly current version on `PyPI
 <https://pypi.org/project/ocfl-py/>`_, which can be installed or upgraded
@@ -33,7 +36,7 @@ The latest version is in the `main` branch on `github
 Use
 ---
 
-There should then be three scripts available:
+There should then be four command-line scripts available:
 
 - ``ocfl-validate.py`` - validate OCFL objects, OCFL storage roots or standalone OCFL inventory files
 - ``ocfl-object.py`` - build, manipulate, extract from or validate an OCFL object
@@ -60,7 +63,7 @@ for guidelines for contributing.
 Copyright and License
 ---------------------
 
-Copyright 2018--2021 Simeon Warner and `contributors
+Copyright 2018--2022 Simeon Warner and `contributors
 <https://github.com/zimeon/ocfl-py/graphs/contributors>`_.
 Provided under the MIT license, see `LICENSE.txt
 <https://github.com/zimeon/ocfl-py/blob/main/LICENSE.txt>`_.
diff --git a/docs/demo_build_spec_examples.md b/docs/demo_build_spec_examples.md
index f358e333..9df3097e 100644
--- a/docs/demo_build_spec_examples.md
+++ b/docs/demo_build_spec_examples.md
@@ -90,7 +90,7 @@ WARNING:ocfl.object:### Inventory for v1{
   "type": "https://ocfl.io/1.0/spec/#inventory",
   "versions": {
     "v1": {
-      "created": "2021-04-26T13:25:35.957146Z",
+      "created": "2022-04-26T14:47:17.394880Z",
       "state": {
         "7dcc352f96c56dc...c31": [
           "foo/bar.xml"
@@ -157,7 +157,7 @@ WARNING:ocfl.object:### Inventory for v2{
   "type": "https://ocfl.io/1.0/spec/#inventory",
   "versions": {
     "v1": {
-      "created": "2021-04-26T13:25:35.957146Z",
+      "created": "2022-04-26T14:47:17.394880Z",
       "state": {
         "7dcc352f96c56dc...c31": [
           "foo/bar.xml"
@@ -171,7 +171,7 @@ WARNING:ocfl.object:### Inventory for v2{
       }
     },
     "v2": {
-      "created": "2021-04-26T13:25:35.958734Z",
+      "created": "2022-04-26T14:47:17.397084Z",
       "state": {
         "4d27c86b026ff70...b53": [
           "foo/bar.xml"
@@ -236,7 +236,7 @@ WARNING:ocfl.object:### Inventory for v3{
   "type": "https://ocfl.io/1.0/spec/#inventory",
   "versions": {
     "v1": {
-      "created": "2021-04-26T13:25:35.957146Z",
+      "created": "2022-04-26T14:47:17.394880Z",
       "state": {
         "7dcc352f96c56dc...c31": [
           "foo/bar.xml"
@@ -250,7 +250,7 @@ WARNING:ocfl.object:### Inventory for v3{
       }
     },
     "v2": {
-      "created": "2021-04-26T13:25:35.958734Z",
+      "created": "2022-04-26T14:47:17.397084Z",
       "state": {
         "4d27c86b026ff70...b53": [
           "foo/bar.xml"
@@ -262,7 +262,7 @@ WARNING:ocfl.object:### Inventory for v3{
       }
     },
     "v3": {
-      "created": "2021-04-26T13:25:35.959946Z",
+      "created": "2022-04-26T14:47:17.399480Z",
       "state": {
         "4d27c86b026ff70...b53": [
           "foo/bar.xml"
diff --git a/docs/demo_ocfl_object_script.md b/docs/demo_ocfl_object_script.md
index 28e3b513..b751dd7a 100644
--- a/docs/demo_ocfl_object_script.md
+++ b/docs/demo_ocfl_object_script.md
@@ -10,7 +10,7 @@ The `--version` argument will show version number and exit
 
 ```
 > python ocfl-object.py --version
-ocfl-object.py is part of ocfl-py version 1.2.2
+ocfl-object.py is part of ocfl-py version 1.3.0
 ```
 
 
@@ -34,7 +34,7 @@ WARNING:ocfl.object:### Inventory for v1{
   "type": "https://ocfl.io/1.0/spec/#inventory",
   "versions": {
     "v1": {
-      "created": "2021-04-26T13:25:37.573161Z",
+      "created": "2022-04-26T14:47:18.794059Z",
       "state": {
         "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
           "a_file.txt"
@@ -65,7 +65,7 @@ WARNING:ocfl.object:### Inventory for v1{
   "type": "https://ocfl.io/1.0/spec/#inventory",
   "versions": {
     "v1": {
-      "created": "2021-04-26T13:25:38.047154Z",
+      "created": "2022-04-26T14:47:19.184724Z",
       "state": {
         "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
           "a_file.txt"
@@ -90,7 +90,7 @@ WARNING:ocfl.object:### Inventory for v2{
   "type": "https://ocfl.io/1.0/spec/#inventory",
   "versions": {
     "v1": {
-      "created": "2021-04-26T13:25:38.047154Z",
+      "created": "2022-04-26T14:47:19.184724Z",
       "state": {
         "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
           "a_file.txt"
@@ -98,7 +98,7 @@ WARNING:ocfl.object:### Inventory for v2{
       }
     },
     "v2": {
-      "created": "2021-04-26T13:25:38.047821Z",
+      "created": "2022-04-26T14:47:19.185642Z",
       "state": {
         "296e72b8fd5f7f0ac1473993600ae34953d5dab646f17e7b182b8648aff830d7bf01b56490777cb3e72b33fcc1ae520506badea1032252d1a55fd7362e269975": [
           "a_file.txt"
@@ -123,7 +123,7 @@ WARNING:ocfl.object:### Inventory for v3{
   "type": "https://ocfl.io/1.0/spec/#inventory",
   "versions": {
     "v1": {
-      "created": "2021-04-26T13:25:38.047154Z",
+      "created": "2022-04-26T14:47:19.184724Z",
       "state": {
         "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
           "a_file.txt"
@@ -131,7 +131,7 @@ WARNING:ocfl.object:### Inventory for v3{
       }
     },
     "v2": {
-      "created": "2021-04-26T13:25:38.047821Z",
+      "created": "2022-04-26T14:47:19.185642Z",
       "state": {
         "296e72b8fd5f7f0ac1473993600ae34953d5dab646f17e7b182b8648aff830d7bf01b56490777cb3e72b33fcc1ae520506badea1032252d1a55fd7362e269975": [
           "a_file.txt"
@@ -139,7 +139,7 @@ WARNING:ocfl.object:### Inventory for v3{
       }
     },
     "v3": {
-      "created": "2021-04-26T13:25:38.048884Z",
+      "created": "2022-04-26T14:47:19.186428Z",
       "state": {
         "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
           "a_file.txt"
diff --git a/docs/demo_ocfl_sidecar_script.md b/docs/demo_ocfl_sidecar_script.md
index 0871aa47..0aebbf21 100644
--- a/docs/demo_ocfl_sidecar_script.md
+++ b/docs/demo_ocfl_sidecar_script.md
@@ -10,7 +10,7 @@ The `--version` argument will show version number and exit
 
 ```
 > python ocfl-sidecar.py --version
-ocfl-sidecar.py is part of ocfl-py version 1.2.2
+ocfl-sidecar.py is part of ocfl-py version 1.3.0
 ```
 
 
diff --git a/docs/demo_ocfl_store_script.md b/docs/demo_ocfl_store_script.md
index 136d8809..409df001 100644
--- a/docs/demo_ocfl_store_script.md
+++ b/docs/demo_ocfl_store_script.md
@@ -10,7 +10,7 @@ The `--version` argument will show version number and exit (but we still tave to
 
 ```
 > python ocfl-store.py --version --root=tmp/root --list
-ocfl-store.py is part of ocfl-py version 1.2.2
+ocfl-store.py is part of ocfl-py version 1.3.0
 ```
 
 
diff --git a/docs/demo_using_bagit_bags.md b/docs/demo_using_bagit_bags.md
index b3132950..ad4aea7d 100644
--- a/docs/demo_using_bagit_bags.md
+++ b/docs/demo_using_bagit_bags.md
@@ -161,8 +161,8 @@ Taking the newly created OCFL object `/tmp/obj` we can `--extract` the `v4` cont
 INFO:ocfl.object:Extracted v4 into tmp/extracted_v4
 INFO:bagit:Creating bag for directory tmp/extracted_v4
 INFO:bagit:Creating data directory
-INFO:bagit:Moving my_content to /privatetmp/extracted_v4/tmp32x9b1fz/my_content
-INFO:bagit:Moving /privatetmp/extracted_v4/tmp32x9b1fz to data
+INFO:bagit:Moving my_content to /privatetmp/extracted_v4/tmpmnmylt6l/my_content
+INFO:bagit:Moving /privatetmp/extracted_v4/tmpmnmylt6l to data
 INFO:bagit:Using 1 processes to generate manifests: sha512
 INFO:bagit:Generating manifest lines for file data/my_content/dracula.txt
 INFO:bagit:Generating manifest lines for file data/my_content/dunwich.txt
@@ -184,12 +184,12 @@ We note that the OCFL object had only one `content` file in `v4` but the extract
 diff -r tmp/extracted_v4/bag-info.txt tests/testdata/bags/uaa_v4/bag-info.txt
 1,2c1
 < Bag-Software-Agent: bagit.py v1.8.1 <https://github.com/LibraryOfCongress/bagit-python>
-< Bagging-Date: 2021-04-26
+< Bagging-Date: 2022-04-26
 ---
 > Bagging-Date: 2020-01-04
 diff -r tmp/extracted_v4/tagmanifest-sha512.txt tests/testdata/bags/uaa_v4/tagmanifest-sha512.txt
 2c2
-< 18c8191c83d80fdcf836098d3392665caf2f23cb1d1ec20ec8dd5af473b695245f0f991a579e045a04a736b8c2a6c963556412b3f510038f424322e882a8e881 bag-info.txt
+< 7dd1a757393da8b7513d149e185edd05598dfe48dd20ba5dde166f29eaf59bbf08c173718b3bc2976945c7a48aa52516c69a7d57a7c3b2b796247ad67e28f4bf bag-info.txt
 ---
 > 10624e6d45462def7af66d1a0d977606c7b073b01809c1d42258cfab5c34a275480943cbe78044416aee1f23822cc3762f92247b8f39b5c6ddc5ae32a8f94ce5 bag-info.txt
 ```
diff --git a/docs/validation_status.md b/docs/validation_status.md
index 881edaac..34bb78a7 100644
--- a/docs/validation_status.md
+++ b/docs/validation_status.md
@@ -6,184 +6,150 @@ The following tables show the implementation status of all errors and warnings i
 
 | Code | Specification text (or suffixed code) | Implementation status and message/links |
 | --- | --- | --- |
-| [E001](https://ocfl.io/1.0/spec#E001) | 'The OCFL Object Root must not contain files or directories other than those specified in the following sections.' | _See multiple cases identified with suffixes below_ |
-| | E001a | OCFL Object root contains unexpected file: %s \[[ocfl/validator.py#L173](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L173)\] |
-| | E001b | OCFL Object root contains unexpected directory: %s \[[ocfl/validator.py#L180](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L180)\] |
-| | E001c | OCFL Object root contains unexpected entry that isn't a file or directory: %s \[[ocfl/validator.py#L182](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L182)\] |
-| [E002](https://ocfl.io/1.0/spec#E002) | 'The version declaration must be formatted according to the NAMASTE specification.' | NOTE - E002 is redundant to more specific errors E003, E004, E005, E006. \[_Not implemented_\] |
-| [E003](https://ocfl.io/1.0/spec#E003) | '[The version declaration] must be a file in the base directory of the OCFL Object Root giving the OCFL version in the filename.' | _See multiple cases identified with suffixes below_ |
-| | E003a | OCFL Object version declaration file is missing \[[ocfl/validator.py#L85](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L85)\] |
-| | E003b | OCFL Object includes more that one file that looks like an object declaration (got %s) \[[ocfl/validator.py#L87](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L87)\] |
-| | E003c | No OCFL Object to validate at path %s. The root of an OCFL Object must be a directory containing an object declaration \[[ocfl/validator.py#L80](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L80)\] |
-| | E003d | OCFL Storage Root hierarchy includes directory %s with more that one file that looks like an object declaration, ignoring \[[ocfl/store.py#L158](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L158)\] |
-| [E004](https://ocfl.io/1.0/spec#E004) | 'The [version declaration] filename MUST conform to the pattern T=dvalue, where T must be 0, and dvalue must be ocfl_object_, followed by the OCFL specification version number.' | _See multiple cases identified with suffixes below_ |
-| | E004a | OCFL Storage Root hierarchy includes directory %s with an object declaration giving unknown version %s, ignoring \[[ocfl/store.py#L165](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L165)\] |
-| | E004b | OCFL Storage Root hierarchy includes directory %s with an unrecognized object declaration %s, ignoring \[[ocfl/store.py#L167](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L167)\] |
-| [E005](https://ocfl.io/1.0/spec#E005) | 'The [version declaration] filename must conform to the pattern T=dvalue, where T MUST be 0, and dvalue must be ocfl_object_, followed by the OCFL specification version number.' | _Not implemented_ |
-| [E006](https://ocfl.io/1.0/spec#E006) | 'The [version declaration] filename must conform to the pattern T=dvalue, where T must be 0, and dvalue MUST be ocfl_object_, followed by the OCFL specification version number.' | _Not implemented_ |
-| [E007](https://ocfl.io/1.0/spec#E007) | 'The text contents of the [version declaration] file must be the same as dvalue, followed by a newline (\n).' | OCFL Object declaration file contents do not match file name without leading 0= (the 'dvalue') \[[ocfl/validator.py#L89](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L89)\] |
-| [E008](https://ocfl.io/1.0/spec#E008) | 'OCFL Object content must be stored as a sequence of one or more versions.' | OCFL Object %s inventory versions block does not contain any versions, there must be at least version 1 \[[ocfl/inventory_validator.py#L223](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L223)\] |
-| [E009](https://ocfl.io/1.0/spec#E009) | 'The version number sequence MUST start at 1 and must be continuous without missing integers.' | OCFL Object %s inventory versions block does not contain v1 or a zero padded equivalent \[[ocfl/inventory_validator.py#L243](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L243)\] |
-| [E010](https://ocfl.io/1.0/spec#E010) | 'The version number sequence must start at 1 and MUST be continuous without missing integers.' | OCFL Object %s inventory versions block includes an out-of-sequence version \[[ocfl/inventory_validator.py#L254](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L254)\] |
-| [E011](https://ocfl.io/1.0/spec#E011) | 'If zero-padded version directory numbers are used then they must start with the prefix v and then a zero.' | **Missing description** \[[ocfl/inventory_validator.py#L261](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L261)\] |
-| [E012](https://ocfl.io/1.0/spec#E012) | 'All version directories of an object must use the same naming convention: either a non-padded version directory number, or a zero-padded version directory number of consistent length.' | _Not implemented_ |
-| [E013](https://ocfl.io/1.0/spec#E013) | 'Operations that add a new version to an object must follow the version directory naming convention established by earlier versions.' | _Not implemented_ |
-| [E014](https://ocfl.io/1.0/spec#E014) | 'In all cases, references to files inside version directories from inventory files must use the actual version directory names.' | _Not implemented_ |
-| [E015](https://ocfl.io/1.0/spec#E015) | 'There must be no other files as children of a version directory, other than an inventory file and a inventory digest.' | OCFL Object version directory %s includes an illegal file (%s) \[[ocfl/validator.py#L289](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L289)\] |
-| [E016](https://ocfl.io/1.0/spec#E016) | 'Version directories must contain a designated content sub-directory if the version contains files to be preserved, and should not contain this sub-directory otherwise.' | _Not implemented_ |
-| [E017](https://ocfl.io/1.0/spec#E017) | 'The contentDirectory value MUST NOT contain the forward slash (/) path separator and must not be either one or two periods (. or ..).' | OCFL Object %s inventory contentDirectory must be a string and must not contain a forward slash (/) \[[ocfl/inventory_validator.py#L86](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L86)\] |
-| [E018](https://ocfl.io/1.0/spec#E018) | 'The contentDirectory value must not contain the forward slash (/) path separator and MUST NOT be either one or two periods (. or ..).' | OCFL Object %s inventory contentDirectory must not be either . or .. \[[ocfl/inventory_validator.py#L88](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L88)\] |
-| [E019](https://ocfl.io/1.0/spec#E019) | 'If the key contentDirectory is set, it MUST be set in the first version of the object and must not change between versions of the same object.' | _Not implemented_ |
-| [E020](https://ocfl.io/1.0/spec#E020) | 'If the key contentDirectory is set, it must be set in the first version of the object and MUST NOT change between versions of the same object.' | _Not implemented_ |
-| [E021](https://ocfl.io/1.0/spec#E021) | 'If the key contentDirectory is not present in the inventory file then the name of the designated content sub-directory must be content.' | _Not implemented_ |
-| [E022](https://ocfl.io/1.0/spec#E022) | 'OCFL-compliant tools (including any validators) must ignore all directories in the object version directory except for the designated content directory.' | _Not implemented_ |
-| [E023](https://ocfl.io/1.0/spec#E023) | 'Every file within a version\'s content directory must be referenced in the manifest section of the inventory.' | _See multiple cases identified with suffixes below_ |
-| | E023a | OCFL Object includes one or more files that are not mentioned in the  %s inventory manifest (%s) \[[ocfl/validator.py#L328](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L328)\] |
-| | E023b | OCFL Object %s manifest does not include files listed in previous version manifests (%s) \[[ocfl/validator.py#L243](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L243)\] |
-| [E024](https://ocfl.io/1.0/spec#E024) | 'There must not be empty directories within a version\'s content directory.' | OCFL Object version %s content directory includes empty path %s \[[ocfl/validator.py#L280](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L280)\] |
-| [E025](https://ocfl.io/1.0/spec#E025) | 'For content-addressing, OCFL Objects must use either sha512 or sha256, and should use sha512.' | _See multiple cases identified with suffixes below_ |
-| | E025a | OCFL Object %s inventory manifest block includes a digest (%s) that doesn't have the correct form for the %s algorithm \[[ocfl/inventory_validator.py#L145](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L145)\] |
-| [E026](https://ocfl.io/1.0/spec#E026) | 'For storage of additional fixity values, or to support legacy content migration, implementers must choose from the following controlled vocabulary of digest algorithms, or from a list of additional algorithms given in the [Digest-Algorithms-Extension].' | _See multiple cases identified with suffixes below_ |
-| | E026a | OCFL Object %s inventory uses unknown digest type %s \[[ocfl/inventory_validator.py#L384](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L384)\] |
-| [E027](https://ocfl.io/1.0/spec#E027) | 'OCFL clients must support all fixity algorithms given in the table below, and may support additional algorithms from the extensions.' | _Not implemented_ |
-| [E028](https://ocfl.io/1.0/spec#E028) | 'Optional fixity algorithms that are not supported by a client must be ignored by that client.' | _Not implemented_ |
-| [E029](https://ocfl.io/1.0/spec#E029) | 'SHA-1 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ |
-| [E030](https://ocfl.io/1.0/spec#E030) | 'SHA-256 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ |
-| [E031](https://ocfl.io/1.0/spec#E031) | 'SHA-512 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ |
-| [E032](https://ocfl.io/1.0/spec#E032) | '[blake2b-512] must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ |
-| [E033](https://ocfl.io/1.0/spec#E033) | 'An OCFL Object Inventory MUST follow the [JSON] structure described in this section and must be named inventory.json.' | OCFL Object %s inventory is not valid JSON (%s) \[[ocfl/validator.py#L126](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L126) [ocfl/object.py#L544](https://github.com/zimeon/ocfl-py/blob/main/ocfl/object.py#L544)\] |
-| [E034](https://ocfl.io/1.0/spec#E034) | 'An OCFL Object Inventory must follow the [JSON] structure described in this section and MUST be named inventory.json.' | _Not implemented_ |
-| [E035](https://ocfl.io/1.0/spec#E035) | 'The forward slash (/) path separator must be used in content paths in the manifest and fixity blocks within the inventory.' | _Not implemented_ |
-| [E036](https://ocfl.io/1.0/spec#E036) | 'An OCFL Object Inventory must include the following keys: [id, type, digestAlgorithm, head]' | _See multiple cases identified with suffixes below_ |
-| | E036a | OCFL Object %s inventory missing `id` attribute \[[ocfl/inventory_validator.py#L66](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L66)\] |
-| | E036b | OCFL Object %s inventory missing `type` attribute \[[ocfl/inventory_validator.py#L68](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L68)\] |
-| | E036c | OCFL Object %s inventory missing `digestAlgorithm` attribute \[[ocfl/inventory_validator.py#L72](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L72)\] |
-| | E036d | OCFL Object %s inventory missing `head` attribute \[[ocfl/inventory_validator.py#L103](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L103)\] |
-| [E037](https://ocfl.io/1.0/spec#E037) | '[id] must be unique in the local context, and should be a URI [RFC3986].' | _See multiple cases identified with suffixes below_ |
-| | E037a | OCFL Object %s inventory `id` attribute is empty or badly formed \[[ocfl/inventory_validator.py#L60](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L60)\] |
-| | E037b | OCFL Object %s inventory id `%s` does not match the value in the root inventory `%s` \[[ocfl/validator.py#L233](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L233)\] |
-| [E038](https://ocfl.io/1.0/spec#E038) | 'In the object root inventory [the type value] must be the URI of the inventory section of the specification version matching the object conformance declaration.' | OCFL Object %s inventory `type` attribute has wrong value \[[ocfl/inventory_validator.py#L70](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L70)\] |
-| [E039](https://ocfl.io/1.0/spec#E039) | '[digestAlgorithm] must be the algorithm used in the manifest and state blocks.' | OCFL Object %s inventory `digestAlgorithm` attribute not an allowed value (got '%s') \[[ocfl/inventory_validator.py#L81](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L81)\] |
-| [E040](https://ocfl.io/1.0/spec#E040) | [head] must be the version directory name with the highest version number.' | OCFL Object %s inventory head attribute doesn't match versions (got %s, expected %s) \[[ocfl/inventory_validator.py#L107](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L107)\] |
-| [E041](https://ocfl.io/1.0/spec#E041) | 'In addition to these keys, there must be two other blocks present, manifest and versions, which are discussed in the next two sections.' | _See multiple cases identified with suffixes below_ |
-| | E041a | OCFL Object %s inventory missing `manifest` attribute \[[ocfl/inventory_validator.py#L93](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L93)\] |
-| | E041b | OCFL Object %s inventory missing `versions` attribute \[[ocfl/inventory_validator.py#L98](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L98)\] |
-| | E041c | OCFL Object %s inventory manifest block is not a JSON object \[[ocfl/inventory_validator.py#L138](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L138)\] |
-| [E042](https://ocfl.io/1.0/spec#E042) | 'Content paths within a manifest block must be relative to the OCFL Object Root.' | _See multiple cases identified with suffixes below_ |
-| | E042a | OCFL Object %s inventory manifest includes invalid content path %s \[[ocfl/inventory_validator.py#L419](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L419)\] |
-| | E042b | OCFL Object %s inventory manifest includes content path %s with invalid version directory \[[ocfl/inventory_validator.py#L365](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L365)\] |
-| [E043](https://ocfl.io/1.0/spec#E043) | 'An OCFL Object Inventory must include a block for storing versions.' | _Not implemented_ |
-| [E044](https://ocfl.io/1.0/spec#E044) | 'This block MUST have the key of versions within the inventory, and it must be a JSON object.' | OCFL Object %s inventory versions block is not a JSON object \[[ocfl/inventory_validator.py#L220](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L220)\] |
-| [E045](https://ocfl.io/1.0/spec#E045) | 'This block must have the key of versions within the inventory, and it MUST be a JSON object.' | _Not implemented_ |
-| [E046](https://ocfl.io/1.0/spec#E046) | 'The keys of [the versions object] must correspond to the names of the version directories used.' | OCFL Object root inventory describes version %s but no corresponding version directory is present \[[ocfl/validator.py#L291](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L291)\] |
-| [E047](https://ocfl.io/1.0/spec#E047) | 'Each value [of the versions object] must be another JSON object that characterizes the version, as described in the 3.5.3.1 Version section.' | _Not implemented_ |
-| [E048](https://ocfl.io/1.0/spec#E048) | 'A JSON object to describe one OCFL Version, which must include the following keys: [created, state]' | OCFL Object %s inventory %s version block does not include a created date or it is malformed \[[ocfl/inventory_validator.py#L281](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L281)\] |
-| | E048c | OCFL Object %s inventory %s version block does not include a state block \[[ocfl/inventory_validator.py#L297](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L297)\] |
-| [E049](https://ocfl.io/1.0/spec#E049) | '[the value of the "created" key] must be expressed in the Internet Date/Time Format defined by [RFC3339].' | _See multiple cases identified with suffixes below_ |
-| | E049a | OCFL Object %s inventory %s version block created date SHOULD include a timezone designator \[[ocfl/inventory_validator.py#L289](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L289)\] |
-| | E049b | OCFL Object %s inventory %s version block created date SHOULD be granular to the seconds level \[[ocfl/inventory_validator.py#L291](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L291)\] |
-| | E049c | OCFL Object %s inventory %s version block has bad created date, must be IS8601 (%s) \[[ocfl/inventory_validator.py#L293](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L293)\] |
-| | E049d | OCFL Object %s inventory %s version block created value is not a JSON string \[[ocfl/inventory_validator.py#L283](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L283)\] |
-| [E050](https://ocfl.io/1.0/spec#E050) | 'The keys of [the "state" JSON object] are digest values, each of which must correspond to an entry in the manifest of the inventory.' | _See multiple cases identified with suffixes below_ |
-| | E050a | OCFL Object %s inventory state refers to one or more digests that are not in the manifest (%s) \[[ocfl/inventory_validator.py#L373](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L373)\] |
-| | E050b | OCFL Object %s inventory manifest refers to one or more digests that are not in any state (%s) \[[ocfl/inventory_validator.py#L376](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L376)\] |
-| | E050c | OCFL Object %s inventory %s version block state block is not a JSON object \[[ocfl/inventory_validator.py#L330](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L330)\] |
-| | E050d | OCFL Object %s inventory %s version state block includes a bad digest (%s) \[[ocfl/inventory_validator.py#L335](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L335)\] |
-| | E050e | OCFL Object %s inventory %s version block state block value for digest %s is not list \[[ocfl/inventory_validator.py#L337](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L337)\] |
-| | E050f | OCFL Object %s inventory version %s state includes digest value %s that is not listed in the manifest block \[[ocfl/inventory_validator.py#L346](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L346)\] |
-| [E051](https://ocfl.io/1.0/spec#E051) | 'The logical path [value of a "state" digest key] must be interpreted as a set of one or more path elements joined by a / path separator.' | NOTE - E051 is essentially a processing instruction and can't be tested for. \[_Not implemented_\] |
-| [E052](https://ocfl.io/1.0/spec#E052) | '[logical] Path elements must not be ., .., or empty (//).' | OCFL Object %s inventory %s version block state block includes an invalid path %s that starts or ends with / \[[ocfl/inventory_validator.py#L401](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L401)\] |
-| [E053](https://ocfl.io/1.0/spec#E053) | 'Additionally, a logical path must not begin or end with a forward slash (/).' | OCFL Object %s inventory %s version block state block includes an invalid path %s that includes . .. or // \[[ocfl/inventory_validator.py#L396](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L396)\] |
-| [E054](https://ocfl.io/1.0/spec#E054) | 'The value of the user key must contain a user name key, "name" and should contain an address key, "address".' | _See multiple cases identified with suffixes below_ |
-| | E054a | OCFL Object %s inventory %s version block has user key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L307](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L307)\] |
-| | E054b | OCFL Object %s inventory %s version block has user/name key with value that isn't a string \[[ocfl/inventory_validator.py#L310](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L310)\] |
-| | E054c | OCFL Object %s inventory %s version block has user/address key with value that isn't a string \[[ocfl/inventory_validator.py#L314](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L314)\] |
-| [E055](https://ocfl.io/1.0/spec#E055) | 'This block must have the key of fixity within the inventory.' | _Not implemented_ |
-| [E056](https://ocfl.io/1.0/spec#E056) | 'The fixity block must contain keys corresponding to the controlled vocabulary given in the digest algorithms listed in the Digests section, or in a table given in an Extension.' | _See multiple cases identified with suffixes below_ |
-| | E056a | OCFL Object %s inventory includes a fixity key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L173](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L173)\] |
-| | E056b | OCFL Object %s inventory fixity block includes and unknown digest algorithm name %s \[[ocfl/inventory_validator.py#L181](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L181)\] |
-| [E057](https://ocfl.io/1.0/spec#E057) | 'The value of the fixity block for a particular digest algorithm must follow the structure of the manifest block; that is, a key corresponding to the digest value, and an array of content paths that match that digest.' | _See multiple cases identified with suffixes below_ |
-| | E057a | OCFL Object %s inventory fixity block entry for digest algorithm name %s is not a JSON object \[[ocfl/inventory_validator.py#L188](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L188)\] |
-| | E057b | OCFL Object %s inventory fixity block entry for digest algorithm %s includes digest %s which has the wrong form \[[ocfl/inventory_validator.py#L194](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L194)\] |
-| | E057c | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s is not a JSON list \[[ocfl/inventory_validator.py#L196](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L196)\] |
-| | E057d | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s includes a content path %s that is not in the manifest \[[ocfl/inventory_validator.py#L209](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L209)\] |
-| [E058](https://ocfl.io/1.0/spec#E058) | 'Every occurrence of an inventory file must have an accompanying sidecar file stating its digest.' | _See multiple cases identified with suffixes below_ |
-| | E058a | OCFL Object %s inventory is missing sidecar digest file at %s \[[ocfl/validator.py#L137](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L137)\] |
-| | E058b | Cannot extract digest type from inventory digest file name %s \[[ocfl/validator.py#L160](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L160)\] |
-| [E059](https://ocfl.io/1.0/spec#E059) | 'This value must match the value given for the digestAlgorithm key in the inventory.' | _Not implemented_ |
-| [E060](https://ocfl.io/1.0/spec#E060) | 'The digest sidecar file must contain the digest of the inventory file.' | Mismatch between actual and recorded inventory digests for %s (calculated %s but read %s from %s) \[[ocfl/validator.py#L156](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L156)\] |
-| [E061](https://ocfl.io/1.0/spec#E061) | '[The digest sidecar file] must follow the format: DIGEST inventory.json' | Cannot extract digest from inventory digest file (%s) \[[ocfl/validator.py#L158](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L158)\] |
-| [E062](https://ocfl.io/1.0/spec#E062) | 'The digest of the inventory must be computed only after all changes to the inventory have been made, and thus writing the digest sidecar file is the last step in the versioning process.' | _Not implemented_ |
-| [E063](https://ocfl.io/1.0/spec#E063) | 'Every OCFL Object must have an inventory file within the OCFL Object Root, corresponding to the state of the OCFL Object at the current version.' | OCFL Object root inventory is missing \[[ocfl/validator.py#L93](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L93)\] |
-| [E064](https://ocfl.io/1.0/spec#E064) | 'Where an OCFL Object contains inventory.json in version directories, the inventory file in the OCFL Object Root must be the same as the file in the most recent version.' | Object root inventory and copy in last version MUST be identical but are not (%s and %s) \[[ocfl/validator.py#L219](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L219)\] |
-| [E066](https://ocfl.io/1.0/spec#E066) | 'Each version block in each prior inventory file must represent the same object state as the corresponding version block in the current inventory file.' | _See multiple cases identified with suffixes below_ |
-| | E066a | OCFL Object inventory for %s doesn't have a subset of version blocks of inventory for %s \[[ocfl/inventory_validator.py#L441](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L441)\] |
-| | E066b | OCFL Object inventory manifest for %s in %s doesn't have a subset of manifest entries of inventory for %s \[[ocfl/inventory_validator.py#L450](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L450)\] |
-| | E066c | OCFL Object %s inventory %s version state has file %s that maps to different content files (%s) than in the %s inventory (%s) \[[ocfl/inventory_validator.py#L455](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L455)\] |
-| [E067](https://ocfl.io/1.0/spec#E067) | 'The extensions directory must not contain any files, and no sub-directories other than extension sub-directories.' | OCFL Object extensions direct contains an unexpected non-directory entry: %s \[[ocfl/validator.py#L198](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L198)\] |
-| [E068](https://ocfl.io/1.0/spec#E068) | 'The specific structure and function of the extension, as well as a declaration of the registered extension name must be defined in one of the following locations: The OCFL Extensions repository OR The Storage Root, as a plain text document directly in the Storage Root.' | _Not implemented_ |
-| [E069](https://ocfl.io/1.0/spec#E069) | 'An OCFL Storage Root MUST contain a Root Conformance Declaration identifying it as such.' | _Not implemented_ |
-| [E070](https://ocfl.io/1.0/spec#E070) | 'If present, [the ocfl_layout.json document] MUST include the following two keys in the root JSON object: [key, description]' | _Not implemented_ |
-| [E071](https://ocfl.io/1.0/spec#E071) | 'The value of the [ocfl_layout.json] extension key must be the registered extension name for the extension defining the arrangement under the storage root.' | _Not implemented_ |
-| [E072](https://ocfl.io/1.0/spec#E072) | 'The directory hierarchy used to store OCFL Objects MUST NOT contain files that are not part of an OCFL Object.' | OCFL storage root hierarchy include directory %s with at least one file but no object declaration. Such additional files are not allowed \[[ocfl/store.py#L169](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L169)\] |
-| [E073](https://ocfl.io/1.0/spec#E073) | 'Empty directories MUST NOT appear under a storage root.' | OCFL storage root hierarchy contains an empty directory: %s \[[ocfl/store.py#L151](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L151)\] |
-| [E074](https://ocfl.io/1.0/spec#E074) | 'Although implementations may require multiple OCFL Storage Roots - that is, several logical or physical volumes, or multiple "buckets" in an object store - each OCFL Storage Root MUST be independent.' | _Not implemented_ |
-| [E075](https://ocfl.io/1.0/spec#E075) | 'The OCFL version declaration MUST be formatted according to the NAMASTE specification.' | _Not implemented_ |
-| [E076](https://ocfl.io/1.0/spec#E076) | '[The OCFL version declaration] MUST be a file in the base directory of the OCFL Storage Root giving the OCFL version in the filename.' | _Not implemented_ |
-| [E077](https://ocfl.io/1.0/spec#E077) | '[The OCFL version declaration filename] MUST conform to the pattern T=dvalue, where T must be 0, and dvalue must be ocfl_, followed by the OCFL specification version number.' | _Not implemented_ |
-| [E078](https://ocfl.io/1.0/spec#E078) | '[The OCFL version declaration filename] must conform to the pattern T=dvalue, where T MUST be 0, and dvalue must be ocfl_, followed by the OCFL specification version number.' | _Not implemented_ |
-| [E079](https://ocfl.io/1.0/spec#E079) | '[The OCFL version declaration filename] must conform to the pattern T=dvalue, where T must be 0, and dvalue MUST be ocfl_, followed by the OCFL specification version number.' | _Not implemented_ |
-| [E080](https://ocfl.io/1.0/spec#E080) | 'The text contents of [the OCFL version declaration file] MUST be the same as dvalue, followed by a newline (\n).' | _Not implemented_ |
-| [E081](https://ocfl.io/1.0/spec#E081) | 'OCFL Objects within the OCFL Storage Root also include a conformance declaration which MUST indicate OCFL Object conformance to the same or earlier version of the specification.' | _Not implemented_ |
-| [E082](https://ocfl.io/1.0/spec#E082) | 'OCFL Object Roots MUST be stored either as the terminal resource at the end of a directory storage hierarchy or as direct children of a containing OCFL Storage Root.' | _Not implemented_ |
-| [E083](https://ocfl.io/1.0/spec#E083) | 'There MUST be a deterministic mapping from an object identifier to a unique storage path.' | _Not implemented_ |
-| [E084](https://ocfl.io/1.0/spec#E084) | 'Storage hierarchies MUST NOT include files within intermediate directories.' | _Not implemented_ |
-| [E085](https://ocfl.io/1.0/spec#E085) | 'Storage hierarchies MUST be terminated by OCFL Object Roots.' | _Not implemented_ |
-| [E086](https://ocfl.io/1.0/spec#E086) | 'The storage root extensions directory MUST conform to the same guidelines and limitations as those defined for object extensions.' | _Not implemented_ |
-| [E087](https://ocfl.io/1.0/spec#E087) | 'An OCFL validator MUST ignore any files in the storage root it does not understand.' | NOTE - E087 is a processing instruction and can't be tested for. \[_Not implemented_\] |
-| [E088](https://ocfl.io/1.0/spec#E088) | 'An OCFL Storage Root MUST NOT contain directories or sub-directories other than as a directory hierarchy used to store OCFL Objects or for storage root extensions.' | NOTE - E088 is redundant to more specific errors E072 and E073 \[_Not implemented_\] |
-| [E089](https://ocfl.io/1.0/spec#E089) | 'If the preservation of non-OCFL-compliant features is required then the content MUST be wrapped in a suitable disk or filesystem image format which OCFL can treat as a regular file.' | _Not implemented_ |
-| [E090](https://ocfl.io/1.0/spec#E090) | 'Hard and soft (symbolic) links are not portable and MUST NOT be used within OCFL Storage hierachies.' | NOTE - E090 is a processing instruction and can't be tested for. \[_Not implemented_\] |
-| [E091](https://ocfl.io/1.0/spec#E091) | 'Filesystems MUST preserve the case of OCFL filepaths and filenames.' | OCFL Object %s inventory manifest file list for digest %s is not a JSON array \[_Not implemented_\] |
-| [E092](https://ocfl.io/1.0/spec#E092) | 'The value for each key in the manifest must be an array containing the content paths of files in the OCFL Object that have content with the given digest.' | **Missing description** \[[ocfl/inventory_validator.py#L147](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L147)\] |
-| | E092a | OCFL Object %s inventory manifest has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L302](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L302) [ocfl/validator.py#L311](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L311)\] |
-| | E092b | OCFL Object %s inventory manifest refers to a file path that is not present in the object (%s) \[[ocfl/validator.py#L297](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L297)\] |
-| [E093](https://ocfl.io/1.0/spec#E093) | 'Where included in the fixity block, the digest values given must match the digests of the files at the corresponding content paths.' | _See multiple cases identified with suffixes below_ |
-| | E093a | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L323](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L323)\] |
-| | E093b | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for a file %s which does not exist in the object \[[ocfl/validator.py#L325](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L325)\] |
-| [E094](https://ocfl.io/1.0/spec#E094) | 'The value of [the message] key is freeform text, used to record the rationale for creating this version. It must be a JSON string.' | OCFL Object %s inventory %s version block has message key with value that isn't a string \[[ocfl/inventory_validator.py#L301](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L301)\] |
-| [E095](https://ocfl.io/1.0/spec#E095) | 'Within a version, logical paths must be unique and non-conflicting, so the logical path for a file cannot appear as the initial part of another logical path.' | _See multiple cases identified with suffixes below_ |
-| | E095a | OCFL Object %s inventory version %s state has logical path %s used more than once. \[[ocfl/inventory_validator.py#L341](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L341)\] |
-| | E095b | OCFL Object %s inventory version %s state has logical path %s used as both a directory and a file path. \[[ocfl/inventory_validator.py#L352](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L352)\] |
-| [E096](https://ocfl.io/1.0/spec#E096) | 'As JSON keys are case sensitive, while digests may not be, there is an additional requirement that each digest value must occur only once in the manifest regardless of case.' | OCFL Object %s inventory manifest block includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L153](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L153)\] |
-| [E097](https://ocfl.io/1.0/spec#E097) | 'As JSON keys are case sensitive, while digests may not be, there is an additional requirement that each digest value must occur only once in the fixity block for any digest algorithm, regardless of case.' | OCFL Object %s inventory fixity block for digest algorithm %s, includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L204](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L204)\] |
-| [E098](https://ocfl.io/1.0/spec#E098) | 'The content path must be interpreted as a set of one or more path elements joined by a / path separator.' | _Not implemented_ |
-| [E099](https://ocfl.io/1.0/spec#E099) | '[content] path elements must not be ., .., or empty (//).' | OCFL Object %s inventory manifest content path %s includes invalid element ., .., or empty (//). \[[ocfl/inventory_validator.py#L424](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L424)\] |
-| [E100](https://ocfl.io/1.0/spec#E100) | 'A content path must not begin or end with a forward slash (/).' | OCFL Object %s inventory manifest content path %s must not begin or end with /. \[[ocfl/inventory_validator.py#L415](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L415)\] |
-| [E101](https://ocfl.io/1.0/spec#E101) | 'Within an inventory, content paths must be unique and non-conflicting, so the content path for a file cannot appear as the initial part of another content path.' | _See multiple cases identified with suffixes below_ |
-| | E101a | OCFL Object %s inventory manifest content path %s is repeated. \[[ocfl/inventory_validator.py#L428](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L428)\] |
-| | E101b | OCFL Object %s inventory manifest content path %s used as both a directory and a file path. \[[ocfl/inventory_validator.py#L163](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L163)\] |
-| [E102](https://ocfl.io/1.0/spec#E102) | 'An inventory file must not contain keys that are not specified.' | _Not implemented_ |
+| E001 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E001a | OCFL Object root contains unexpected file: %s \[[ocfl/validator.py#L199](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L199)\] |
+| | E001b | OCFL Object root contains unexpected directory: %s \[[ocfl/validator.py#L210](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L210)\] |
+| | E001c | OCFL Object root contains unexpected entry that isn't a file or directory: %s \[[ocfl/validator.py#L212](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L212)\] |
+| E002 | **Not in specification** | NOTE - E002 is redundant to more specific errors E003, E004, E005, E006. \[_Not implemented_\] |
+| E003 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E003a | OCFL Object version declaration file is missing (assuming version %s) \[[ocfl/validator.py#L88](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L88)\] |
+| | E003b | OCFL Object includes more that one file that looks like an object declaration (got %s), using version %s \[[ocfl/validator.py#L109](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L109)\] |
+| | E003c | OCFL Object includes one or move object declaration files (starting 0=) but no valid version number could be extracted from any of them (assuming version %s) \[[ocfl/validator.py#L105](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L105)\] |
+| | E003d | OCFL Storage Root hierarchy includes directory %s with more that one file that looks like an object declaration, ignoring \[[ocfl/store.py#L168](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L168)\] |
+| | E003e | No OCFL Object to validate at path %s. The root of an OCFL Object must be a directory containing an object declaration \[[ocfl/validator.py#L82](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L82)\] |
+| E004 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E004a | OCFL Storage Root hierarchy includes directory %s with an object declaration giving unknown version %s, ignoring \[[ocfl/store.py#L175](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L175)\] |
+| | E004b | OCFL Storage Root hierarchy includes directory %s with an unrecognized object declaration %s, ignoring \[[ocfl/store.py#L177](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L177)\] |
+| E006 | **Not in specification** | The OCFL object conformance declaration filename must be 0=ocfl_object_, followed by the OCFL specification version number, got %s instead \[[ocfl/validator.py#L99](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L99)\] |
+| E007 | **Not in specification** | OCFL Object declaration file %s contents do not match file name without leading 0= (the 'dvalue') \[[ocfl/validator.py#L103](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L103)\] |
+| E008 | **Not in specification** | OCFL Object %s inventory versions block does not contain any versions, there must be at least version 1 \[[ocfl/inventory_validator.py#L252](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L252)\] |
+| E009 | **Not in specification** | OCFL Object %s inventory versions block does not contain v1 or a zero padded equivalent \[[ocfl/inventory_validator.py#L272](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L272)\] |
+| E010 | **Not in specification** | OCFL Object %s inventory versions block includes an out-of-sequence version \[[ocfl/inventory_validator.py#L283](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L283)\] |
+| E011 | **Not in specification** | **Missing description** \[[ocfl/inventory_validator.py#L290](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L290)\] |
+| E015 | **Not in specification** | OCFL Object version directory %s includes an illegal file (%s) \[[ocfl/validator.py#L335](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L335)\] |
+| E017 | **Not in specification** | OCFL Object %s inventory contentDirectory must be a string and must not contain a forward slash (/) \[[ocfl/inventory_validator.py#L112](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L112)\] |
+| E018 | **Not in specification** | OCFL Object %s inventory contentDirectory must not be either . or .. \[[ocfl/inventory_validator.py#L114](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L114)\] |
+| E023 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E023a | OCFL Object includes one or more files that are not mentioned in the  %s inventory manifest (%s) \[[ocfl/validator.py#L373](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L373)\] |
+| | E023b | OCFL Object %s manifest does not include files listed in previous version manifests (%s) \[[ocfl/validator.py#L274](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L274)\] |
+| E024 | **Not in specification** | OCFL Object version %s content directory includes empty path %s \[[ocfl/validator.py#L326](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L326)\] |
+| E025 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E025a | OCFL Object %s inventory manifest block includes a digest (%s) that doesn't have the correct form for the %s algorithm \[[ocfl/inventory_validator.py#L171](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L171)\] |
+| E026 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E026a | OCFL Object %s inventory uses unknown digest type %s \[[ocfl/inventory_validator.py#L413](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L413)\] |
+| E033 | **Not in specification** | OCFL Object %s inventory is not valid JSON (%s) \[[ocfl/object.py#L544](https://github.com/zimeon/ocfl-py/blob/main/ocfl/object.py#L544) [ocfl/validator.py#L151](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L151)\] |
+| E036 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E036a | OCFL Object %s inventory missing `id` attribute \[[ocfl/inventory_validator.py#L82](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L82)\] |
+| | E036b | OCFL Object %s inventory missing `type` attribute \[[ocfl/inventory_validator.py#L84](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L84)\] |
+| | E036c | OCFL Object %s inventory missing `digestAlgorithm` attribute \[[ocfl/inventory_validator.py#L98](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L98)\] |
+| | E036d | OCFL Object %s inventory missing `head` attribute \[[ocfl/inventory_validator.py#L129](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L129)\] |
+| E037 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E037a | OCFL Object %s inventory `id` attribute is empty or badly formed \[[ocfl/inventory_validator.py#L74](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L74)\] |
+| | E037b | OCFL Object %s inventory id `%s` does not match the value in the root inventory `%s` \[[ocfl/validator.py#L264](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L264)\] |
+| E038 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E038a | OCFL Object %s inventory `type` attribute has wrong value (expected %s, got %s) \[[ocfl/inventory_validator.py#L96](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L96)\] |
+| | E038b | OCFL Object %s inventory `type` attribute does not look like a valid specification URI (got %s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L90](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L90)\] |
+| | E038c | OCFL Object %s inventory `type` attribute has an unsupported specification version number (%s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L94](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L94)\] |
+| E039 | **Not in specification** | OCFL Object %s inventory `digestAlgorithm` attribute not an allowed value (got '%s') \[[ocfl/inventory_validator.py#L107](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L107)\] |
+| E040 | **Not in specification** | OCFL Object %s inventory head attribute doesn't match versions (got %s, expected %s) \[[ocfl/inventory_validator.py#L133](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L133)\] |
+| E041 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E041a | OCFL Object %s inventory missing `manifest` attribute \[[ocfl/inventory_validator.py#L119](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L119)\] |
+| | E041b | OCFL Object %s inventory missing `versions` attribute \[[ocfl/inventory_validator.py#L124](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L124)\] |
+| | E041c | OCFL Object %s inventory manifest block is not a JSON object \[[ocfl/inventory_validator.py#L164](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L164)\] |
+| E042 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E042a | OCFL Object %s inventory manifest includes invalid content path %s \[[ocfl/inventory_validator.py#L448](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L448)\] |
+| | E042b | OCFL Object %s inventory manifest includes content path %s with invalid version directory \[[ocfl/inventory_validator.py#L394](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L394)\] |
+| E044 | **Not in specification** | OCFL Object %s inventory versions block is not a JSON object \[[ocfl/inventory_validator.py#L249](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L249)\] |
+| E046 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E046a | OCFL Object root inventory describes version %s but no corresponding version directory is present \[[ocfl/validator.py#L337](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L337)\] |
+| | E046b | OCFL Object includes directory %s that looks like a version directory but isn't a valid version in the inventory \[[ocfl/validator.py#L207](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L207)\] |
+| E048 | **Not in specification** | OCFL Object %s inventory %s version block does not include a created date or it is malformed \[[ocfl/inventory_validator.py#L310](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L310)\] |
+| | E048c | OCFL Object %s inventory %s version block does not include a state block \[[ocfl/inventory_validator.py#L326](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L326)\] |
+| E049 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E049a | OCFL Object %s inventory %s version block created date SHOULD include a timezone designator \[[ocfl/inventory_validator.py#L318](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L318)\] |
+| | E049b | OCFL Object %s inventory %s version block created date SHOULD be granular to the seconds level \[[ocfl/inventory_validator.py#L320](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L320)\] |
+| | E049c | OCFL Object %s inventory %s version block has bad created date, must be IS8601 (%s) \[[ocfl/inventory_validator.py#L322](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L322)\] |
+| | E049d | OCFL Object %s inventory %s version block created value is not a JSON string \[[ocfl/inventory_validator.py#L312](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L312)\] |
+| E050 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E050a | OCFL Object %s inventory state refers to one or more digests that are not in the manifest (%s) \[[ocfl/inventory_validator.py#L402](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L402)\] |
+| | E050c | OCFL Object %s inventory %s version block state block is not a JSON object \[[ocfl/inventory_validator.py#L359](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L359)\] |
+| | E050d | OCFL Object %s inventory %s version state block includes a bad digest (%s) \[[ocfl/inventory_validator.py#L364](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L364)\] |
+| | E050e | OCFL Object %s inventory %s version block state block value for digest %s is not list \[[ocfl/inventory_validator.py#L366](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L366)\] |
+| | E050f | OCFL Object %s inventory version %s state includes digest value %s that is not listed in the manifest block \[[ocfl/inventory_validator.py#L375](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L375)\] |
+| E051 | **Not in specification** | NOTE - E051 is essentially a processing instruction and can't be tested for. \[_Not implemented_\] |
+| E052 | **Not in specification** | OCFL Object %s inventory %s version block state block includes an invalid path %s that starts or ends with / \[[ocfl/inventory_validator.py#L430](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L430)\] |
+| E053 | **Not in specification** | OCFL Object %s inventory %s version block state block includes an invalid path %s that includes . .. or // \[[ocfl/inventory_validator.py#L425](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L425)\] |
+| E054 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E054a | OCFL Object %s inventory %s version block has user key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L336](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L336)\] |
+| | E054b | OCFL Object %s inventory %s version block has user/name key with value that isn't a string \[[ocfl/inventory_validator.py#L339](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L339)\] |
+| | E054c | OCFL Object %s inventory %s version block has user/address key with value that isn't a string \[[ocfl/inventory_validator.py#L343](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L343)\] |
+| E056 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E056a | OCFL Object %s inventory includes a fixity key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L202](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L202)\] |
+| | E056b | OCFL Object %s inventory fixity block includes a key that is not a known digest algorithm name: %s \[[ocfl/inventory_validator.py#L210](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L210)\] |
+| E057 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E057a | OCFL Object %s inventory fixity block entry for digest algorithm name %s is not a JSON object \[[ocfl/inventory_validator.py#L217](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L217)\] |
+| | E057b | OCFL Object %s inventory fixity block entry for digest algorithm %s includes digest %s which has the wrong form \[[ocfl/inventory_validator.py#L223](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L223)\] |
+| | E057c | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s is not a JSON list \[[ocfl/inventory_validator.py#L225](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L225)\] |
+| | E057d | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s includes a content path %s that is not in the manifest \[[ocfl/inventory_validator.py#L238](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L238)\] |
+| E058 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E058a | OCFL Object %s inventory is missing sidecar digest file at %s \[[ocfl/validator.py#L163](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L163)\] |
+| | E058b | Cannot extract digest type from inventory digest file name %s \[[ocfl/validator.py#L186](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L186)\] |
+| E060 | **Not in specification** | Mismatch between actual and recorded inventory digests for %s (calculated %s but read %s from %s) \[[ocfl/validator.py#L182](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L182)\] |
+| E061 | **Not in specification** | Cannot extract digest from inventory digest file (%s) \[[ocfl/validator.py#L184](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L184)\] |
+| E063 | **Not in specification** | OCFL Object root inventory is missing \[[ocfl/validator.py#L113](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L113)\] |
+| E064 | **Not in specification** | Object root inventory and copy in last version MUST be identical but are not (%s and %s) \[[ocfl/validator.py#L250](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L250)\] |
+| E066 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E066a | OCFL Object inventory for %s doesn't have a subset of version blocks of inventory for %s \[[ocfl/inventory_validator.py#L472](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L472)\] |
+| | E066b | OCFL Object %s inventory %s version state doesn't have same logical paths as same version in %s inventory (paths %s only in %s inventory) \[[ocfl/inventory_validator.py#L492](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L492) [ocfl/inventory_validator.py#L494](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L494)\] |
+| | E066c | OCFL Object %s inventory %s version state has logical path %s that maps to content path %s, but in %s inventory it maps to content path %s \[[ocfl/inventory_validator.py#L499](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L499)\] |
+| | E066d | OCFL Object %s inventory %s version state has digest %s (mapping to logical files %s) that does not appear in the %s inventory \[[ocfl/inventory_validator.py#L528](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L528)\] |
+| | E066e | OCFL Object %s inventory %s version state has digest %s (mapping to logical files %s) that does not appear in the %s inventory \[[ocfl/inventory_validator.py#L531](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L531)\] |
+| E067 | **Not in specification** | OCFL Object extensions direct contains an unexpected non-directory entry: %s \[[ocfl/validator.py#L228](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L228)\] |
+| E072 | **Not in specification** | OCFL storage root hierarchy include directory %s with at least one file but no object declaration. Such additional files are not allowed \[[ocfl/store.py#L179](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L179)\] |
+| E073 | **Not in specification** | OCFL storage root hierarchy contains an empty directory: %s \[[ocfl/store.py#L161](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L161)\] |
+| E086 | **Not in specification** | OCFL Storage Root extensions direct contains an unexpected non-directory entry: %s \[[ocfl/store.py#L195](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L195)\] |
+| E087 | **Not in specification** | NOTE - E087 is a processing instruction and can't be tested for \[_Not implemented_\] |
+| E088 | **Not in specification** | NOTE - E088 is redundant to more specific errors E072 and E073 \[_Not implemented_\] |
+| E090 | **Not in specification** | NOTE - E090 is a processing instruction and can't be tested for \[_Not implemented_\] |
+| E091 | **Not in specification** | OCFL Object %s inventory manifest file list for digest %s is not a JSON array \[_Not implemented_\] |
+| E092 | **Not in specification** | **Missing description** \[[ocfl/inventory_validator.py#L173](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L173)\] |
+| | E092a | OCFL Object %s inventory manifest using digest algorithm %s has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L368](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L368) [ocfl/validator.py#L363](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L363)\] |
+| | E092b | OCFL Object %s inventory manifest refers to a file path that is not present in the object (%s) \[[ocfl/validator.py#L358](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L358)\] |
+| E093 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E093a | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L369](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L369) [ocfl/validator.py#L366](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L366)\] |
+| | E093b | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for a file %s which does not exist in the object \[[ocfl/validator.py#L352](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L352)\] |
+| E094 | **Not in specification** | OCFL Object %s inventory %s version block has message key with value that isn't a string \[[ocfl/inventory_validator.py#L330](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L330)\] |
+| E095 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E095a | OCFL Object %s inventory version %s state has logical path %s used more than once \[[ocfl/inventory_validator.py#L370](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L370)\] |
+| | E095b | OCFL Object %s inventory version %s state has logical path %s used as both a directory and a file path. \[[ocfl/inventory_validator.py#L381](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L381)\] |
+| E096 | **Not in specification** | OCFL Object %s inventory manifest block includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L179](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L179)\] |
+| E097 | **Not in specification** | OCFL Object %s inventory fixity block for digest algorithm %s, includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L233](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L233)\] |
+| E099 | **Not in specification** | OCFL Object %s inventory manifest content path %s includes invalid element ., .., or empty (//). \[[ocfl/inventory_validator.py#L453](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L453)\] |
+| E100 | **Not in specification** | OCFL Object %s inventory manifest content path %s must not begin or end with /. \[[ocfl/inventory_validator.py#L444](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L444)\] |
+| E101 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | E101a | OCFL Object %s inventory manifest content path %s is repeated \[[ocfl/inventory_validator.py#L457](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L457)\] |
+| | E101b | OCFL Object %s inventory manifest content path %s used as both a directory and a file path \[[ocfl/inventory_validator.py#L189](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L189)\] |
+| E107 | **Not in specification** | **Missing description** \[[ocfl/inventory_validator.py#L405](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L405)\] |
+| E111 | **Not in specification** | OCFL Object %s inventory includes a fixity key with value that isn't a JSON object \[_Not implemented_\] |
+| E999 | **Not in specification** | **Missing description** \[[ocfl/inventory_validator.py#L86](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L86)\] |
 
 ## Warnings
 
 | Code | Specification text (or suffixed code) | Implementation status and message/links |
 | --- | --- | --- |
-| [W001](https://ocfl.io/1.0/spec#W001) | 'Implementations SHOULD use version directory names constructed without zero-padding the version number, ie. v1, v2, v3, etc.'' | OCFL Object %s inventory version numbers SHOULD NOT be zero-padded \[[ocfl/inventory_validator.py#L246](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L246)\] |
-| [W002](https://ocfl.io/1.0/spec#W002) | 'The version directory SHOULD NOT contain any directories other than the designated content sub-directory. Once created, the contents of a version directory are expected to be immutable.' | OCFL Object version directory %s SHOULD NOT contain any directory except the designated content directory (found %s) \[[ocfl/validator.py#L287](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L287)\] |
-| [W003](https://ocfl.io/1.0/spec#W003) | 'Version directories must contain a designated content sub-directory if the version contains files to be preserved, and SHOULD NOT contain this sub-directory otherwise.' | OCFL Object version directory %s SHOULD NOT contain an empty content directory \[[ocfl/validator.py#L285](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L285)\] |
-| [W004](https://ocfl.io/1.0/spec#W004) | 'For content-addressing, OCFL Objects SHOULD use sha512.' | OCFL Object %s inventory SHOULD use sha512 but uses sha256 as the DigestAlgorithm \[[ocfl/inventory_validator.py#L78](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L78)\] |
-| [W005](https://ocfl.io/1.0/spec#W005) | 'The OCFL Object Inventory id SHOULD be a URI.' | OCFL Object %s inventory id SHOULD be a URI (got %s) \[[ocfl/inventory_validator.py#L63](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L63)\] |
-| [W007](https://ocfl.io/1.0/spec#W007) | 'In the OCFL Object Inventory, the JSON object describing an OCFL Version, SHOULD include the message and user keys.' | _See multiple cases identified with suffixes below_ |
-| | W007a | OCFL Object %s inventory %s version block SHOULD include a message key \[[ocfl/inventory_validator.py#L299](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L299)\] |
-| | W007b | OCFL Object %s inventory %s version block SHOULD include a user key \[[ocfl/inventory_validator.py#L303](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L303)\] |
-| [W008](https://ocfl.io/1.0/spec#W008) | 'In the OCFL Object Inventory, in the version block, the value of the user key SHOULD contain an address key, address.' | OCFL Object %s inventory %s version block user description SHOULD have an address \[[ocfl/inventory_validator.py#L312](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L312)\] |
-| [W009](https://ocfl.io/1.0/spec#W009) | 'In the OCFL Object Inventory, in the version block, the address value SHOULD be a URI: either a mailto URI [RFC6068] with the e-mail address of the user or a URL to a personal identifier, e.g., an ORCID iD.' | OCFL Object %s inventory %s version block user description SHOULD be a mailto: or person identifier URI \[[ocfl/inventory_validator.py#L316](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L316)\] |
-| [W010](https://ocfl.io/1.0/spec#W010) | 'In addition to the inventory in the OCFL Object Root, every version directory SHOULD include an inventory file that is an Inventory of all content for versions up to and including that particular version.' | OCFL Object %s SHOULD have an inventory file but does not \[[ocfl/validator.py#L213](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L213)\] |
-| [W011](https://ocfl.io/1.0/spec#W011) | 'In the case that prior version directories include an inventory file, the values of the created, message and user keys in each version block in each prior inventory file SHOULD have the same values as the corresponding keys in the corresponding version block in the current inventory file.' | OCFL Object version metadata '%s' for %s in %s inventory does not match that in %s inventory \[[ocfl/inventory_validator.py#L463](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L463)\] |
-| [W012](https://ocfl.io/1.0/spec#W012) | 'Implementers SHOULD use the logs directory, if present, for storing files that contain a record of actions taken on the object.' | _Not implemented_ |
-| [W013](https://ocfl.io/1.0/spec#W013) | 'In an OCFL Object, extension sub-directories SHOULD be named according to a registered extension name.' | OCFL Object includes unregistered extension directory '%s' \[[ocfl/validator.py#L196](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L196)\] |
-| [W014](https://ocfl.io/1.0/spec#W014) | 'Storage hierarchies within the same OCFL Storage Root SHOULD use just one layout pattern.' | _Not implemented_ |
-| [W015](https://ocfl.io/1.0/spec#W015) | 'Storage hierarchies within the same OCFL Storage Root SHOULD consistently use either a directory hierarchy of OCFL Objects or top-level OCFL Objects.' | _Not implemented_ |
+| W001 | **Not in specification** | OCFL Object %s inventory version numbers SHOULD NOT be zero-padded \[[ocfl/inventory_validator.py#L275](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L275)\] |
+| W002 | **Not in specification** | OCFL Object version directory %s SHOULD NOT contain any directory except the designated content directory (found %s) \[[ocfl/validator.py#L333](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L333)\] |
+| W003 | **Not in specification** | OCFL Object version directory %s SHOULD NOT contain an empty content directory \[[ocfl/validator.py#L331](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L331)\] |
+| W004 | **Not in specification** | OCFL Object %s inventory SHOULD use sha512 but uses sha256 as the DigestAlgorithm \[[ocfl/inventory_validator.py#L104](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L104)\] |
+| W005 | **Not in specification** | OCFL Object %s inventory id SHOULD be a URI (got %s) \[[ocfl/inventory_validator.py#L79](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L79)\] |
+| W007 | **Not in specification** | _See multiple cases identified with suffixes below_ |
+| | W007a | OCFL Object %s inventory %s version block SHOULD include a message key \[[ocfl/inventory_validator.py#L328](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L328)\] |
+| | W007b | OCFL Object %s inventory %s version block SHOULD include a user key \[[ocfl/inventory_validator.py#L332](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L332)\] |
+| W008 | **Not in specification** | OCFL Object %s inventory %s version block user description SHOULD have an address \[[ocfl/inventory_validator.py#L341](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L341)\] |
+| W009 | **Not in specification** | OCFL Object %s inventory %s version block user description SHOULD be a mailto: or person identifier URI \[[ocfl/inventory_validator.py#L345](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L345)\] |
+| W010 | **Not in specification** | OCFL Object %s SHOULD have an inventory file but does not \[[ocfl/validator.py#L244](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L244)\] |
+| W011 | **Not in specification** | OCFL Object version metadata '%s' for %s in %s inventory does not match that in %s inventory \[[ocfl/inventory_validator.py#L507](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L507)\] |
+| W013 | **Not in specification** | OCFL Object includes unregistered extension directory '%s' \[[ocfl/validator.py#L226](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L226)\] |
+| W901 | **Not in specification** | OCFL Storage Root includes unregistered extension directory '%s' \[[ocfl/store.py#L193](https://github.com/zimeon/ocfl-py/blob/main/ocfl/store.py#L193)\] |
 
-_Generated by `extract_codes.py` at 2021-04-26 09:25:28.509356_
\ No newline at end of file
+_Generated by `extract_codes.py` at 2022-04-26 10:47:16.403931_
\ No newline at end of file
diff --git a/extra_fixtures/bad-objects/E009_version_two_only/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E009_version_two_only/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E009_version_two_only/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E009_version_two_only/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E009_version_two_only/inventory.json b/extra_fixtures/1.0/bad-objects/E009_version_two_only/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E009_version_two_only/inventory.json
rename to extra_fixtures/1.0/bad-objects/E009_version_two_only/inventory.json
diff --git a/extra_fixtures/bad-objects/E009_version_two_only/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E009_version_two_only/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E009_version_two_only/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E009_version_two_only/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E009_version_two_only/v2/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E009_version_two_only/v2/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E009_version_two_only/v2/content/a_file.txt
rename to extra_fixtures/1.0/bad-objects/E009_version_two_only/v2/content/a_file.txt
diff --git a/extra_fixtures/bad-objects/E009_version_two_only/v2/inventory.json b/extra_fixtures/1.0/bad-objects/E009_version_two_only/v2/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E009_version_two_only/v2/inventory.json
rename to extra_fixtures/1.0/bad-objects/E009_version_two_only/v2/inventory.json
diff --git a/extra_fixtures/bad-objects/E009_version_two_only/v2/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E009_version_two_only/v2/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E009_version_two_only/v2/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E009_version_two_only/v2/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E024_empty_dir_in_content.zip b/extra_fixtures/1.0/bad-objects/E024_empty_dir_in_content.zip
similarity index 100%
rename from extra_fixtures/bad-objects/E024_empty_dir_in_content.zip
rename to extra_fixtures/1.0/bad-objects/E024_empty_dir_in_content.zip
diff --git a/extra_fixtures/bad-objects/E033_inventory_bad_json/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E033_inventory_bad_json/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E033_inventory_bad_json/inventory.json b/extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E033_inventory_bad_json/inventory.json
rename to extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/inventory.json
diff --git a/extra_fixtures/bad-objects/E033_inventory_bad_json/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E033_inventory_bad_json/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E033_inventory_bad_json/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E033_inventory_bad_json/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E033_inventory_bad_json/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E033_inventory_bad_json/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E033_inventory_bad_json/v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E041_manifest_not_object/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E041_manifest_not_object/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E041_manifest_not_object/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E041_manifest_not_object/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E041_manifest_not_object/inventory.json b/extra_fixtures/1.0/bad-objects/E041_manifest_not_object/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E041_manifest_not_object/inventory.json
rename to extra_fixtures/1.0/bad-objects/E041_manifest_not_object/inventory.json
diff --git a/extra_fixtures/bad-objects/E041_manifest_not_object/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E041_manifest_not_object/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E041_manifest_not_object/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E041_manifest_not_object/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E041_manifest_not_object/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E041_manifest_not_object/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E041_manifest_not_object/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E041_manifest_not_object/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E041_manifest_not_object/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E041_manifest_not_object/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E041_manifest_not_object/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E041_manifest_not_object/v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E042_bad_manifest_content_path/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E042_bad_manifest_content_path/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E042_bad_manifest_content_path/inventory.json b/extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E042_bad_manifest_content_path/inventory.json
rename to extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/inventory.json
diff --git a/extra_fixtures/bad-objects/E042_bad_manifest_content_path/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E042_bad_manifest_content_path/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E042_bad_manifest_content_path/v1/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/v1/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E042_bad_manifest_content_path/v1/content/a_file.txt
rename to extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/v1/content/a_file.txt
diff --git a/extra_fixtures/bad-objects/E042_bad_manifest_content_path/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E042_bad_manifest_content_path/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E042_bad_manifest_content_path/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E042_bad_manifest_content_path/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E042_bad_manifest_content_path/v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E046_missing_version_dir/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E046_missing_version_dir/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E046_missing_version_dir/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E046_missing_version_dir/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E046_missing_version_dir/inventory.json b/extra_fixtures/1.0/bad-objects/E046_missing_version_dir/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E046_missing_version_dir/inventory.json
rename to extra_fixtures/1.0/bad-objects/E046_missing_version_dir/inventory.json
diff --git a/extra_fixtures/bad-objects/E046_missing_version_dir/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E046_missing_version_dir/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E046_missing_version_dir/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E046_missing_version_dir/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E050_state_digest_different_case/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_digest_different_case/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E050_state_digest_different_case/inventory.json b/extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_digest_different_case/inventory.json
rename to extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/inventory.json
diff --git a/extra_fixtures/bad-objects/E050_state_digest_different_case/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_digest_different_case/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E050_state_digest_different_case/v1/content/empty1 b/extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/v1/content/empty1
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_digest_different_case/v1/content/empty1
rename to extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/v1/content/empty1
diff --git a/extra_fixtures/bad-objects/E050_state_digest_different_case/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_digest_different_case/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E050_state_digest_different_case/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_digest_different_case/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E050_state_digest_different_case/v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E050_state_repeated_digest/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_repeated_digest/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E050_state_repeated_digest/inventory.json b/extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_repeated_digest/inventory.json
rename to extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/inventory.json
diff --git a/extra_fixtures/bad-objects/E050_state_repeated_digest/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_repeated_digest/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E050_state_repeated_digest/v1/content/empty1 b/extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/v1/content/empty1
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_repeated_digest/v1/content/empty1
rename to extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/v1/content/empty1
diff --git a/extra_fixtures/bad-objects/E050_state_repeated_digest/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_repeated_digest/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E050_state_repeated_digest/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E050_state_repeated_digest/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E050_state_repeated_digest/v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E092_bad_manifest_digest/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E056_null_fixity/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E092_bad_manifest_digest/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E056_null_fixity/0=ocfl_object_1.0
diff --git a/extra_fixtures/1.0/bad-objects/E056_null_fixity/inventory.json b/extra_fixtures/1.0/bad-objects/E056_null_fixity/inventory.json
new file mode 100644
index 00000000..3b3ad026
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E056_null_fixity/inventory.json
@@ -0,0 +1,16 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "http://example.org/minimal_no_content",
+  "fixity": null,
+  "manifest": { },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "One version and no content, null fixity",
+      "state": { },
+      "user": { "address": "mailto:Person_A@example.org", "name": "Person A" }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E056_null_fixity/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E056_null_fixity/inventory.json.sha512
new file mode 100644
index 00000000..a9bb67e9
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E056_null_fixity/inventory.json.sha512
@@ -0,0 +1,1 @@
+38054db487b2c456bef029ad3be3e0a590103faa02b341f509e7fbc0bdbb3d458dfd6a408ab5ab385b3c74a3340b1adfa45242f78db2364ef4229fca3fcc24f0 inventory.json
diff --git a/extra_fixtures/1.0/bad-objects/E056_null_fixity/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E056_null_fixity/v1/inventory.json
new file mode 100644
index 00000000..3b3ad026
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E056_null_fixity/v1/inventory.json
@@ -0,0 +1,16 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "http://example.org/minimal_no_content",
+  "fixity": null,
+  "manifest": { },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "One version and no content, null fixity",
+      "state": { },
+      "user": { "address": "mailto:Person_A@example.org", "name": "Person A" }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E056_null_fixity/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E056_null_fixity/v1/inventory.json.sha512
new file mode 100644
index 00000000..a9bb67e9
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E056_null_fixity/v1/inventory.json.sha512
@@ -0,0 +1,1 @@
+38054db487b2c456bef029ad3be3e0a590103faa02b341f509e7fbc0bdbb3d458dfd6a408ab5ab385b3c74a3340b1adfa45242f78db2364ef4229fca3fcc24f0 inventory.json
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/0=ocfl_object_1.0
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/inventory.json b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/inventory.json
new file mode 100644
index 00000000..87bc33a9
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/inventory.json
@@ -0,0 +1,48 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v2",
+  "id": "ark:123/erroneous_switching_logical_path",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ],
+    "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "v1/content/b_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with three files",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file.txt"
+        ],
+        "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "b_file.txt", "erroneous_switching_logical_path"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    },
+    "v2": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with three files",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file.txt"
+        ],
+        "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "b_file.txt", "valid_rename"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/inventory.json.sha512
new file mode 100644
index 00000000..37f92915
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/inventory.json.sha512
@@ -0,0 +1,1 @@
+a6897bae9369c794a699f0e03bb9bef8a074cc67065ba79bafdda65524152309fe75382488443ef394d558d932bbeb25c517480c5ada91c651de795ee2e15b46 inventory.json
diff --git a/extra_fixtures/bad-objects/E092_bad_manifest_digest/v1/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E092_bad_manifest_digest/v1/content/a_file.txt
rename to extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/content/a_file.txt
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/content/b_file.txt b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/content/b_file.txt
new file mode 100644
index 00000000..7db2339f
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/content/b_file.txt
@@ -0,0 +1,1 @@
+To b [a file] or not to b [a file], that is the error being illustrated.
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/inventory.json
new file mode 100644
index 00000000..62332696
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/inventory.json
@@ -0,0 +1,32 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "ark:123/erroneous_switching_logical_path",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ],
+    "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "v1/content/b_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with three files",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+            "a_file.txt", "erroneous_switching_logical_path"
+        ],
+        "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "b_file.txt"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/inventory.json.sha512
new file mode 100644
index 00000000..7f4aa521
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v1/inventory.json.sha512
@@ -0,0 +1,1 @@
+71da364898764d0c964e88b838a13b35d95de7ab555d0318bedb8fd10c64e8b8a1ca1c37a259cacac0b53d479be328ee74374348ab8d282dc81b4388ecbc61a9 inventory.json
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v2/inventory.json b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v2/inventory.json
new file mode 100644
index 00000000..87bc33a9
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v2/inventory.json
@@ -0,0 +1,48 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v2",
+  "id": "ark:123/erroneous_switching_logical_path",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ],
+    "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "v1/content/b_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with three files",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file.txt"
+        ],
+        "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "b_file.txt", "erroneous_switching_logical_path"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    },
+    "v2": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with three files",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file.txt"
+        ],
+        "418a432dbca769d6595bfe4211c37d2a73a8acfebc997d5fbda1989c4140d9f37d2795fb5380ea49cf74dd97f1ed944280440136b488f22f64a97a34621bf7c1": [
+	  "b_file.txt", "valid_rename"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v2/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v2/inventory.json.sha512
new file mode 100644
index 00000000..37f92915
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_content_for_logical_path/v2/inventory.json.sha512
@@ -0,0 +1,1 @@
+a6897bae9369c794a699f0e03bb9bef8a074cc67065ba79bafdda65524152309fe75382488443ef394d558d932bbeb25c517480c5ada91c651de795ee2e15b46 inventory.json
diff --git a/extra_fixtures/bad-objects/E094_message_not_a_string/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E094_message_not_a_string/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/0=ocfl_object_1.0
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/inventory.json b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/inventory.json
new file mode 100644
index 00000000..447364fd
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/inventory.json
@@ -0,0 +1,39 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v2",
+  "id": "ark:123/abc",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with one file",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file_different_name_hence_different_object_state.txt"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    },
+    "v2": {
+      "created": "2022-03-17T16:40:00Z",
+      "message": "An version with one file but the state has been broken in v1",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file_renamed_legal.txt"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/inventory.json.sha512
new file mode 100644
index 00000000..9238542d
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/inventory.json.sha512
@@ -0,0 +1,1 @@
+28cc0eb3def8e29056aa518094b6b19d7789d7788cd71aac863465bab7a1760eca092f8602da91e73ce9ae792dc5dc6f5f783a38324edbc2ed06100ed122ab83 inventory.json
diff --git a/extra_fixtures/bad-objects/E094_message_not_a_string/v1/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E094_message_not_a_string/v1/content/a_file.txt
rename to extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/content/a_file.txt
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/inventory.json
new file mode 100644
index 00000000..a0f568c2
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/inventory.json
@@ -0,0 +1,26 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "ark:123/abc",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with one file",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file.txt"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/inventory.json.sha512
new file mode 100644
index 00000000..35c7a7cf
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v1/inventory.json.sha512
@@ -0,0 +1,1 @@
+f889cd4ba8cfd5b52c5f8c9ca99cb404586e60ee5d5b9b5508338f296776bf613719175253d9027c7e166ede7182785889b5ca59e19e441e47cde56b5bc20949 inventory.json
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v2/inventory.json b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v2/inventory.json
new file mode 100644
index 00000000..447364fd
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v2/inventory.json
@@ -0,0 +1,39 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v2",
+  "id": "ark:123/abc",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "An version with one file",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file_different_name_hence_different_object_state.txt"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    },
+    "v2": {
+      "created": "2022-03-17T16:40:00Z",
+      "message": "An version with one file but the state has been broken in v1",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file_renamed_legal.txt"
+        ]
+      },
+      "user": {
+        "address": "mailto:a_person@example.org",
+        "name": "A Person"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v2/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v2/inventory.json.sha512
new file mode 100644
index 00000000..9238542d
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E066_changed_v1_logical_path/v2/inventory.json.sha512
@@ -0,0 +1,1 @@
+28cc0eb3def8e29056aa518094b6b19d7789d7788cd71aac863465bab7a1760eca092f8602da91e73ce9ae792dc5dc6f5f783a38324edbc2ed06100ed122ab83 inventory.json
diff --git a/extra_fixtures/bad-objects/E096_manifest_repeated_digest/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E096_manifest_repeated_digest/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E092_bad_manifest_digest/inventory.json b/extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E092_bad_manifest_digest/inventory.json
rename to extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/inventory.json
diff --git a/extra_fixtures/bad-objects/E092_bad_manifest_digest/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E092_bad_manifest_digest/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/v1/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/content/a_file.txt
rename to extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/v1/content/a_file.txt
diff --git a/extra_fixtures/bad-objects/E092_bad_manifest_digest/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E092_bad_manifest_digest/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E092_bad_manifest_digest/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E092_bad_manifest_digest/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E092_bad_manifest_digest/v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E097_fixity_repeated_digest/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E097_fixity_repeated_digest/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v2/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E099_bad_content_path_elements/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E094_message_not_a_string/0=ocfl_object_1.0
similarity index 100%
rename from extra_fixtures/bad-objects/E099_bad_content_path_elements/0=ocfl_object_1.0
rename to extra_fixtures/1.0/bad-objects/E094_message_not_a_string/0=ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E094_message_not_a_string/inventory.json b/extra_fixtures/1.0/bad-objects/E094_message_not_a_string/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E094_message_not_a_string/inventory.json
rename to extra_fixtures/1.0/bad-objects/E094_message_not_a_string/inventory.json
diff --git a/extra_fixtures/bad-objects/E094_message_not_a_string/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E094_message_not_a_string/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E094_message_not_a_string/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E094_message_not_a_string/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E094_message_not_a_string/v1/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/content/a_file.txt
rename to extra_fixtures/1.0/bad-objects/E094_message_not_a_string/v1/content/a_file.txt
diff --git a/extra_fixtures/bad-objects/E094_message_not_a_string/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E094_message_not_a_string/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E094_message_not_a_string/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E094_message_not_a_string/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E094_message_not_a_string/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E094_message_not_a_string/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E094_message_not_a_string/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E094_message_not_a_string/v1/inventory.json.sha512
diff --git a/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/0=ocfl_object_1.0
new file mode 100644
index 00000000..4d1d62c7
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/0=ocfl_object_1.0
@@ -0,0 +1,1 @@
+ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E096_manifest_repeated_digest/inventory.json b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E096_manifest_repeated_digest/inventory.json
rename to extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/inventory.json
diff --git a/extra_fixtures/bad-objects/E096_manifest_repeated_digest/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E096_manifest_repeated_digest/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E099_bad_content_path_elements/v1/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E099_bad_content_path_elements/v1/content/a_file.txt
rename to extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/content/a_file.txt
diff --git a/extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/content/duplicate_of_a_file.txt b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/content/duplicate_of_a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/content/duplicate_of_a_file.txt
rename to extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/content/duplicate_of_a_file.txt
diff --git a/extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E096_manifest_repeated_digest/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E096_manifest_repeated_digest/v1/inventory.json.sha512
diff --git a/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/0=ocfl_object_1.0
new file mode 100644
index 00000000..4d1d62c7
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/0=ocfl_object_1.0
@@ -0,0 +1,1 @@
+ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E097_fixity_repeated_digest/inventory.json b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E097_fixity_repeated_digest/inventory.json
rename to extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/inventory.json
diff --git a/extra_fixtures/bad-objects/E097_fixity_repeated_digest/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E097_fixity_repeated_digest/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/inventory.json.sha512
diff --git a/extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/content/duplicate_of_a_file.txt b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/content/a_file.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/content/duplicate_of_a_file.txt
rename to extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/content/a_file.txt
diff --git a/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/content/duplicate_of_a_file.txt b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/content/duplicate_of_a_file.txt
new file mode 100644
index 00000000..2baefe4a
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/content/duplicate_of_a_file.txt
@@ -0,0 +1,1 @@
+Hello! I am a file.
diff --git a/extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E097_fixity_repeated_digest/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E097_fixity_repeated_digest/v1/inventory.json.sha512
diff --git a/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/0=ocfl_object_1.0 b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/0=ocfl_object_1.0
new file mode 100644
index 00000000..4d1d62c7
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/0=ocfl_object_1.0
@@ -0,0 +1,1 @@
+ocfl_object_1.0
diff --git a/extra_fixtures/bad-objects/E099_bad_content_path_elements/inventory.json b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E099_bad_content_path_elements/inventory.json
rename to extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/inventory.json
diff --git a/extra_fixtures/bad-objects/E099_bad_content_path_elements/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E099_bad_content_path_elements/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/inventory.json.sha512
diff --git a/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/v1/content/a_file.txt b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/v1/content/a_file.txt
new file mode 100644
index 00000000..2baefe4a
--- /dev/null
+++ b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/v1/content/a_file.txt
@@ -0,0 +1,1 @@
+Hello! I am a file.
diff --git a/extra_fixtures/bad-objects/E099_bad_content_path_elements/v1/inventory.json b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/v1/inventory.json
similarity index 100%
rename from extra_fixtures/bad-objects/E099_bad_content_path_elements/v1/inventory.json
rename to extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/v1/inventory.json
diff --git a/extra_fixtures/bad-objects/E099_bad_content_path_elements/v1/inventory.json.sha512 b/extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/v1/inventory.json.sha512
similarity index 100%
rename from extra_fixtures/bad-objects/E099_bad_content_path_elements/v1/inventory.json.sha512
rename to extra_fixtures/1.0/bad-objects/E099_bad_content_path_elements/v1/inventory.json.sha512
diff --git a/extra_fixtures/good-objects/ten_level_deep_directories.zip b/extra_fixtures/1.0/good-objects/ten_level_deep_directories.zip
similarity index 100%
rename from extra_fixtures/good-objects/ten_level_deep_directories.zip
rename to extra_fixtures/1.0/good-objects/ten_level_deep_directories.zip
diff --git a/extra_fixtures/warn-objects/W003_empty_content_dir.zip b/extra_fixtures/1.0/warn-objects/W003_empty_content_dir.zip
similarity index 100%
rename from extra_fixtures/warn-objects/W003_empty_content_dir.zip
rename to extra_fixtures/1.0/warn-objects/W003_empty_content_dir.zip
diff --git a/extra_fixtures/1.1/bad-objects/E003_two_declarations/0=ocfl_object_1.0 b/extra_fixtures/1.1/bad-objects/E003_two_declarations/0=ocfl_object_1.0
new file mode 100644
index 00000000..4d1d62c7
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E003_two_declarations/0=ocfl_object_1.0
@@ -0,0 +1,1 @@
+ocfl_object_1.0
diff --git a/extra_fixtures/1.1/bad-objects/E003_two_declarations/0=ocfl_object_1.1 b/extra_fixtures/1.1/bad-objects/E003_two_declarations/0=ocfl_object_1.1
new file mode 100644
index 00000000..fe195909
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E003_two_declarations/0=ocfl_object_1.1
@@ -0,0 +1,2 @@
+ocfl_object_1.1
+
diff --git a/extra_fixtures/1.1/bad-objects/E003_two_declarations/inventory.json b/extra_fixtures/1.1/bad-objects/E003_two_declarations/inventory.json
new file mode 100644
index 00000000..92adbd34
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E003_two_declarations/inventory.json
@@ -0,0 +1,26 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "ark:123/abc",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "Test case",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file.txt"
+        ]
+      },
+      "user": {
+        "name": "A Person",	    
+        "address": "mailto:a_person@somewhere.edu"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.1/bad-objects/E003_two_declarations/inventory.json.sha512 b/extra_fixtures/1.1/bad-objects/E003_two_declarations/inventory.json.sha512
new file mode 100644
index 00000000..5dffdec1
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E003_two_declarations/inventory.json.sha512
@@ -0,0 +1,1 @@
+2955a152a4d6b3f6a30022e2c00c84578f91bb8a3649d73b3590dd548367e7aaf1a8f91834fe5e7c80e9aa46b393636fc0882dc2ce1e20881e2e59ac2b409b9b inventory.json
diff --git a/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/content/a_file.txt b/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/content/a_file.txt
new file mode 100644
index 00000000..2baefe4a
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/content/a_file.txt
@@ -0,0 +1,1 @@
+Hello! I am a file.
diff --git a/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/inventory.json b/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/inventory.json
new file mode 100644
index 00000000..92adbd34
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/inventory.json
@@ -0,0 +1,26 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "ark:123/abc",
+  "manifest": {
+    "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+      "v1/content/a_file.txt"
+    ]
+  },
+  "type": "https://ocfl.io/1.0/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "Test case",
+      "state": {
+        "43a43fe8a8a082d3b5343dfaf2fd0c8b8e370675b1f376e92e9994612c33ea255b11298269d72f797399ebb94edeefe53df243643676548f584fb8603ca53a0f": [
+          "a_file.txt"
+        ]
+      },
+      "user": {
+        "name": "A Person",	    
+        "address": "mailto:a_person@somewhere.edu"
+      }
+    }
+  }
+}
diff --git a/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/inventory.json.sha512 b/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/inventory.json.sha512
new file mode 100644
index 00000000..5dffdec1
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E003_two_declarations/v1/inventory.json.sha512
@@ -0,0 +1,1 @@
+2955a152a4d6b3f6a30022e2c00c84578f91bb8a3649d73b3590dd548367e7aaf1a8f91834fe5e7c80e9aa46b393636fc0882dc2ce1e20881e2e59ac2b409b9b inventory.json
diff --git a/extra_fixtures/1.1/bad-objects/E111_null_fixity/0=ocfl_object_1.1 b/extra_fixtures/1.1/bad-objects/E111_null_fixity/0=ocfl_object_1.1
new file mode 100644
index 00000000..14705cb1
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E111_null_fixity/0=ocfl_object_1.1
@@ -0,0 +1,1 @@
+ocfl_object_1.1
diff --git a/extra_fixtures/1.1/bad-objects/E111_null_fixity/inventory.json b/extra_fixtures/1.1/bad-objects/E111_null_fixity/inventory.json
new file mode 100644
index 00000000..8029fc98
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E111_null_fixity/inventory.json
@@ -0,0 +1,16 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "http://example.org/minimal_no_content",
+  "fixity": null,
+  "manifest": { },
+  "type": "https://ocfl.io/1.1/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "One version and no content",
+      "state": { },
+      "user": { "address": "mailto:Person_A@example.org", "name": "Person A" }
+    }
+  }
+}
diff --git a/extra_fixtures/1.1/bad-objects/E111_null_fixity/inventory.json.sha512 b/extra_fixtures/1.1/bad-objects/E111_null_fixity/inventory.json.sha512
new file mode 100644
index 00000000..fe1e1ca4
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E111_null_fixity/inventory.json.sha512
@@ -0,0 +1,1 @@
+7852e487502fb97643658f35940b2ae97c0aaf4220701b006b8a4dd3db43588be2409435f4543a0dd5ae090b74eb8cca2240b5831adcc2a5045de07bbe3b85ec inventory.json
diff --git a/extra_fixtures/1.1/bad-objects/E111_null_fixity/v1/inventory.json b/extra_fixtures/1.1/bad-objects/E111_null_fixity/v1/inventory.json
new file mode 100644
index 00000000..8029fc98
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E111_null_fixity/v1/inventory.json
@@ -0,0 +1,16 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "http://example.org/minimal_no_content",
+  "fixity": null,
+  "manifest": { },
+  "type": "https://ocfl.io/1.1/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "One version and no content",
+      "state": { },
+      "user": { "address": "mailto:Person_A@example.org", "name": "Person A" }
+    }
+  }
+}
diff --git a/extra_fixtures/1.1/bad-objects/E111_null_fixity/v1/inventory.json.sha512 b/extra_fixtures/1.1/bad-objects/E111_null_fixity/v1/inventory.json.sha512
new file mode 100644
index 00000000..fe1e1ca4
--- /dev/null
+++ b/extra_fixtures/1.1/bad-objects/E111_null_fixity/v1/inventory.json.sha512
@@ -0,0 +1,1 @@
+7852e487502fb97643658f35940b2ae97c0aaf4220701b006b8a4dd3db43588be2409435f4543a0dd5ae090b74eb8cca2240b5831adcc2a5045de07bbe3b85ec inventory.json
diff --git a/extra_fixtures/1.1/good-objects/empty_fixity/0=ocfl_object_1.1 b/extra_fixtures/1.1/good-objects/empty_fixity/0=ocfl_object_1.1
new file mode 100644
index 00000000..14705cb1
--- /dev/null
+++ b/extra_fixtures/1.1/good-objects/empty_fixity/0=ocfl_object_1.1
@@ -0,0 +1,1 @@
+ocfl_object_1.1
diff --git a/extra_fixtures/1.1/good-objects/empty_fixity/inventory.json b/extra_fixtures/1.1/good-objects/empty_fixity/inventory.json
new file mode 100644
index 00000000..72212744
--- /dev/null
+++ b/extra_fixtures/1.1/good-objects/empty_fixity/inventory.json
@@ -0,0 +1,16 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "http://example.org/minimal_no_content",
+  "fixity": { },
+  "manifest": { },
+  "type": "https://ocfl.io/1.1/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "One version and no content, empty fixity",
+      "state": { },
+      "user": { "address": "mailto:Person_A@example.org", "name": "Person A" }
+    }
+  }
+}
diff --git a/extra_fixtures/1.1/good-objects/empty_fixity/inventory.json.sha512 b/extra_fixtures/1.1/good-objects/empty_fixity/inventory.json.sha512
new file mode 100644
index 00000000..1eb737b9
--- /dev/null
+++ b/extra_fixtures/1.1/good-objects/empty_fixity/inventory.json.sha512
@@ -0,0 +1,1 @@
+4ce09b0d61638436dc1fc7a201a7531bf2c2fe08af5d7f1a5d247fa0d40b0bcb5f12b52bf6d6e1143521aef66d0d79e4cbeac7119c9c3ca670b59fe8448f0de6 inventory.json
diff --git a/extra_fixtures/1.1/good-objects/empty_fixity/v1/inventory.json b/extra_fixtures/1.1/good-objects/empty_fixity/v1/inventory.json
new file mode 100644
index 00000000..72212744
--- /dev/null
+++ b/extra_fixtures/1.1/good-objects/empty_fixity/v1/inventory.json
@@ -0,0 +1,16 @@
+{
+  "digestAlgorithm": "sha512",
+  "head": "v1",
+  "id": "http://example.org/minimal_no_content",
+  "fixity": { },
+  "manifest": { },
+  "type": "https://ocfl.io/1.1/spec/#inventory",
+  "versions": {
+    "v1": {
+      "created": "2019-01-01T02:03:04Z",
+      "message": "One version and no content, empty fixity",
+      "state": { },
+      "user": { "address": "mailto:Person_A@example.org", "name": "Person A" }
+    }
+  }
+}
diff --git a/extra_fixtures/1.1/good-objects/empty_fixity/v1/inventory.json.sha512 b/extra_fixtures/1.1/good-objects/empty_fixity/v1/inventory.json.sha512
new file mode 100644
index 00000000..1eb737b9
--- /dev/null
+++ b/extra_fixtures/1.1/good-objects/empty_fixity/v1/inventory.json.sha512
@@ -0,0 +1,1 @@
+4ce09b0d61638436dc1fc7a201a7531bf2c2fe08af5d7f1a5d247fa0d40b0bcb5f12b52bf6d6e1143521aef66d0d79e4cbeac7119c9c3ca670b59fe8448f0de6 inventory.json
diff --git a/extract_codes.py b/extract_codes.py
index 0841980a..c5df13d6 100755
--- a/extract_codes.py
+++ b/extract_codes.py
@@ -22,7 +22,7 @@ def __init__(self, code, desc):
         """Initialize Code object."""
         self.code = code
         self.desc = desc
-        self.suffixes = dict()
+        self.suffixes = {}
 
     def add_suffix(self, suffix, link=None, desc=None):
         """Add details for one code suffix, which may be None."""
@@ -70,7 +70,7 @@ class Codes():
 
     def __init__(self):
         """Initialize Codes object."""
-        self.codes = dict()
+        self.codes = {}
 
     def add_spec(self, code, desc):
         """Add a code from the specification."""
@@ -133,7 +133,7 @@ def main():
 
     # 4. Write table of what is implemented and raise warnings
     logging.info("Writing summary to %s", VALIDATION_STATUS_MD)
-    with open(VALIDATION_STATUS_MD, "w") as fh:
+    with open(VALIDATION_STATUS_MD, "w", encoding="utf-8") as fh:
         fh.write("# Implementation status for errors and warnings\n\n")
         fh.write("The following tables show the implementation status of all errors and warnings in the OCFL v1.0 specification, with links to the specification and into the code repository.\n\n")
         fh.write("## Errors\n\n")
diff --git a/fixtures b/fixtures
index bec9060e..84917b59 160000
--- a/fixtures
+++ b/fixtures
@@ -1,1 +1,1 @@
-Subproject commit bec9060ed116ebc03bf327aa14ea93b67cc2ff8f
+Subproject commit 84917b597313256224b372a2bc08638e303b69c3
diff --git a/ocfl-validate.py b/ocfl-validate.py
index 30e1d9c9..f5653ec1 100755
--- a/ocfl-validate.py
+++ b/ocfl-validate.py
@@ -61,7 +61,8 @@
         obj = ocfl.Object(lax_digests=args.lax_digests)
         if obj.validate_inventory(path,
                                   show_warnings=show_warnings,
-                                  show_errors=not args.very_quiet):
+                                  show_errors=not args.very_quiet,
+                                  extract_spec_version=True):
             num_good += 1
     else:
         log.error("Bad path %s (%s)", path, path_type)
diff --git a/ocfl/_version.py b/ocfl/_version.py
index ac094bb0..395d4922 100644
--- a/ocfl/_version.py
+++ b/ocfl/_version.py
@@ -1,2 +1,2 @@
 """Version number for this Python implementation of OCFL."""
-__version__ = '1.2.3'
+__version__ = '1.3.0'
diff --git a/ocfl/data/validation-errors.json b/ocfl/data/validation-errors.json
index cc5a1703..fe8f6769 100644
--- a/ocfl/data/validation-errors.json
+++ b/ocfl/data/validation-errors.json
@@ -23,20 +23,21 @@
     }
   },
   "E003a": {
+    "params": ["assumed_version"],
     "description": {
-       "en": "OCFL Object version declaration file is missing"
+       "en": "OCFL Object version declaration file is missing (assuming version %s)"
     }
   },
   "E003b": {
-    "params": ["files"],
+    "params": ["files", "using_version"],
     "description": {
-       "en": "OCFL Object includes more that one file that looks like an object declaration (got %s)"
+       "en": "OCFL Object includes more that one file that looks like an object declaration (got %s), using version %s"
     }
   },
   "E003c": {
-    "params": ["path"],
+    "params": ["path", "assumed_version"],
     "description": {
-       "en": "No OCFL Object to validate at path %s. The root of an OCFL Object must be a directory containing an object declaration"
+       "en": "OCFL Object includes one or move object declaration files (starting 0=) but no valid version number could be extracted from any of them (assuming version %s)"
     }
   },
   "E003d": {
@@ -45,6 +46,12 @@
        "en": "OCFL Storage Root hierarchy includes directory %s with more that one file that looks like an object declaration, ignoring"
     }
   },
+  "E003e": {
+    "params": ["path"],
+    "description": {
+       "en": "No OCFL Object to validate at path %s. The root of an OCFL Object must be a directory containing an object declaration"
+    }
+  },
   "E004a": {
     "params": ["path", "version"],
     "description": {
@@ -57,9 +64,16 @@
        "en": "OCFL Storage Root hierarchy includes directory %s with an unrecognized object declaration %s, ignoring"
     }
   },
+  "E006": {
+    "params": ["filename"],
+    "description": {
+      "en": "The OCFL object conformance declaration filename must be 0=ocfl_object_, followed by the OCFL specification version number, got %s instead"
+    }
+  },
   "E007": {
+    "params": ["filename"],
     "description": {
-       "en": "OCFL Object declaration file contents do not match file name without leading 0= (the 'dvalue')"
+       "en": "OCFL Object declaration file %s contents do not match file name without leading 0= (the 'dvalue')"
     }
   },
   "E008": {
@@ -170,10 +184,22 @@
        "en": "OCFL Object %s inventory id `%s` does not match the value in the root inventory `%s`"
     }
   },
-  "E038": {
-    "params": ["where"],
+  "E038a": {
+    "params": ["where", "expected", "got"],
     "description": {
-       "en": "OCFL Object %s inventory `type` attribute has wrong value"
+       "en": "OCFL Object %s inventory `type` attribute has wrong value (expected %s, got %s)"
+    }
+  },
+  "E038b": {
+    "params": ["where", "got", "assumed_spec_version"],
+    "description": {
+       "en": "OCFL Object %s inventory `type` attribute does not look like a valid specification URI (got %s), will proceed as if using version %s"
+    }
+  },
+  "E038c": {
+    "params": ["where", "got", "assumed_spec_version"],
+    "description": {
+       "en": "OCFL Object %s inventory `type` attribute has an unsupported specification version number (%s), will proceed as if using version %s"
     }
   },
   "E039": {
@@ -278,12 +304,6 @@
        "en": "OCFL Object %s inventory state refers to one or more digests that are not in the manifest (%s)"
     }
   },
-  "E050b": {
-    "params": ["where", "digests"],
-    "description": {
-       "en": "OCFL Object %s inventory manifest refers to one or more digests that are not in any state (%s)"
-    }
-  },
   "E050c": {
     "params": ["where", "version"],
     "description": {
@@ -352,7 +372,7 @@
   "E056b": {
     "params": ["where", "algorithm"],
     "description": {
-       "en": "OCFL Object %s inventory fixity block includes and unknown digest algorithm name %s"
+       "en": "OCFL Object %s inventory fixity block includes a key that is not a known digest algorithm name: %s"
     }
   },
   "E057a": {
@@ -421,15 +441,15 @@
     }
   },
  "E066b": {
-    "params": ["version" , "prior_head", "where"],
+    "params": ["prior_head", "version" , "where", "logical_paths", "only_in"],
     "description": {
-       "en": "OCFL Object inventory manifest for %s in %s doesn't have a subset of manifest entries of inventory for %s"
+       "en": "OCFL Object %s inventory %s version state doesn't have same logical paths as same version in %s inventory (paths %s only in %s inventory)"
     }
   },
   "E066c": {
-    "params": ["prior_head", "version", "file", "prior_content", "where", "current_content"],
+    "params": ["prior_head", "version", "logical_path", "prior_content", "where", "current_content"],
     "description": {
-       "en": "OCFL Object %s inventory %s version state has file %s that maps to different content files (%s) than in the %s inventory (%s)"
+       "en": "OCFL Object %s inventory %s version state has logical path %s that maps to content path %s, but in %s inventory it maps to content path %s"
     }
   },
   "E066d": {
@@ -470,7 +490,7 @@
   },
   "E087": {
     "description": {
-       "en": "NOTE - E087 is a processing instruction and can't be tested for."
+       "en": "NOTE - E087 is a processing instruction and can't be tested for"
     }
   },
   "E088": {
@@ -480,7 +500,7 @@
   },
   "E090": {
     "description": {
-       "en": "NOTE - E090 is a processing instruction and can't be tested for."
+       "en": "NOTE - E090 is a processing instruction and can't be tested for"
     }
   },
   "E091": {
@@ -522,7 +542,7 @@
   "E095a": {
     "params": ["where", "version", "path"],
     "description": {
-       "en": "OCFL Object %s inventory version %s state has logical path %s used more than once."
+       "en": "OCFL Object %s inventory version %s state has logical path %s used more than once"
     }
   },
   "E095b": {
@@ -558,13 +578,19 @@
   "E101a": {
     "params": ["where", "path"],
     "description": {
-       "en": "OCFL Object %s inventory manifest content path %s is repeated."
+       "en": "OCFL Object %s inventory manifest content path %s is repeated"
     }
   },
   "E101b": {
     "params": ["where", "path"],
     "description": {
-       "en": "OCFL Object %s inventory manifest content path %s used as both a directory and a file path."
+       "en": "OCFL Object %s inventory manifest content path %s used as both a directory and a file path"
+    }
+  },
+  "E111": {
+    "params": ["where"],
+    "description": {
+      "en": "OCFL Object %s inventory includes a fixity key with value that isn't a JSON object"
     }
   },
 
diff --git a/ocfl/inventory_validator.py b/ocfl/inventory_validator.py
index 27eb8073..6d5770f8 100644
--- a/ocfl/inventory_validator.py
+++ b/ocfl/inventory_validator.py
@@ -10,10 +10,10 @@
 from .w3c_datetime import str_to_datetime
 
 
-def get_file_map(inventory, version):
-    """Get a map of files in state to files on disk for version in inventory.
+def get_logical_path_map(inventory, version):
+    """Get a map of logical paths in state to files on disk for version in inventory.
 
-    Returns a dictionary: file_in_state -> set(content_files)
+    Returns a dictionary: logical_path_in_state -> set(content_files)
 
     The set of content_files may includes references to duplicate files in
     later versions than the version being described.
@@ -32,10 +32,11 @@ class InventoryValidator():
     """Class for OCFL Inventory Validator."""
 
     def __init__(self, log=None, where='???',
-                 lax_digests=False):
+                 lax_digests=False, spec_version='1.0'):
         """Initialize OCFL Inventory Validator."""
         self.log = ValidationLogger() if log is None else log
         self.where = where
+        self.spec_version = spec_version
         # Object state
         self.inventory = None
         self.id = None
@@ -47,6 +48,8 @@ def __init__(self, log=None, where='???',
         self.head = 'UNKNOWN'
         # Validation control
         self.lax_digests = lax_digests
+        # Configuration
+        self.spec_versions_supported = ('1.0', '1.1')
 
     def error(self, code, **args):
         """Error with added context."""
@@ -56,8 +59,13 @@ def warning(self, code, **args):
         """Warning with added context."""
         self.log.warning(code, where=self.where, **args)
 
-    def validate(self, inventory):
-        """Validate a given inventory."""
+    def validate(self, inventory, extract_spec_version=False):
+        """Validate a given inventory.
+
+        If extract_spec_version is True then will look at the type value to determine
+        the specification version. In the case that there is no type value or it isn't
+        valid, then other tests will be based on the version given in self.spec_version.
+        """
         # Basic structure
         self.inventory = inventory
         if 'id' in inventory:
@@ -74,8 +82,18 @@ def validate(self, inventory):
             self.error("E036a")
         if 'type' not in inventory:
             self.error("E036b")
-        elif inventory['type'] != 'https://ocfl.io/1.0/spec/#inventory':
-            self.error("E038")
+        elif not isinstance(inventory['type'], str):
+            self.error("E999")
+        elif extract_spec_version:
+            m = re.match(r'''https://ocfl.io/(\d+.\d)/spec/#inventory''', inventory['type'])
+            if not m:
+                self.error('E038b', got=inventory['type'], assumed_spec_version=self.spec_version)
+            elif m.group(1) in self.spec_versions_supported:
+                self.spec_version = m.group(1)
+            else:
+                self.error("E038c", got=m.group(1), assumed_spec_version=self.spec_version)
+        elif inventory['type'] != 'https://ocfl.io/' + self.spec_version + '/spec/#inventory':
+            self.error("E038a", expected='https://ocfl.io/' + self.spec_version + '/spec/#inventory', got=inventory['type'])
         if 'digestAlgorithm' not in inventory:
             self.error("E036c")
         elif inventory['digestAlgorithm'] == 'sha512':
@@ -178,7 +196,10 @@ def validate_fixity(self, fixity, manifest_files):
         listed in the manifest are referenced.
         """
         if not isinstance(fixity, dict):
-            self.error('E056a')
+            # The value of fixity must be a JSON object. In v1.0 I catch not an object
+            # as part of E056 but this was clarified as E111 in v1.1. The value may
+            # be an empty object in either case
+            self.error('E056a' if self.spec_version == '1.0' else 'E111')
         else:
             for digest_algorithm in fixity:
                 known_digest = True
@@ -381,7 +402,7 @@ def check_digests_present_and_used(self, manifest_files, digests_used):
             self.error("E050a", digests=", ".join(sorted(not_in_manifest)))
         not_in_state = in_manifest.difference(in_state)
         if len(not_in_state) > 0:
-            self.error("E050b", digests=", ".join(sorted(not_in_state)))
+            self.error("E107", digests=", ".join(sorted(not_in_state)))
 
     def digest_regex(self):
         """Return regex for validating un-normalized digest format."""
@@ -442,11 +463,11 @@ def check_content_path(self, path, content_paths, content_directories):
     def validate_as_prior_version(self, prior):
         """Check that prior is a valid prior version of the current inventory object.
 
-        The input prior is also expected to be an InventoryValidator object and
-        both self and prior inventories are assumed to have been checked for
+        The input variable prior is also expected to be an InventoryValidator object
+        and both self and prior inventories are assumed to have been checked for
         internal consistency.
         """
-        # Must have a subset of versions which also check zero padding format etc.
+        # Must have a subset of versions which also checks zero padding format etc.
         if not set(prior.all_versions) < set(self.all_versions):
             self.error('E066a', prior_head=prior.head)
         else:
@@ -458,20 +479,26 @@ def validate_as_prior_version(self, prior):
                 # direct check on whether the state blocks match
                 if prior.digest_algorithm == self.digest_algorithm:
                     self.compare_states_for_version(prior, version)
-                # Now check the mappings from state to content files which must
+                # Now check the mappings from state to logical path, which must
                 # be consistent even if the digestAlgorithm is different between
-                # versions
-                prior_map = get_file_map(prior.inventory, version)
-                self_map = get_file_map(self.inventory, version)
-                if prior_map.keys() != self_map.keys():
-                    self.error('E066b', version=version, prior_head=prior.head)
+                # versions. Get maps from logical paths to files on disk:
+                prior_map = get_logical_path_map(prior.inventory, version)
+                self_map = get_logical_path_map(self.inventory, version)
+                # Look first for differences in logical paths listed
+                only_in_prior = prior_map.keys() - self_map.keys()
+                only_in_self = self_map.keys() - prior_map.keys()
+                if only_in_prior or only_in_self:
+                    if only_in_prior:
+                        self.error('E066b', version=version, prior_head=prior.head, only_in=prior.head, logical_paths=','.join(only_in_prior))
+                    if only_in_self:
+                        self.error('E066b', version=version, prior_head=prior.head, only_in=self.where, logical_paths=','.join(only_in_self))
                 else:
-                    # Check them all...
-                    for file in prior_map:
-                        if not prior_map[file].issubset(self_map[file]):
+                    # Check them all in details - digests must match
+                    for logical_path, this_map in prior_map.items():
+                        if not this_map.issubset(self_map[logical_path]):
                             self.error('E066c', version=version, prior_head=prior.head,
-                                       file=file, prior_content=','.join(prior_map[file]),
-                                       current_content=','.join(self_map[file]))
+                                       logical_path=logical_path, prior_content=','.join(this_map),
+                                       current_content=','.join(self_map[logical_path]))
                 # Check metadata
                 prior_version = prior.inventory['versions'][version]
                 self_version = self.inventory['versions'][version]
@@ -482,7 +509,8 @@ def validate_as_prior_version(self, prior):
     def compare_states_for_version(self, prior, version):
         """Compare state blocks for version between self and prior.
 
-        The digest algorithm must be the same in both, do not call otherwise!
+        Assumes the same digest algorithm in both, do not call otherwise!
+
         Looks only for digests that appear in one but not in the other, the code
         in validate_as_prior_version(..) does a check for whether the same sets
         of logical files appear and we don't want to duplicate an error message
diff --git a/ocfl/namaste.py b/ocfl/namaste.py
index 6dff2b50..ee282859 100644
--- a/ocfl/namaste.py
+++ b/ocfl/namaste.py
@@ -25,7 +25,9 @@ def find_namastes(d, dir='', pyfs=None, limit=10):
     """Find NAMASTE files with tag d in dir, return list of Namaste objects.
 
     limit sets a limit on the number of Namaste objects returned, a NamasteException
-    will be raised if more than limit files with tag d are found.
+    will be raised if more than limit files with tag d are found. Returns the values
+    sorter by filename to ensure consistent behaviour when multiple match files are
+    present.
     """
     prefix = str(d) + '='
     if pyfs is not None:
@@ -34,7 +36,7 @@ def find_namastes(d, dir='', pyfs=None, limit=10):
         filenames = [f for f in os.listdir(dir) if f.startswith(prefix)]
     if len(filenames) > limit:
         raise NamasteException("Found too many Namaste files with tag %s in %s" % (d, dir))
-    return [Namaste(d, tvalue=filename[len(prefix):]) for filename in filenames]
+    return [Namaste(d, tvalue=filename[len(prefix):]) for filename in sorted(filenames)]
 
 
 def get_namaste(d, dir):
@@ -96,7 +98,7 @@ def write(self, dir='', pyfs=None):
         if pyfs is not None:
             pyfs.writetext(fs.path.join(dir, self.filename), self.content + "\n")
         else:
-            with open(os.path.join(dir, self.filename), 'w') as fh:
+            with open(os.path.join(dir, self.filename), 'w', encoding="utf-8") as fh:
                 fh.write(self.content + "\n")
 
     def check_content(self, dir='', pyfs=None):
@@ -112,7 +114,7 @@ def check_content(self, dir='', pyfs=None):
         else:
             if not os.path.isfile(filepath):
                 raise NamasteException("Namaste file %s does not exist!" % (filepath))
-            with open(filepath, 'r') as fh:
+            with open(filepath, 'r', encoding="utf-8") as fh:
                 content = fh.read()
         if self.tvalue != self._tr_func(content):
             raise NamasteException("Content of Namaste file %s doesn't match tvalue %s" % (filepath, self.tvalue))
diff --git a/ocfl/object.py b/ocfl/object.py
index 83b3e741..e8fb726e 100755
--- a/ocfl/object.py
+++ b/ocfl/object.py
@@ -532,14 +532,14 @@ def validate(self, objdir, show_warnings=True, show_errors=True, check_digests=T
             self.log.info("OCFL object at %s is INVALID", objdir)
         return passed
 
-    def validate_inventory(self, path, show_warnings=True, show_errors=True):
+    def validate_inventory(self, path, show_warnings=True, show_errors=True, extract_spec_version=False):
         """Validate just an OCFL Object inventory at path."""
         validator = Validator(show_warnings=show_warnings,
                               show_errors=show_errors)
         try:
             (inv_dir, inv_file) = fs.path.split(path)
             validator.obj_fs = open_fs(inv_dir, create=False)
-            validator.validate_inventory(inv_file, where='standalone')
+            validator.validate_inventory(inv_file, where='standalone', extract_spec_version=extract_spec_version)
         except fs.errors.ResourceNotFound:
             validator.log.error('E033', where='standalone', explanation='failed to open directory')
         except ValidatorAbortException:
diff --git a/ocfl/object_utils.py b/ocfl/object_utils.py
index 3ff2298e..2ca80736 100755
--- a/ocfl/object_utils.py
+++ b/ocfl/object_utils.py
@@ -134,9 +134,10 @@ def find_path_type(path):
     namastes = find_namastes(0, pyfs=pyfs)
     if len(namastes) == 0:
         return "no 0= declaration file"
-    if len(namastes) > 1:
-        return "more than one 0= declaration file"
-    m = re.match(r'''ocfl(_object)?_(\d+\.\d+)$''', namastes[0].tvalue)
-    if m:
-        return 'root' if m.group(1) is None else 'object'
-    return "unrecognized 0= declaration file 0=%s" % (namastes[0].tvalue)
+    # Look at the first 0= Namaste file that is of OCFL form to determine type, if there are
+    # multiple declarations this will be caught later
+    for namaste in namastes:
+        m = re.match(r'''ocfl(_object)?_(\d+\.\d+)$''', namaste.tvalue)
+        if m:
+            return 'root' if m.group(1) is None else 'object'
+    return "unrecognized 0= declaration file or files (first is %s)" % (namastes[0].tvalue)
diff --git a/ocfl/validation_logger.py b/ocfl/validation_logger.py
index 2306fd9c..1bf997c2 100644
--- a/ocfl/validation_logger.py
+++ b/ocfl/validation_logger.py
@@ -28,7 +28,7 @@ def __init__(self, show_warnings=False, show_errors=True,
         if validation_codes is not None:
             self.validation_codes = validation_codes
         elif self.validation_codes is None:
-            with open(os.path.join(os.path.dirname(__file__), 'data/validation-errors.json'), 'r') as fh:
+            with open(os.path.join(os.path.dirname(__file__), 'data/validation-errors.json'), 'r', encoding="utf-8") as fh:
                 self.validation_codes = json.load(fh)
 
     def error_or_warning(self, code, severity='error', **args):
diff --git a/ocfl/validator.py b/ocfl/validator.py
index 88661147..6fc3dc10 100644
--- a/ocfl/validator.py
+++ b/ocfl/validator.py
@@ -41,6 +41,7 @@ def __init__(self, log=None, show_warnings=False, show_errors=True, check_digest
         ]
         # The following actually initialized in initialize() method
         self.id = None
+        self.spec_version = None
         self.digest_algorithm = None
         self.content_directory = None
         self.inventory_digest_files = None
@@ -54,6 +55,7 @@ def initialize(self):
         Must be called between attempts to validate objects.
         """
         self.id = None
+        self.spec_version = '1.0'  # default to latest published version
         self.digest_algorithm = 'sha512'
         self.content_directory = 'content'
         self.inventory_digest_files = {}  # index by version_dir, algorithms may differ
@@ -77,16 +79,34 @@ def validate(self, path):
                 self.obj_fs = path
                 path = self.obj_fs.desc('')
         except fs.errors.CreateFailed:
-            self.log.error('E003c', path=path)
+            self.log.error('E003e', path=path)
             return False
-        # Object declaration
+        # Object declaration, set spec version number. If there are multiple declarations,
+        # look for the lastest object version then report any others as errors
         namastes = find_namastes(0, pyfs=self.obj_fs)
         if len(namastes) == 0:
-            self.log.error('E003a')
-        elif len(namastes) > 1:
-            self.log.error('E003b', files=len(namastes))
-        elif not namastes[0].content_ok(pyfs=self.obj_fs):
-            self.log.error('E007')
+            self.log.error('E003a', assumed_version=self.spec_version)
+        else:
+            spec_version = None
+            for namaste in namastes:
+                # Extract and check spec version number
+                this_file_version = None
+                for version in ('1.1', '1.0'):
+                    if namaste.filename == '0=ocfl_object_' + version:
+                        this_file_version = version
+                        break
+                if this_file_version is None:
+                    self.log.error('E006', filename=namaste.filename)
+                elif spec_version is None or this_file_version > spec_version:
+                    spec_version = this_file_version
+                    if not namaste.content_ok(pyfs=self.obj_fs):
+                        self.log.error('E007', filename=namaste.filename)
+            if spec_version is None:
+                self.log.error('E003c', assumed_version=self.spec_version)
+            else:
+                self.spec_version = spec_version
+                if len(namastes) > 1:
+                    self.log.error('E003b', files=len(namastes), using_version=self.spec_version)
         # Object root inventory file
         inv_file = 'inventory.json'
         if not self.obj_fs.exists(inv_file):
@@ -102,7 +122,7 @@ def validate(self, path):
             self.digest_algorithm = inv_validator.digest_algorithm
             self.validate_inventory_digest(inv_file, self.digest_algorithm)
             # Object root
-            self.validate_object_root(all_versions)
+            self.validate_object_root(all_versions, already_checked=[namaste.filename for namaste in namastes])
             # Version inventory files
             (prior_manifest_digests, prior_fixity_digests) = self.validate_version_inventories(all_versions)
             if inventory_is_valid:
@@ -112,12 +132,17 @@ def validate(self, path):
             pass
         return self.log.num_errors == 0
 
-    def validate_inventory(self, inv_file, where='root'):
+    def validate_inventory(self, inv_file, where='root', extract_spec_version=False):
         """Validate a given inventory file, record errors with self.log.error().
 
         Returns inventory object for use in later validation
         of object content. Does not look at anything else in the
         object itself.
+
+        where - used for reporting messages of where inventory is in object
+
+        extract_spec_version - if set True will attempt to take spec_version from the
+            inventory itself instead of using the spec_version provided
         """
         try:
             with self.obj_fs.openbin(inv_file, 'r') as fh:
@@ -126,8 +151,9 @@ def validate_inventory(self, inv_file, where='root'):
             self.log.error('E033', where=where, explanation=str(e))
             raise ValidatorAbortException
         inv_validator = InventoryValidator(log=self.log, where=where,
-                                           lax_digests=self.lax_digests)
-        inv_validator.validate(inventory)
+                                           lax_digests=self.lax_digests,
+                                           spec_version=self.spec_version)
+        inv_validator.validate(inventory, extract_spec_version=extract_spec_version)
         return inventory, inv_validator
 
     def validate_inventory_digest(self, inv_file, digest_algorithm, where="root"):
@@ -159,17 +185,17 @@ def validate_inventory_digest_match(self, inv_file, inv_digest_file):
         else:
             self.log.error("E058b", inv_digest_file=inv_digest_file)
 
-    def validate_object_root(self, version_dirs):
+    def validate_object_root(self, version_dirs, already_checked):
         """Validate object root.
 
         All expected_files must be present and no other files.
         All expected_dirs must be present and no other dirs.
         """
-        expected_files = ['0=ocfl_object_1.0', 'inventory.json',
+        expected_files = ['0=ocfl_object_' + self.spec_version, 'inventory.json',
                           'inventory.json.' + self.digest_algorithm]
         for entry in self.obj_fs.scandir(''):
             if entry.is_file:
-                if entry.name not in expected_files:
+                if entry.name not in expected_files and entry.name not in already_checked:
                     self.log.error('E001a', file=entry.name)
             elif entry.is_dir:
                 if entry.name in version_dirs:
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..e4f45403
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,8 @@
+# Required for core code
+bagit >= 1.8.1
+dateutils >= 0.6.6
+fs >= 2.4.13
+fs_s3fs >= 1.1.1
+pairtree >= 0.8.1
+# Required for extras
+requests >= 2.20.0
diff --git a/setup.py b/setup.py
index 7bdfd09a..2c9ee836 100644
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,11 @@
 """Setup for ocfl-py."""
 import os
 import re
+from pathlib import Path
 from setuptools import setup, Command
 
 # Extract version number
-verfile = open("ocfl/_version.py", "rt").read()
+verfile = Path("ocfl/_version.py").read_text(encoding="utf-8")
 match = re.search(r"^__version__ = '(\d\.\d.\d+(\.\d+)?)'",
                   verfile, re.MULTILINE)
 if match:
@@ -53,16 +54,17 @@ def run(self):  # pylint: disable=no-self-use
                  "Programming Language :: Python :: 3.6",
                  "Programming Language :: Python :: 3.7",
                  "Programming Language :: Python :: 3.8",
+                 "Programming Language :: Python :: 3.9",
                  "Topic :: Internet :: WWW/HTTP",
                  "Topic :: Software Development :: "
                  "Libraries :: Python Modules"],
     url='https://github.com/zimeon/ocfl-py',
     description='ocfl-py - A Python implementation of OCFL',
-    long_description=open('README').read(),
+    long_description=Path('README').read_text(encoding="utf-8"),
     install_requires=[
         'bagit>=1.8.1',
         'dateutils>=0.6.6',
-        'fs>2.4.0',
+        'fs>2.4.13',
         'fs_s3fs>=1.1.1',
         'pairtree>=0.8.1'
     ],


Test patch:
diff --git a/extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/content/test.txt b/extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/content/test.txt
similarity index 100%
rename from extra_fixtures/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/content/test.txt
rename to extra_fixtures/1.0/bad-objects/E093_fixity_digest_mismatch_in_v1/v1/content/test.txt
diff --git a/tests/test_bagger.py b/tests/test_bagger.py
index ac5fd658..2b8a6436 100644
--- a/tests/test_bagger.py
+++ b/tests/test_bagger.py
@@ -38,11 +38,11 @@ def test_bag_extracted_version(self):
         """Test bag_extracted_version method."""
         # Write bag with no metadata
         tempdir = tempfile.mkdtemp(prefix='test_bag1')
-        with open(os.path.join(tempdir, 'my_file'), 'w') as fh:
+        with open(os.path.join(tempdir, 'my_file'), 'w', encoding="utf-8") as fh:
             fh.write("Something\n")
         metadata = VersionMetadata()
         bag_extracted_version(tempdir, metadata)
-        with open(os.path.join(tempdir, 'bag-info.txt'), 'r') as fh:
+        with open(os.path.join(tempdir, 'bag-info.txt'), 'r', encoding="utf-8") as fh:
             info = fh.read()
         self.assertNotIn('Contact-Email', info)
         self.assertNotIn('Contact-Name', info)
@@ -51,7 +51,7 @@ def test_bag_extracted_version(self):
         self.assertIn('Payload-Oxum', info)
         # Write bag with all metadata
         tempdir = tempfile.mkdtemp(prefix='test_bag2')
-        with open(os.path.join(tempdir, 'my_file2'), 'w') as fh:
+        with open(os.path.join(tempdir, 'my_file2'), 'w', encoding="utf-8") as fh:
             fh.write("Something else\n")
         metadata = VersionMetadata()
         metadata.message = "hello"
@@ -59,7 +59,7 @@ def test_bag_extracted_version(self):
         metadata.address = "mailto:a.person@example.org"
         metadata.id = 'info:a-bag-2'
         bag_extracted_version(tempdir, metadata)
-        with open(os.path.join(tempdir, 'bag-info.txt'), 'r') as fh:
+        with open(os.path.join(tempdir, 'bag-info.txt'), 'r', encoding="utf-8") as fh:
             info = fh.read()
         self.assertIn('Contact-Email: a.person@example.org', info)
         self.assertIn('Contact-Name: A Person', info)
diff --git a/tests/test_inventory_validator.py b/tests/test_inventory_validator.py
index 37519a67..61a4ccfc 100644
--- a/tests/test_inventory_validator.py
+++ b/tests/test_inventory_validator.py
@@ -68,8 +68,14 @@ def test_validate(self):
         self.assertIn('W004', log.warns)
         log.clear()
         iv.validate({"id": "like:uri", "type": "wrong type", "digestAlgorithm": "my_digest"})
-        self.assertIn('E038', log.errors)
+        self.assertIn('E038a', log.errors)
         self.assertIn('E039', log.errors)
+        log.clear()
+        iv.validate({"id": "like:uri", "type": "wrong type", "digestAlgorithm": "my_digest"}, extract_spec_version=True)
+        self.assertIn('E038b', log.errors)
+        log.clear()
+        iv.validate({"id": "like:uri", "type": "https://ocfl.io/100.9/spec/#inventory", "digestAlgorithm": "my_digest"}, extract_spec_version=True)
+        self.assertIn('E038c', log.errors)
         iv = InventoryValidator(log=log, lax_digests=True)
         log.clear()
         iv.validate({"id": "like:uri", "type": "wrong type", "digestAlgorithm": "my_digest"})
@@ -279,7 +285,7 @@ def test_check_digests_present_and_used(self):
         iv.check_digests_present_and_used(manifest, ['aaa', 'bbb'])
         self.assertEqual(len(log.errors), 0)
         iv.check_digests_present_and_used(manifest, ['aaa'])
-        self.assertIn('E050b', log.errors)
+        self.assertIn('E107', log.errors)
         log.clear()
         iv.check_digests_present_and_used(manifest, ['aaa', 'bbb', 'ccc'])
         self.assertIn('E050a', log.errors)
@@ -453,7 +459,7 @@ def test_bad_inventory_files(self):
                            'inventory_E042b_unknown_version': ['E042b'],
                            'inventory_E042b_zero_padding_mismatch': ['E042b']}.items():
             filepath = 'extra_fixtures/bad-inventories/' + bad + '.json'
-            with open(filepath, 'r') as fh:
+            with open(filepath, 'r', encoding="utf-8") as fh:
                 inventory = json.load(fh)
             log = TLogger()
             iv = InventoryValidator(log=log)
diff --git a/tests/test_namaste.py b/tests/test_namaste.py
index a2e6fd50..ace36994 100644
--- a/tests/test_namaste.py
+++ b/tests/test_namaste.py
@@ -78,7 +78,7 @@ def test14_write(self):
         n.write(tempdir)
         filepath = os.path.join(tempdir, '0=balloon')
         self.assertTrue(os.path.isfile(filepath))
-        with open(filepath, 'r') as fh:
+        with open(filepath, 'r', encoding="utf-8") as fh:
             self.assertEqual(fh.read(), 'balloon\n')
         # With fs filesystem
         tmpfs = fs.tempfs.TempFS()
diff --git a/tests/test_object.py b/tests/test_object.py
index 0cfcc390..e18c89d9 100644
--- a/tests/test_object.py
+++ b/tests/test_object.py
@@ -86,7 +86,7 @@ def test05_add_version(self):
         self.maxDiff = None
         oo = Object(digest_algorithm="md5")
         inventory = {'manifest': {}, 'versions': {}}
-        with open('fixtures/1.0/content/spec-ex-full/v1_inventory.json') as fh:
+        with open('fixtures/1.0/content/spec-ex-full/v1_inventory.json', 'r', encoding="utf-8") as fh:
             v_inventory = json.load(fh)
         metadata = VersionMetadata(inventory=v_inventory, version='v1')
         src_fs = fs.open_fs('fixtures/1.0/content/spec-ex-full')
@@ -107,7 +107,7 @@ def test05_add_version(self):
                            'user': {'address': 'alice@example.com', 'name': 'Alice'}}})
         self.assertNotIn('fixity', inventory)
         # Now add second version to check forward delta
-        with open('fixtures/1.0/content/spec-ex-full/v2_inventory.json') as fh:
+        with open('fixtures/1.0/content/spec-ex-full/v2_inventory.json', 'r', encoding="utf-8") as fh:
             v_inventory = json.load(fh)
         metadata = VersionMetadata(inventory=v_inventory, version='v2')
         src_fs = fs.open_fs('fixtures/1.0/content/spec-ex-full/v2')
@@ -129,7 +129,7 @@ def test05_add_version(self):
         oo = Object(digest_algorithm="md5", fixity=['sha1'])
         inventory = {'manifest': {}, 'versions': {}, 'fixity': {'sha1': {}}}
         md1 = VersionMetadata()
-        with open('fixtures/1.0/content/spec-ex-full/v1_inventory.json') as fh:
+        with open('fixtures/1.0/content/spec-ex-full/v1_inventory.json', 'r', encoding="utf-8") as fh:
             v_inventory = json.load(fh)
         md1 = VersionMetadata(inventory=v_inventory, version='v1')
         src_fs = fs.open_fs('fixtures/1.0/content/spec-ex-full/v1')
diff --git a/tests/test_object_utils.py b/tests/test_object_utils.py
index acfe3fd5..7b48c2b1 100644
--- a/tests/test_object_utils.py
+++ b/tests/test_object_utils.py
@@ -72,5 +72,5 @@ def test_find_path_type(self):
         self.assertIn("does not exist", find_path_type("this_path_does_not_exist"))
         self.assertIn("nor can parent", find_path_type("still_nope/nope_doesnt_exist"))
         self.assertEqual(find_path_type("ocfl"), "no 0= declaration file")
-        self.assertIn("more than one 0= declaration file", find_path_type("extra_fixtures/misc/multiple_declarations"))
+        self.assertEqual(find_path_type("extra_fixtures/misc/multiple_declarations"), 'root')
         self.assertIn("unrecognized", find_path_type("extra_fixtures/misc/unknown_declaration"))
diff --git a/tests/test_store.py b/tests/test_store.py
index 328ec20f..79f4bcd0 100644
--- a/tests/test_store.py
+++ b/tests/test_store.py
@@ -79,12 +79,12 @@ def test_check_root_structure(self):
         self.assertRaises(StoreException, s.check_root_structure)
         # Wrong declaration
         decl = os.path.join(tempdir, '0=something_else')
-        with open(decl, 'w') as fh:
+        with open(decl, 'w', encoding="utf-8") as fh:
             fh.close()
         self.assertRaises(StoreException, s.check_root_structure)
         # Two declarations
         decl2 = os.path.join(tempdir, '0=ocfl_1.0')
-        with open(decl2, 'w') as fh:
+        with open(decl2, 'w', encoding="utf-8") as fh:
             fh.write("not correct")
             fh.close()
         self.assertRaises(StoreException, s.check_root_structure)
@@ -93,7 +93,7 @@ def test_check_root_structure(self):
         self.assertRaises(StoreException, s.check_root_structure)
         os.remove(decl2)
         # All good
-        with open(decl2, 'w') as fh:
+        with open(decl2, 'w', encoding="utf-8") as fh:
             fh.write("ocfl_1.0\n")
             fh.close()
         self.assertTrue(s.check_root_structure())
diff --git a/tests/test_validation_logger.py b/tests/test_validation_logger.py
index 22523df6..70100716 100644
--- a/tests/test_validation_logger.py
+++ b/tests/test_validation_logger.py
@@ -32,9 +32,9 @@ def test_error_or_warning(self):
                 }
             }}
         vl = ValidationLogger(lang='zz', validation_codes=vc)
-        vl.error_or_warning("E111")
+        vl.error_or_warning("E333")
         self.assertEqual(len(vl.messages), 1)
-        self.assertIn("Unknown error: E111 - params ({})", vl.messages[-1])
+        self.assertIn("Unknown error: E333 - params ({})", vl.messages[-1])
         vl.error_or_warning("E222", one="two", three="four")
         self.assertEqual(len(vl.messages), 2)
         self.assertIn("Unknown error: E222 - params ({'one': 'two', 'three': 'four'})", vl.messages[-1])
@@ -67,9 +67,9 @@ def test_error_or_warning(self):
     def test_error(self):
         """Test error method."""
         vl = ValidationLogger()
-        vl.error("E111")
+        vl.error("E333")
         self.assertEqual(vl.num_errors, 1)
-        self.assertIn("Unknown error: E111 - params ({})", vl.messages[-1])
+        self.assertIn("Unknown error: E333 - params ({})", vl.messages[-1])
 
     def test_warning(self):
         """Test warning method."""
diff --git a/tests/test_validator.py b/tests/test_validator.py
index 1ee7a0e6..a2cbd0f0 100644
--- a/tests/test_validator.py
+++ b/tests/test_validator.py
@@ -19,9 +19,9 @@ def extra_fixture_maybe_zip(filepath):
 class TestAll(unittest.TestCase):
     """TestAll class to run tests."""
 
-    def test01_bad(self):
-        """Check bad objects fail."""
-        for bad, codes in {'does_not_even_exist': ['E003c'],
+    def test01_bad_v1_0(self):
+        """Check bad v1.0 objects fail."""
+        for bad, codes in {'does_not_even_exist': ['E003e'],
                            'E001_extra_dir_in_root': ['E001b'],
                            'E001_extra_file_in_root': ['E001a'],
                            'E001_invalid_version_format': ['E009'],  # E009 OK, see https://github.com/OCFL/fixtures/pull/80
@@ -54,13 +54,14 @@ def test01_bad(self):
                            'E042_bad_manifest_content_path': ['E042a'],
                            'E046_missing_version_dir': ['E046a'],
                            'E046_root_not_most_recent': ['E046b'],
-                           'E049_E050_E054_bad_version_block_values': ['E049d', 'E050b', 'E050c', 'E054a', 'E094'],
+                           'E049_E050_E054_bad_version_block_values': ['E049d', 'E050c', 'E054a', 'E094', 'E107'],
                            'E049_created_no_timezone': ['E049a'],
                            'E049_created_not_to_seconds': ['E049b'],
-                           'E050_file_in_manifest_not_used': ['E050b'],
                            'E050_manifest_digest_wrong_case': ['E050f'],
+                           'E050_state_digest_different_case': ['E050f'],
                            'E050_state_repeated_digest': ['E050f'],
                            'E053_E052_invalid_logical_paths': ['E052', 'E053'],
+                           'E056_null_fixity': ['E056a'],
                            'E058_no_sidecar': ['E058a'],
                            'E060_E064_root_inventory_digest_mismatch': ['E060', 'E064'],
                            'E061_invalid_sidecar': ['E061'],
@@ -68,7 +69,9 @@ def test01_bad(self):
                            'E064_different_root_and_latest_inventories': ['E064'],
                            'E066_E092_old_manifest_digest_incorrect': ['E066d', 'E066e', 'E092a'],
                            'E066_algorithm_change_state_mismatch': ['E066b'],
+                           'E066_changed_v1_logical_path': ['E066b'],
                            'E066_inconsistent_version_state': ['E066b'],
+                           'E066_changed_content_for_logical_path': ['E066c'],
                            'E067_file_in_extensions_dir': ['E067'],
                            'E092_bad_manifest_digest': ['E092a'],
                            'E092_content_file_digest_mismatch': ['E092a'],
@@ -90,13 +93,80 @@ def test01_bad(self):
             v = Validator()
             filepath = 'fixtures/1.0/bad-objects/' + bad
             if not os.path.isdir(filepath):
-                filepath = extra_fixture_maybe_zip('extra_fixtures/bad-objects/' + bad)
-            self.assertFalse(v.validate(filepath), msg=" for object at " + filepath)
+                filepath = extra_fixture_maybe_zip('extra_fixtures/1.0/bad-objects/' + bad)
+            self.assertFalse(v.validate(filepath), msg=" for v1.0 object at " + filepath)
             for code in codes:
-                self.assertIn(code, v.log.codes, msg="for object at " + filepath)
+                self.assertIn(code, v.log.codes, msg="for v1.0 object at " + filepath)
 
-    def test02_warn(self):
-        """Check warm objects pass but give expected warnings."""
+    def test02_bad_v1_1(self):
+        """Check bad v1.1 objects fail."""
+        for bad, codes in {'does_not_even_exist': ['E003e'],
+                           'E001_extra_dir_in_root': ['E001b'],
+                           'E001_extra_file_in_root': ['E001a'],
+                           'E001_invalid_version_format': ['E009'],  # E009 OK, see https://github.com/OCFL/fixtures/pull/80
+                           'E001_v2_file_in_root': ['E001a'],
+                           'E003_E063_empty': ['E003a', 'E063'],
+                           'E003_no_decl': ['E003a'],
+                           'E003_two_declarations': ['E003b'],
+                           'E008_E036_no_versions_no_head': ['E008', 'E036d'],
+                           'E010_missing_versions': ['E046a'],  # FIXME - Check code https://github.com/OCFL/spec/issues/540
+                           'E010_skipped_versions': ['E010'],
+                           'E011_E013_invalid_padded_head_version': ['E011', 'E040', 'E064'],  # E011 only OK, see https://github.com/OCFL/spec/issues/541
+                           'E015_content_not_in_content_dir': ['E042a'],  # FIXME - What should test case and error be?
+                           'E017_invalid_content_dir': ['E017'],
+                           'E019_inconsistent_content_dir': ['E042a'],  # FIXME - What should error be?
+                           'E023_extra_file': ['E023a'],
+                           'E023_missing_file': ['E092b'],
+                           'E023_old_manifest_missing_entries': ['E023b'],
+                           'E025_wrong_digest_algorithm': ['E025a'],
+                           'E036_no_id': ['E036a'],
+                           'E036_no_head': ['E036d'],
+                           'E037_inconsistent_id': ['E037b'],
+                           'E040_head_not_most_recent': ['E040'],
+                           'E040_wrong_head_doesnt_exist': ['E040'],
+                           'E040_wrong_head_format': ['E040'],
+                           'E040_wrong_version_in_version_dir': ['E066a'],  # FIXME
+                           'E041_no_manifest': ['E041a'],
+                           'E046_root_not_most_recent': ['E046b'],
+                           'E049_E050_E054_bad_version_block_values': ['E049d', 'E050c', 'E054a', 'E094', 'E107'],
+                           'E049_created_no_timezone': ['E049a'],
+                           'E049_created_not_to_seconds': ['E049b'],
+                           'E050_manifest_digest_wrong_case': ['E050f'],
+                           'E050_state_digest_not_in_manifest': ['E050f'],
+                           'E053_E052_invalid_logical_paths': ['E052', 'E053'],
+                           'E058_no_sidecar': ['E058a'],
+                           'E060_E064_root_inventory_digest_mismatch': ['E060', 'E064'],
+                           'E061_invalid_sidecar': ['E061'],
+                           'E063_no_inv': ['E063'],
+                           'E064_different_root_and_latest_inventories': ['E064'],
+                           'E066_E092_old_manifest_digest_incorrect': ['E066d', 'E066e', 'E092a'],
+                           'E066_algorithm_change_state_mismatch': ['E066b'],
+                           'E066_inconsistent_version_state': ['E066b'],
+                           'E067_file_in_extensions_dir': ['E067'],
+                           'E092_content_file_digest_mismatch': ['E092a'],
+                           'E092_algorithm_change_incorrect_digest': ['E092a'],
+                           'E092_E093_content_path_does_not_exist': ['E092b', 'E093b'],
+                           'E093_fixity_digest_mismatch': ['E093a'],
+                           'E095_conflicting_logical_paths': ['E095b'],
+                           'E095_non_unique_logical_paths': ['E095a'],
+                           'E096_manifest_duplicate_digests': ['E096'],
+                           'E097_fixity_duplicate_digests': ['E097'],
+                           'E100_E099_fixity_invalid_content_paths': ['E057d'],  # E057 OK, different test approach
+                           'E100_E099_manifest_invalid_content_paths': ['E099', 'E100'],
+                           'E101_non_unique_content_paths': ['E101a'],
+                           'E107_file_in_manifest_not_used': ['E107'],
+                           'E111_null_fixity': ['E111']
+                           }.items():
+            v = Validator()
+            filepath = 'fixtures/1.1/bad-objects/' + bad
+            if not os.path.isdir(filepath):
+                filepath = extra_fixture_maybe_zip('extra_fixtures/1.1/bad-objects/' + bad)
+            self.assertFalse(v.validate(filepath), msg=" for v1.1 object at " + filepath)
+            for code in codes:
+                self.assertIn(code, v.log.codes, msg="for v1.1 object at " + filepath)
+
+    def test03_warn_1_0(self):
+        """Check warn v1.0 objects pass but give expected warnings."""
         for warn, codes in {'W001_zero_padded_versions': ['W001'],
                             'W001_W004_W005_zero_padded_versions': ['W001', 'W004', 'W005'],
                             'W002_extra_dir_in_version_dir': ['W002'],
@@ -113,14 +183,37 @@ def test02_warn(self):
             v = Validator()
             filepath = 'fixtures/1.0/warn-objects/' + warn
             if not os.path.isdir(filepath):
-                filepath = extra_fixture_maybe_zip('extra_fixtures/warn-objects/' + warn)
-            self.assertTrue(v.validate(filepath), msg="for object at " + filepath)
-            self.assertEqual(set(codes), set(v.log.codes), msg="for object at " + filepath)
+                filepath = extra_fixture_maybe_zip('extra_fixtures/1.0/warn-objects/' + warn)
+            self.assertTrue(v.validate(filepath), msg="for v1.0 object at " + filepath)
+            self.assertEqual(set(codes), set(v.log.codes), msg="for v1.0 object at " + filepath)
+
+    def test04_warn_1_1(self):
+        """Check warn v1.1 objects pass but give expected warnings."""
+        for warn, codes in {'W001_zero_padded_versions': ['W001'],
+                            'W001_W004_W005_zero_padded_versions': ['W001', 'W004', 'W005'],
+                            'W002_extra_dir_in_version_dir': ['W002'],
+                            'W004_uses_sha256': ['W004'],
+                            'W004_versions_diff_digests': ['W004'],
+                            'W005_id_not_uri': ['W005'],
+                            'W007_no_message_or_user': ['W007a', 'W007b'],
+                            'W008_user_no_address': ['W008'],
+                            'W009_user_address_not_uri': ['W009'],
+                            'W010_no_version_inventory': ['W010'],
+                            'W011_version_inv_diff_metadata': ['W011'],
+                            'W013_unregistered_extension': ['W013']}.items():
+            v = Validator()
+            filepath = 'fixtures/1.1/warn-objects/' + warn
+            if not os.path.isdir(filepath):
+                filepath = extra_fixture_maybe_zip('extra_fixtures/1.1/warn-objects/' + warn)
+            self.assertTrue(v.validate(filepath), msg="for v1.1 object at " + filepath)
+            self.assertEqual(set(codes), set(v.log.codes), msg="for v1.1 object at " + filepath)
 
-    def test03_good(self):
-        """Check good objects pass."""
+    def test05_good(self):
+        """Check good objects (v1.0, v1.1 and extra) pass."""
         for base_dir in ['fixtures/1.0/good-objects',
-                         'extra_fixtures/good-objects']:
+                         'fixtures/1.1/good-objects',
+                         'extra_fixtures/1.0/good-objects',
+                         'extra_fixtures/1.1/good-objects']:
             for name in os.listdir(base_dir):
                 filepath = extra_fixture_maybe_zip(os.path.join(base_dir, name))
                 v = Validator()
diff --git a/tests/test_version_metadata.py b/tests/test_version_metadata.py
index 058e8f6a..a9a730bf 100644
--- a/tests/test_version_metadata.py
+++ b/tests/test_version_metadata.py
@@ -31,7 +31,7 @@ def test02_init(self):
         self.assertEqual(d['user'], {'name': 'c', 'address': 'd'})
         self.assertEqual(d['extra'], 'x')
         # with load from file
-        with open('tests/testdata/inventories/inv_1_good.json') as fh:
+        with open('tests/testdata/inventories/inv_1_good.json', 'r', encoding="utf-8") as fh:
             inventory = json.load(fh)
         m = VersionMetadata(inventory=inventory, version='v1')
         self.assertEqual(m.version, 'v1')