mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-07 02:07:45 +02:00
[eric] swarm: bundle keys are slashes on every OS; a Windows import walked staged files with backslashes, matched no workspace/ entry, and saved a hollow app that then said its files were missing
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9zwUaHucUgrdxvK8FvjYT
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
425df32fc4
commit
6abc71e2ac
@@ -315,6 +315,13 @@ def p_read_payload(sandbox: str, ref: EntityRef) -> dict:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def bundle_key(full: str, base: str) -> str:
|
||||
"""A staged file's key inside the bundle: slash-separated on every OS. On Windows relpath gave
|
||||
`workspace\\app\\...`, the app importer looked for `workspace/`, skipped every file, and saved a
|
||||
hollow app that then said its files were missing (the Tisusa import, 2026-09-04)."""
|
||||
return os.path.relpath(full, base).replace(os.sep, "/")
|
||||
|
||||
|
||||
def p_read_files(sandbox: str, ref: EntityRef) -> dict[str, bytes]:
|
||||
base = p_safe_join(sandbox, os.path.join(ref.path, "files"))
|
||||
out: dict[str, bytes] = {}
|
||||
@@ -324,7 +331,7 @@ def p_read_files(sandbox: str, ref: EntityRef) -> dict[str, bytes]:
|
||||
for fn in fnames:
|
||||
full = os.path.join(root, fn)
|
||||
with open(full, "rb") as f:
|
||||
out[os.path.relpath(full, base)] = f.read()
|
||||
out[bundle_key(full, base)] = f.read()
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -93,6 +93,10 @@ class AppExportable:
|
||||
with open(dest, "wb") as f:
|
||||
f.write(data)
|
||||
wrote_workspace = True
|
||||
if files and not wrote_workspace:
|
||||
# The bundle shipped files but none under workspace/: a hollow app would import "successfully" and
|
||||
# then say its files are missing. Fail here, where the rollback still knows what to undo.
|
||||
raise ValueError("the app's files are not where the bundle format puts them; nothing was imported")
|
||||
if wrote_workspace:
|
||||
p_localize_env(folder)
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ def read_supporting_files(skill_dir: str) -> dict[str, bytes]:
|
||||
p_dirs[:] = [d for d in p_dirs if d not in WALK_SKIP_DIRS]
|
||||
for n in names:
|
||||
full = os.path.join(root, n)
|
||||
rel = os.path.relpath(full, skill_dir)
|
||||
# Slash keys on every OS, like the app exporter; a Windows export used to ship `scripts\\x.py`.
|
||||
rel = os.path.relpath(full, skill_dir).replace(os.sep, "/")
|
||||
if rel == "SKILL.md" or n.startswith("."):
|
||||
continue
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""A Windows user imported the Tisusa .swarm (507 workspace files) and got an app card that said
|
||||
"This app's files are missing" (2026-09-04). The staging reader keyed files with the OS separator,
|
||||
the app importer looked for `workspace/`, skipped every file, and saved a hollow app. Bundle keys
|
||||
are slash-separated everywhere now, and a bundle that ships files but none under workspace/ fails
|
||||
loudly instead of importing a shell."""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.apps.swarm import closure
|
||||
from backend.apps.swarm.entities.apps import AppExportable
|
||||
|
||||
|
||||
def test_bundle_keys_are_slashes_even_when_the_os_answers_with_backslashes(monkeypatch):
|
||||
real_relpath = os.path.relpath
|
||||
# The Windows shape: relpath answers with backslashes and the separator is a backslash.
|
||||
monkeypatch.setattr(os.path, "relpath", lambda full, start: real_relpath(full, start).replace("/", "\\"))
|
||||
monkeypatch.setattr(os, "sep", "\\")
|
||||
assert closure.bundle_key("/sb/entities/e1/files/workspace/app/index.html", "/sb/entities/e1/files") == "workspace/app/index.html"
|
||||
|
||||
|
||||
def test_the_staging_reader_uses_the_slash_keys(tmp_path):
|
||||
base = tmp_path / "entities" / "e1" / "files" / "workspace" / "app"
|
||||
base.mkdir(parents=True)
|
||||
(base / "index.html").write_bytes(b"<h1>x</h1>")
|
||||
ref = type("Ref", (), {"path": "entities/e1"})()
|
||||
assert list(closure.p_read_files(str(tmp_path), ref)) == ["workspace/app/index.html"]
|
||||
src = open(closure.__file__).read()
|
||||
assert "out[bundle_key(full, base)]" in src
|
||||
|
||||
|
||||
def test_a_bundle_whose_files_miss_the_workspace_prefix_fails_loudly(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("backend.apps.swarm.entities.apps.OUTPUTS_WORKSPACE_DIR", str(tmp_path / "ws"))
|
||||
monkeypatch.setattr("backend.apps.swarm.entities.apps.OUTPUTS_DIR", str(tmp_path / "out"))
|
||||
with pytest.raises(ValueError, match="nothing was imported"):
|
||||
AppExportable.import_({"name": "Hollow"}, {"workspace\\\\app\\\\index.html": b"<h1>x</h1>"}, {})
|
||||
assert not (tmp_path / "out").exists() or not list((tmp_path / "out").glob("*.json"))
|
||||
Reference in New Issue
Block a user