Skip to content

Commit f1f3e6f

Browse files
committed
Close final portability and release gaps
1 parent e301b75 commit f1f3e6f

8 files changed

Lines changed: 100 additions & 47 deletions

File tree

projects/openshell-agent-runner/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ help: ## Show available targets and configurable variables.
2222
@printf " VERSION=X.Y.Z Required package version for publish\n"
2323
@printf " DRY_RUN=1 Validate a release without tagging or uploading\n"
2424
@printf " ALLOW_NON_MAIN=1 Permit release validation or publishing off main\n"
25-
@printf " RETRY_ARTIFACT=... Retry only a missing wheel or sdist after partial upload\n"
25+
@printf " RETRY_ARTIFACT=... Retry a missing wheel, sdist, or both after a failed upload\n"
2626

2727
publish: ## Validate or publish a release; requires VERSION.
2828
ifndef VERSION

projects/openshell-agent-runner/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ uvx --from openshell-agent-runner oar doctor --gateway openshell
7070
uvx --from openshell-agent-runner oar run \
7171
projects/openshell-agent-runner/profiles/reviewer \
7272
--task review \
73+
--gateway openshell \
7374
--input README.md \
7475
--output /tmp/oar-review.md \
7576
--dry-run
@@ -330,9 +331,9 @@ OpenShell's managed inference path.
330331
| Code | Meaning |
331332
| --- | --- |
332333
| `0` | Execution completed and the output validated. |
333-
| `1` | OpenShell execution, timeout, download size limit, ownership inspection, or cleanup failed. |
334+
| `1` | OpenShell execution, timeout, missing remote output, download size limit, ownership inspection, or cleanup failed. |
334335
| `2` | CLI input or profile configuration was invalid. |
335-
| `3` | The output was missing, empty, invalid, or failed its contract. |
336+
| `3` | A downloaded output was empty, invalid, or failed its contract. |
336337

337338
## Development
338339

projects/openshell-agent-runner/RELEASING.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ The script:
4949
repository.
5050

5151
If the upload fails after the tag is pushed, check the repository or Twine log
52-
to identify which artifact is missing. Retry only that artifact:
52+
to identify which artifacts are missing. Retry a missing artifact with:
5353

5454
```bash
5555
make publish VERSION=0.1.0 RETRY_ARTIFACT=sdist
5656
```
5757

58-
Use `wheel` instead of `sdist` when the wheel is missing. The retry rebuilds and
59-
checks both artifacts from the tagged commit but uploads only the selected file,
60-
so it works with private indexes that reject duplicate filenames. A retry
58+
Use `wheel` instead of `sdist` when the wheel is missing. If neither artifact
59+
was accepted, use `RETRY_ARTIFACT=both`. Every retry rebuilds and checks both
60+
artifacts from the tagged commit, then uploads only the selected file or files.
61+
This works with private indexes that reject duplicate filenames. A retry
6162
requires the remote tag to match the current commit. If both files are already
6263
present, there is nothing to retry.

projects/openshell-agent-runner/docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,6 @@ without creating a sandbox.
191191
| Exit code | Meaning |
192192
| --- | --- |
193193
| `0` | The result was validated and published. |
194-
| `1` | OpenShell execution, timeout, download size limit, ownership inspection, or cleanup failed. |
194+
| `1` | OpenShell execution, timeout, missing remote output, download size limit, ownership inspection, or cleanup failed. |
195195
| `2` | CLI input or profile configuration was invalid. |
196-
| `3` | The result was missing, empty, invalid, or failed its schema. |
196+
| `3` | A downloaded result was empty, invalid, or failed its schema. |

projects/openshell-agent-runner/scripts/publish.sh

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ PROJECT_DIRECTORY=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
88
PYPIRC_REPOSITORY="openshell-research"
99

1010
usage() {
11-
echo "Usage: $0 VERSION [--dry-run] [--allow-non-main] [--retry-artifact wheel|sdist]"
11+
echo "Usage: $0 VERSION [--dry-run] [--allow-non-main] [--retry-artifact wheel|sdist|both]"
1212
echo
1313
echo "Build and publish openshell-agent-runner using the '$PYPIRC_REPOSITORY'"
1414
echo "repository configured in ~/.pypirc."
@@ -58,8 +58,8 @@ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$ ]]; then
5858
echo "publish: invalid version '$VERSION'; expected X.Y.Z or X.Y.ZrcN" >&2
5959
exit 2
6060
fi
61-
if [[ -n "$RETRY_ARTIFACT" && "$RETRY_ARTIFACT" != "wheel" && "$RETRY_ARTIFACT" != "sdist" ]]; then
62-
echo "publish: --retry-artifact must be 'wheel' or 'sdist'" >&2
61+
if [[ -n "$RETRY_ARTIFACT" && "$RETRY_ARTIFACT" != "wheel" && "$RETRY_ARTIFACT" != "sdist" && "$RETRY_ARTIFACT" != "both" ]]; then
62+
echo "publish: --retry-artifact must be 'wheel', 'sdist', or 'both'" >&2
6363
exit 2
6464
fi
6565
if [[ "$DRY_RUN" == true && -n "$RETRY_ARTIFACT" ]]; then
@@ -122,7 +122,7 @@ if [[ -n "$RETRY_ARTIFACT" && "$TAG_PUBLIC" != true ]]; then
122122
exit 1
123123
fi
124124
if [[ -z "$RETRY_ARTIFACT" && "$DRY_RUN" != true && "$TAG_PUBLIC" == true ]]; then
125-
echo "publish: remote tag '$TAG' already exists; retry only the missing artifact with --retry-artifact" >&2
125+
echo "publish: remote tag '$TAG' already exists; retry the missing artifact or artifacts with --retry-artifact" >&2
126126
exit 1
127127
fi
128128

@@ -169,8 +169,12 @@ echo "Uploading openshell-agent-runner $VERSION with .pypirc repository '$PYPIRC
169169
if ! uv run --with twine python -m twine upload \
170170
--repository "$PYPIRC_REPOSITORY" \
171171
"${UPLOAD_ARTIFACTS[@]}"; then
172-
echo "publish: upload failed; identify the missing artifact and retry only that artifact" >&2
172+
echo "publish: upload failed; identify the missing artifact or artifacts before retrying" >&2
173173
exit 1
174174
fi
175175

176-
echo "Published openshell-agent-runner $VERSION from $TAG."
176+
if [[ "$RETRY_ARTIFACT" == "wheel" || "$RETRY_ARTIFACT" == "sdist" ]]; then
177+
echo "Uploaded the missing $RETRY_ARTIFACT artifact for openshell-agent-runner $VERSION."
178+
else
179+
echo "Published openshell-agent-runner $VERSION from $TAG."
180+
fi

projects/openshell-agent-runner/src/openshell_agent_runner/config.py

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -339,29 +339,44 @@ def _validate_output_schema(path: Path) -> None:
339339

340340

341341
def _validate_schema_references(document: Any, path: Path) -> None:
342-
if isinstance(document, dict):
343-
for key, value in document.items():
344-
if key in {"pattern", "patternProperties"}:
345-
raise ConfigurationError(
346-
"output schemas do not support regular-expression keywords "
347-
f"({key}) because host and sandbox engines use different dialects"
348-
)
349-
if key in {"$ref", "$dynamicRef", "$recursiveRef"} and (
350-
not isinstance(value, str) or not value.startswith("#")
351-
):
352-
raise ConfigurationError(
353-
f"output schema references must stay inside {path}: {value!r}"
354-
)
355-
if key in {
356-
"$defs",
357-
"definitions",
358-
"properties",
359-
"dependentSchemas",
360-
} and isinstance(value, dict):
361-
for schema in value.values():
362-
_validate_schema_references(schema, path)
363-
else:
364-
_validate_schema_references(value, path)
365-
elif isinstance(document, list):
366-
for value in document:
367-
_validate_schema_references(value, path)
342+
if not isinstance(document, dict):
343+
return
344+
345+
for key in {"pattern", "patternProperties"}:
346+
if key in document:
347+
raise ConfigurationError(
348+
"output schemas do not support regular-expression keywords "
349+
f"({key}) because host and sandbox engines use different dialects"
350+
)
351+
for key in {"$ref", "$dynamicRef", "$recursiveRef"}:
352+
if key in document and (
353+
not isinstance(document[key], str) or not document[key].startswith("#")
354+
):
355+
raise ConfigurationError(
356+
f"output schema references must stay inside {path}: {document[key]!r}"
357+
)
358+
359+
for key in {"$defs", "definitions", "properties", "dependentSchemas"}:
360+
value = document.get(key)
361+
if isinstance(value, dict):
362+
for schema in value.values():
363+
_validate_schema_references(schema, path)
364+
for key in {"allOf", "anyOf", "oneOf", "prefixItems"}:
365+
value = document.get(key)
366+
if isinstance(value, list):
367+
for schema in value:
368+
_validate_schema_references(schema, path)
369+
for key in {
370+
"additionalProperties",
371+
"contains",
372+
"contentSchema",
373+
"else",
374+
"if",
375+
"items",
376+
"not",
377+
"propertyNames",
378+
"then",
379+
"unevaluatedItems",
380+
"unevaluatedProperties",
381+
}:
382+
_validate_schema_references(document.get(key), path)

projects/openshell-agent-runner/tests/test_config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,19 @@ def test_output_schema_allows_property_names_that_match_schema_keywords(
219219
load_profile(tmp_path)
220220

221221

222+
@pytest.mark.parametrize("keyword", ["const", "examples", "x-oar-note"])
223+
def test_output_schema_does_not_treat_instance_data_as_a_schema(
224+
tmp_path: Path, keyword: str
225+
) -> None:
226+
_write_profile(tmp_path, task="output_schema: output.schema.json")
227+
value: object = (
228+
[{"pattern": "value"}] if keyword == "examples" else {"pattern": "value"}
229+
)
230+
(tmp_path / "output.schema.json").write_text(json.dumps({keyword: value}))
231+
232+
load_profile(tmp_path)
233+
234+
222235
@pytest.mark.parametrize("keyword", ["$ref", "$dynamicRef", "$recursiveRef"])
223236
def test_output_schema_rejects_external_references(
224237
tmp_path: Path, keyword: str

projects/openshell-agent-runner/tests/test_release.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@
77
import textwrap
88
from pathlib import Path
99

10+
import pytest
11+
1012
REPOSITORY = Path(__file__).resolve().parents[3]
1113
PUBLISH_SCRIPT = REPOSITORY / "projects/openshell-agent-runner/scripts/publish.sh"
1214

1315

14-
def test_publish_retry_uploads_only_the_missing_artifact(
15-
tmp_path: Path,
16+
@pytest.mark.parametrize(
17+
("first_accepts_wheel", "retry_artifact"),
18+
[(True, "sdist"), (False, "both")],
19+
)
20+
def test_publish_retry_uploads_only_missing_artifacts(
21+
tmp_path: Path, first_accepts_wheel: bool, retry_artifact: str
1622
) -> None:
1723
project = tmp_path / "project"
1824
script = project / "scripts/publish.sh"
@@ -73,8 +79,16 @@ def test_publish_retry_uploads_only_the_missing_artifact(
7379
if [[ "$*" == *"twine upload"* ]]; then
7480
if [[ "$*" == *"--skip-existing"* ]]; then exit 92; fi
7581
if [[ "$*" == *".whl"* && "$*" == *".tar.gz"* ]]; then
82+
if [[ ! -e "$FAKE_REPOSITORY_STATE/attempted" ]]; then
83+
touch "$FAKE_REPOSITORY_STATE/attempted"
84+
if [[ "$FAKE_FIRST_ACCEPTS_WHEEL" == "true" ]]; then
85+
touch "$FAKE_REPOSITORY_STATE/wheel"
86+
fi
87+
exit 93
88+
fi
7689
touch "$FAKE_REPOSITORY_STATE/wheel"
77-
exit 93
90+
touch "$FAKE_REPOSITORY_STATE/sdist"
91+
exit 0
7892
fi
7993
if [[ "$*" == *".tar.gz"* ]]; then
8094
touch "$FAKE_REPOSITORY_STATE/sdist"
@@ -95,6 +109,7 @@ def test_publish_retry_uploads_only_the_missing_artifact(
95109
environment["FAKE_UV_LOG"] = str(uv_log)
96110
environment["FAKE_GIT_STATE"] = str(git_state)
97111
environment["FAKE_REPOSITORY_STATE"] = str(repository_state)
112+
environment["FAKE_FIRST_ACCEPTS_WHEEL"] = str(first_accepts_wheel).lower()
98113

99114
first_attempt = subprocess.run(
100115
["bash", str(script), "0.1.0"],
@@ -104,11 +119,11 @@ def test_publish_retry_uploads_only_the_missing_artifact(
104119
check=False,
105120
)
106121
assert first_attempt.returncode == 1
107-
assert (repository_state / "wheel").exists()
122+
assert (repository_state / "wheel").exists() is first_accepts_wheel
108123
assert not (repository_state / "sdist").exists()
109124

110125
retry = subprocess.run(
111-
["bash", str(script), "0.1.0", "--retry-artifact", "sdist"],
126+
["bash", str(script), "0.1.0", "--retry-artifact", retry_artifact],
112127
text=True,
113128
capture_output=True,
114129
env=environment,
@@ -121,7 +136,11 @@ def test_publish_retry_uploads_only_the_missing_artifact(
121136
line for line in uv_log.read_text().splitlines() if "twine upload" in line
122137
]
123138
assert ".whl" in uploads[0] and ".tar.gz" in uploads[0]
124-
assert ".whl" not in uploads[1] and ".tar.gz" in uploads[1]
139+
assert (".whl" in uploads[1]) is (retry_artifact == "both")
140+
assert ".tar.gz" in uploads[1]
141+
if retry_artifact == "sdist":
142+
assert "Uploaded the missing sdist artifact" in retry.stdout
143+
assert "Published openshell-agent-runner" not in retry.stdout
125144

126145

127146
def _write_executable(path: Path, content: str) -> None:

0 commit comments

Comments
 (0)