diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index d5ca344..6d36abb 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -32,7 +32,7 @@ jobs:
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
- python -m pip install build "twine>=7,<8" ".[dev]"
+ python -m pip install build "twine>=7,<8" -e ".[dev]"
- name: Test release candidate
run: python -m pytest -q
diff --git a/docs/validation/publisher-admission-031-20261002/COMMANDS.rst b/docs/validation/publisher-admission-031-20261002/COMMANDS.rst
new file mode 100644
index 0000000..fcf8a46
--- /dev/null
+++ b/docs/validation/publisher-admission-031-20261002/COMMANDS.rst
@@ -0,0 +1,38 @@
+Focused qualification command
+=============================
+
+Run from the isolated PolyStore worktree::
+
+ systemd-run --user --scope --quiet \
+ -p MemoryMax=512M -p MemorySwapMax=0 -p CPUQuota=100% \
+ timeout 60s taskset -c 0 env \
+ PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 PYTHONDONTWRITEBYTECODE=1 \
+ OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 \
+ PYTHONPATH=/home/ts/wt/polystore-publisher-admission-031-20261002/src \
+ /home/ts/wt/openhcs-generated-inputs-installed-parent-20261001/.venv/bin/python -B \
+ /home/ts/wt/openhcs-prior-measurement-role-projection-20261001/docs/validation/selected_plane_stream_materialization_20261001/run_bounded_source.py \
+ /home/ts/wt/openhcs-generated-inputs-installed-parent-20261001/.venv/bin/python -B -m pytest \
+ -o addopts= \
+ --basetemp=docs/validation/publisher-admission-031-20261002/qualified-test-temp \
+ --junitxml=docs/validation/publisher-admission-031-20261002/qualified.xml \
+ tests/test_publisher_admission.py tests/test_basic.py \
+ tests/test_imagej_cache_environment.py tests/test_metadata_namespace.py \
+ tests/test_roi_region_properties.py tests/test_roi.py \
+ tests/test_lazy_package_exports.py -q
+
+The default coverage/report addopts are disabled for this focused resource-bound
+source run, not for the publisher. All selected tests/assertions are unchanged.
+Source fixtures prohibit actual Fiji downloads/JVM execution.
+
+Offline lock attempt (terminal failure)::
+
+ systemd-run --user --scope --quiet \
+ -p MemoryMax=512M -p MemorySwapMax=0 -p CPUQuota=100% \
+ timeout 60s taskset -c 0 uv lock --offline --no-python-downloads
+
+No solution for python_full_version >=3.14 / win32: imglyb not found in cache;
+polystore[all] requires imglyb==2.1.0. No lock file was changed.
+
+Owned disposable output is confined to the three source-test-temp,
+admission-test-temp and qualified-test-temp directories below this receipt root.
+Original log, test XML and qualification notes are durable evidence, not caches.
diff --git a/docs/validation/publisher-admission-031-20261002/RECEIPT.rst b/docs/validation/publisher-admission-031-20261002/RECEIPT.rst
new file mode 100644
index 0000000..b581a23
--- /dev/null
+++ b/docs/validation/publisher-admission-031-20261002/RECEIPT.rst
@@ -0,0 +1,73 @@
+PolyStore 0.3.1 publisher admission
+================================
+
+Source base: 84f322e46871de5ed47e7fd20976ad03e533c1c4 (remote main).
+Owner: Dewey, isolated polystore-publisher-admission-031-20261002 worktree.
+Parent owns integration, the sole new 0.3.1 tag, and hosted release acceptance.
+The immutable failed v0.3.0 tag is untouched.
+
+Original failure
+----------------
+
+Run https://github.com/OpenHCSDev/PolyStore/actions/runs/36959840684,
+job 110690882405: 9 failed, 545 passed, 19 skipped in 15.91s.
+Three failures require child processes to import checkout source; the publisher
+installed a regular distribution. Six ROI topology/archive cases fail because
+the existing dev extra omitted roifile. No original test or skip is changed.
+
+The full job log was fetched through the GitHub SDK job-log tool and archived
+as original-job-110690882405.log.gz (lossless gzip), preserving UTF-8 text including
+its original BOM and final newline: 142020 bytes, SHA256
+542d75ef00bcd8677526131a1afe195cea6fa63edca7eca5aa8e0b1b4e4b703c.
+
+Ownership and repair
+--------------------
+
+Replace the existing publisher install with editable .[dev] admission, matching
+the existing ordinary test workflow. The later package build and immutable
+declaration/publish steps are unchanged. Declare roifile in the original dev
+extra, not a workflow-only dependency roster or mandatory runtime requirement.
+Advance the sole project version declaration to 0.3.1; the existing package
+version and release scripts derive it from distribution/project metadata.
+
+Current NRA/refactor-audit skills and packaged pattern catalog were read.
+MEMB-1/MEMB-2: dependency membership remains in the original extras declaration.
+TIME-8: no duplicated version or metadata authority. No Python runtime mechanism
+is introduced, so inheritance/MI would be ornamental here. Independent future
+dev dependencies need only their own extra entry, not publisher consumer edits.
+The real editable-build test reads emitted metadata and the actual source .pth
+from the existing setuptools backend rather than mirroring its implementation.
+
+Open-PR check found only Dependabot PR10, whose publisher-file changes are action
+pins; this patch leaves those pins unchanged. Parent's release worktree is not
+edited. No environment installation, external download, Fiji/native launch,
+science job, tag or release operation was performed.
+
+Focused source qualification
+----------------------------
+
+68 PASS, zero skips: publisher admission/editable metadata, basic package,
+ImageJ cache policy and fresh-process source, namespace handoff, ROI geometry
+and archive roundtrip, and lazy package exports. These include all nine original
+failed cases. The initial original-only run was 63 PASS; new admission controls
+were 2 PASS. Final combined run: 8.43s pytest, 9.089s monitored wall,
+322700 KiB aggregate RSS (315.14 MiB), one CPU, 512 MiB / 60s limits.
+See qualified.xml and qualified-source.txt. The command is in COMMANDS.rst.
+
+Source is explicitly this worktree's src; dependencies/interpreter are readonly
+openhcs-generated-inputs-installed-parent-20261001/.venv/bin/python.
+The two publisher controls exercise the real offline editable build without
+installing its artifact. They verify checkout binding, unfiltered publisher
+pytest invocation, derived version, and dev-only ROI dependency metadata.
+No installed editable-candidate or hosted 0.3.1 acceptance is claimed.
+
+Lock limitation
+---------------
+
+uv lock --offline --no-python-downloads failed because imglyb==2.1.0 is absent
+from the cache for the all/bioformats extras. Original uv.lock already records
+PolyStore 0.2.18 and ZMQRuntime 0.2.21, inconsistent with base declarations.
+The lock is unchanged: no fabricated entries, constrained platforms, dependency
+weakening or downloads. Publisher pip does not consume uv.lock. The existing
+separate distribution CI lock-check remains unqualified until authorized lock
+regeneration; this is not represented as publisher or runtime acceptance.
diff --git a/docs/validation/publisher-admission-031-20261002/admission.xml b/docs/validation/publisher-admission-031-20261002/admission.xml
new file mode 100644
index 0000000..b39cf3a
--- /dev/null
+++ b/docs/validation/publisher-admission-031-20261002/admission.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/validation/publisher-admission-031-20261002/focused-originals.xml b/docs/validation/publisher-admission-031-20261002/focused-originals.xml
new file mode 100644
index 0000000..3285ae2
--- /dev/null
+++ b/docs/validation/publisher-admission-031-20261002/focused-originals.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/validation/publisher-admission-031-20261002/original-job-110690882405.log.gz b/docs/validation/publisher-admission-031-20261002/original-job-110690882405.log.gz
new file mode 100644
index 0000000..f379115
Binary files /dev/null and b/docs/validation/publisher-admission-031-20261002/original-job-110690882405.log.gz differ
diff --git a/docs/validation/publisher-admission-031-20261002/qualified-source.txt b/docs/validation/publisher-admission-031-20261002/qualified-source.txt
new file mode 100644
index 0000000..3abecb3
--- /dev/null
+++ b/docs/validation/publisher-admission-031-20261002/qualified-source.txt
@@ -0,0 +1,4 @@
+.................................................................... [100%]
+- generated xml file: /home/ts/wt/polystore-publisher-admission-031-20261002/docs/validation/publisher-admission-031-20261002/qualified.xml -
+68 passed in 8.43s
+BOUNDED_SOURCE peak_aggregate_rss_kib=322700 elapsed_seconds=9.089 returncode=0 reason=None
diff --git a/docs/validation/publisher-admission-031-20261002/qualified.xml b/docs/validation/publisher-admission-031-20261002/qualified.xml
new file mode 100644
index 0000000..b17c68b
--- /dev/null
+++ b/docs/validation/publisher-admission-031-20261002/qualified.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 40692cb..2bc2ee6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "polystore"
-version = "0.3.0"
+version = "0.3.1"
description = "Framework-agnostic multi-backend storage abstraction for ML and scientific computing"
readme = "README.md"
requires-python = ">=3.11"
@@ -101,6 +101,7 @@ dev = [
"genbadge[coverage]",
"pandas>=2.3.1",
"jpype1>=1.4.0",
+ "roifile",
"black>=23.0.0",
"ruff>=0.1.0",
]
diff --git a/tests/test_publisher_admission.py b/tests/test_publisher_admission.py
new file mode 100644
index 0000000..fcec273
--- /dev/null
+++ b/tests/test_publisher_admission.py
@@ -0,0 +1,39 @@
+"""Release tests use the checkout; the built distribution owns its metadata."""
+
+import shlex
+import tomllib
+import zipfile
+from email.parser import Parser
+from pathlib import Path
+
+from packaging.requirements import Requirement
+from setuptools import build_meta
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def test_publisher_installs_the_dev_candidate_editably():
+ workflow = (ROOT / ".github/workflows/publish.yml").read_text()
+ commands = [shlex.split(line.strip()) for line in workflow.splitlines()]
+ (install,) = [command for command in commands if ".[dev]" in command]
+ assert install[:4] == ["python", "-m", "pip", "install"]
+ assert install[install.index("-e") + 1] == ".[dev]"
+ assert "run: python -m pytest -q" in workflow
+
+
+def test_editable_artifact_owns_checkout_path_and_roi_dev_requirement(tmp_path, monkeypatch):
+ monkeypatch.chdir(ROOT)
+ wheel = build_meta.build_editable(str(tmp_path))
+ with zipfile.ZipFile(tmp_path / wheel) as archive:
+ (pth,) = [name for name in archive.namelist() if name.endswith(".pth")]
+ assert Path(archive.read(pth).decode().strip()).resolve() == ROOT / "src"
+ (metadata_name,) = [name for name in archive.namelist() if name.endswith("/METADATA")]
+ metadata = Parser().parsestr(archive.read(metadata_name).decode())
+ project = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]
+ assert metadata["Name"] == project["name"]
+ assert metadata["Version"] == project["version"]
+ requirements = [Requirement(value) for value in metadata.get_all("Requires-Dist")]
+ (roi,) = [requirement for requirement in requirements if requirement.name == "roifile"]
+ assert roi.marker.evaluate({"extra": "dev"})
+ assert not roi.marker.evaluate({"extra": ""})