muse-code-sdk 1.4.9__tar.gz → 1.4.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {muse_code_sdk-1.4.9/src/muse_code_sdk.egg-info → muse_code_sdk-1.4.10}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/pyproject.toml +1 -1
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/__init__.py +1 -1
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/fold/session_fold.py +21 -8
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10/src/muse_code_sdk.egg-info}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_external_audience.py +188 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_public_mirror_text.py +37 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_session_fold.py +85 -10
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/LICENSE +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/README.md +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/setup.cfg +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/connection/__init__.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/connection/connection.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/connection/discovery.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/connection/spawn.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/connection/validation.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/errors.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/__init__.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/approval.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/client.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/discarded.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/gap_fill.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/host_death.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/host_start.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/session.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/turn_handle.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/facade/turn_submit.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/fold/__init__.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/fold/item_store.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/fold/state_store.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/pending/__init__.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/pending/pending_command_set.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/py.typed +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/replay.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code/sync_facade.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/SOURCES.txt +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/dependency_links.txt +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/requires.txt +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/top_level.txt +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_asyncio_cancellation.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_compatibility_row.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_connection.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_connection_error_arms.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_docs_generation.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_facade_approval.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_facade_gap_fill.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_facade_open_same_chunk.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_facade_persistent_approval.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_facade_required_selection.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_facade_turn.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_host_discovery.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_item_store.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_manifests.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_muse_serve_child.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_pending_command_corpus.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_pending_command_set.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_pending_command_wire_binding.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_permission_state_wire_types.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_publish_shape.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_source_closure.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_spawn_handshake.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_spawn_host_lost.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_state_store.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_sync_facade.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_tolerance_corpus.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_transcript_replay.py +0 -0
- {muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/tests/test_typing_gates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: muse-code-sdk
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.10
|
|
4
4
|
Summary: Muse Code SDK for MSP: asyncio-first facade over the generated wire types (muse-code-msp), with a thin sync wrapper.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/meta-models/muse-code-sdk
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "muse-code-sdk"
|
|
8
|
-
version = "1.4.
|
|
8
|
+
version = "1.4.10"
|
|
9
9
|
description = "Muse Code SDK for MSP: asyncio-first facade over the generated wire types (muse-code-msp), with a thin sync wrapper."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -92,7 +92,7 @@ Python SDK reds this lane instead of drifting silently.
|
|
|
92
92
|
# The lockstep rule: the release train bumps pyproject.toml, the
|
|
93
93
|
# distribution-metadata authority; this constant is aligned at publication
|
|
94
94
|
# time.
|
|
95
|
-
__version__: Final[str] = "1.4.
|
|
95
|
+
__version__: Final[str] = "1.4.10"
|
|
96
96
|
|
|
97
97
|
# The facade is imported last so the constants above are already bound: the
|
|
98
98
|
# spawn path reads ``EXPECTED_SCHEMA_FINGERPRINT`` (lazily, at initialize
|
|
@@ -323,9 +323,12 @@ class IgnoredMissingParams:
|
|
|
323
323
|
class IgnoredStaleFrame:
|
|
324
324
|
"""A recognized frame the fold deliberately dropped, state unchanged.
|
|
325
325
|
|
|
326
|
-
A ``turn/started``
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
A ``turn/started``, ``turn/retryScheduled``, ``turn/modelRetryScheduled``
|
|
327
|
+
or ``turn/foregroundCompleted`` for a turn that already left ``running``;
|
|
328
|
+
a ``turn/completed`` for a ``retracted`` or ``unqueued`` turn; a declined
|
|
329
|
+
``turn/retracted`` for a turn the fold has not seen; an
|
|
330
|
+
``approval/updated`` for an approval it never saw requested (or whose
|
|
331
|
+
entry a resolution already deleted); or a redelivered
|
|
329
332
|
``approval/requested`` / ``userInput/requested`` for an approval/prompt
|
|
330
333
|
that already has its durable terminal. Truthful by contract: a "pending"
|
|
331
334
|
outcome here would make a renderer show an entry the fold does not hold.
|
|
@@ -714,15 +717,25 @@ class SessionFold:
|
|
|
714
717
|
|
|
715
718
|
def _turn_retracted(self, params: Params) -> FoldOutcome:
|
|
716
719
|
turn_id = str(params["turnId"])
|
|
720
|
+
# A declined retract means the submission stood, so the fold keeps
|
|
721
|
+
# the turn's prior state: a running turn stays running and its
|
|
722
|
+
# turn/completed still settles it, and a settled turn stays settled.
|
|
723
|
+
# The fold records the standing submission's commandId on a turn it
|
|
724
|
+
# holds. A declined retract carries no lifecycle fact, so for a turn
|
|
725
|
+
# the fold has not seen it creates no entry. Only an explicit
|
|
726
|
+
# "declined" counts; an absent or unrecognized outcome reads as
|
|
727
|
+
# accepted.
|
|
728
|
+
if params.get("outcome") == "declined":
|
|
729
|
+
held = self._turns.get(turn_id)
|
|
730
|
+
if held is None:
|
|
731
|
+
return IgnoredStaleFrame("turn/retracted", turn_id)
|
|
732
|
+
held.command_id = params.get("commandId")
|
|
733
|
+
return TurnFold(turn_id, held.state)
|
|
717
734
|
turn = self._turn_for(turn_id)
|
|
718
735
|
turn.command_id = params.get("commandId")
|
|
719
736
|
# A retract is NOT a TurnTerminal; leaving `terminal` unset is the
|
|
720
737
|
# whole point. On accepted, the retracted user message re-arrives via
|
|
721
|
-
# `item/updated` with `retracted: true`, which the item half folds
|
|
722
|
-
# (a declined outcome also lands here for now: the entry reads
|
|
723
|
-
# retracted and the later turn/completed arrives as a stale frame,
|
|
724
|
-
# so its terminal is dropped too - a known gap in this fold,
|
|
725
|
-
# behavior unchanged).
|
|
738
|
+
# `item/updated` with `retracted: true`, which the item half folds.
|
|
726
739
|
turn.state = "retracted"
|
|
727
740
|
self._clear_active(turn_id)
|
|
728
741
|
return TurnFold(turn_id, turn.state)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: muse-code-sdk
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.10
|
|
4
4
|
Summary: Muse Code SDK for MSP: asyncio-first facade over the generated wire types (muse-code-msp), with a thin sync wrapper.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/meta-models/muse-code-sdk
|
|
@@ -21,6 +21,7 @@ so this file's own text stays clean under the tree tier.
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
23
|
import importlib.util
|
|
24
|
+
import io
|
|
24
25
|
import re
|
|
25
26
|
import subprocess
|
|
26
27
|
import sys
|
|
@@ -674,3 +675,190 @@ def test_the_npm_tarball_mode_refuses_a_tarball_missing_its_readme(tmp_path: Pat
|
|
|
674
675
|
result = _run_checker("--npm-tarball", str(tarball))
|
|
675
676
|
assert result.returncode != 0
|
|
676
677
|
assert "no package/README.md" in result.stderr
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
# A Rust source member's name, built at run time so this mirrored file carries
|
|
682
|
+
# no repository source-path token of its own.
|
|
683
|
+
LIB_RS = "src/lib" + ".r" + "s"
|
|
684
|
+
|
|
685
|
+
def _seeded_crate(
|
|
686
|
+
tmp_path: Path,
|
|
687
|
+
*,
|
|
688
|
+
readme_line: str = "Drive the host from a Tokio runtime.",
|
|
689
|
+
description: str = "A clean summary.",
|
|
690
|
+
files: dict[str, str] | None = None,
|
|
691
|
+
) -> Path:
|
|
692
|
+
# A .crate is a gzip tarball under one <name>-<version>/ root.
|
|
693
|
+
entries = {
|
|
694
|
+
"README.md": f"# clean\n\n{readme_line}\n",
|
|
695
|
+
"Cargo.toml": (
|
|
696
|
+
'[package]\nname = "muse-code-sdk"\nversion = "1.0.0"\n'
|
|
697
|
+
f'description = "{description}"\n'
|
|
698
|
+
),
|
|
699
|
+
LIB_RS: "//! A clean crate.\n",
|
|
700
|
+
}
|
|
701
|
+
entries.update(files or {})
|
|
702
|
+
crate = tmp_path / "muse-code-sdk-1.0.0.crate"
|
|
703
|
+
with tarfile.open(crate, "w:gz") as archive:
|
|
704
|
+
for name, text in entries.items():
|
|
705
|
+
data = text.encode("utf-8")
|
|
706
|
+
info = tarfile.TarInfo(f"muse-code-sdk-1.0.0/{name}")
|
|
707
|
+
info.size = len(data)
|
|
708
|
+
archive.addfile(info, io.BytesIO(data))
|
|
709
|
+
return crate
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _vcs_record(path_in_vcs: str) -> str:
|
|
713
|
+
return (
|
|
714
|
+
'{"git": {"sha1": "' + "a" * 40 + '", "dirty": false}, '
|
|
715
|
+
f'"path_in_vcs": "{path_in_vcs}"}}\n'
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def test_a_clean_crate_passes(tmp_path: Path) -> None:
|
|
720
|
+
result = _run_checker("--crate", str(_seeded_crate(tmp_path)))
|
|
721
|
+
assert result.returncode == 0, result.stderr
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@pytest.mark.parametrize(
|
|
725
|
+
("site", "overrides"),
|
|
726
|
+
[
|
|
727
|
+
# A metadata-only class: the tree tier allows a closure path, the README
|
|
728
|
+
# (registry metadata) does not.
|
|
729
|
+
("README.md", {"readme_line": LEAKS["client-path"]}),
|
|
730
|
+
("Cargo.toml [package] description", {"description": LEAKS["client-path"]}),
|
|
731
|
+
(
|
|
732
|
+
LIB_RS + ": adr-citation",
|
|
733
|
+
{"files": {LIB_RS: "//! A clean crate.\n// Checked per " + LEAKS["adr-citation"] + ".\n"}},
|
|
734
|
+
),
|
|
735
|
+
],
|
|
736
|
+
ids=["crate-readme", "crate-description", "crate-source"],
|
|
737
|
+
)
|
|
738
|
+
def test_each_crate_site_is_refused(tmp_path: Path, site: str, overrides: dict) -> None:
|
|
739
|
+
result = _run_checker("--crate", str(_seeded_crate(tmp_path, **overrides)))
|
|
740
|
+
assert result.returncode != 0, f"a leak in the {site} site passed the crate gate"
|
|
741
|
+
assert site in result.stderr, f"the refusal must name the {site} site:\n{result.stderr}"
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def test_the_crate_mode_passes_the_mirrors_commit_record_and_refuses_the_producers(
|
|
745
|
+
tmp_path: Path,
|
|
746
|
+
) -> None:
|
|
747
|
+
# Cargo writes .cargo_vcs_info.json into every crate packaged from git: a
|
|
748
|
+
# commit SHA (no class here) and the crate's path in the repository. The
|
|
749
|
+
# tree tier allows the mirror's rust/clients/sdk-rs; the producing
|
|
750
|
+
# checkout's path is a dev-loop path, so a crate packaged there refuses.
|
|
751
|
+
mirror = _seeded_crate(
|
|
752
|
+
tmp_path, files={".cargo_vcs_info.json": _vcs_record("rust/clients/sdk-rs")}
|
|
753
|
+
)
|
|
754
|
+
result = _run_checker("--crate", str(mirror))
|
|
755
|
+
assert result.returncode == 0, result.stderr
|
|
756
|
+
producer_dir = tmp_path / "producer"
|
|
757
|
+
producer_dir.mkdir()
|
|
758
|
+
producer = _seeded_crate(
|
|
759
|
+
producer_dir,
|
|
760
|
+
files={".cargo_vcs_info.json": _vcs_record("projects/" + "tb" + "h/clients/sdk-rs")},
|
|
761
|
+
)
|
|
762
|
+
result = _run_checker("--crate", str(producer))
|
|
763
|
+
assert result.returncode != 0
|
|
764
|
+
assert ".cargo_vcs_info.json: dev-loop-path" in result.stderr, result.stderr
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def test_the_crate_mode_refuses_a_crate_missing_its_readme(tmp_path: Path) -> None:
|
|
768
|
+
crate = tmp_path / "muse-code-sdk-1.0.0.crate"
|
|
769
|
+
data = b'[package]\nname = "muse-code-sdk"\nversion = "1.0.0"\n'
|
|
770
|
+
with tarfile.open(crate, "w:gz") as archive:
|
|
771
|
+
info = tarfile.TarInfo("muse-code-sdk-1.0.0/Cargo.toml")
|
|
772
|
+
info.size = len(data)
|
|
773
|
+
archive.addfile(info, io.BytesIO(data))
|
|
774
|
+
result = _run_checker("--crate", str(crate))
|
|
775
|
+
assert result.returncode != 0
|
|
776
|
+
assert "no muse-code-sdk-1.0.0/README.md" in result.stderr
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def test_the_crate_mode_holds_module_docs_to_the_full_table(tmp_path: Path) -> None:
|
|
780
|
+
# The crate's documentation pages show each module's inner docs, so a
|
|
781
|
+
# metadata-only class refuses there; the same text in an ordinary comment
|
|
782
|
+
# is tree-tier and passes.
|
|
783
|
+
in_docs = _seeded_crate(tmp_path, files={LIB_RS: "//! See " + LEAKS["client-path"] + ".\n"})
|
|
784
|
+
result = _run_checker("--crate", str(in_docs))
|
|
785
|
+
assert result.returncode != 0
|
|
786
|
+
assert LIB_RS + " module docs: client-path" in result.stderr, result.stderr
|
|
787
|
+
comment_dir = tmp_path / "comment"
|
|
788
|
+
comment_dir.mkdir()
|
|
789
|
+
in_comment = _seeded_crate(
|
|
790
|
+
comment_dir, files={LIB_RS: "//! A clean crate.\n// See " + LEAKS["client-path"] + ".\n"}
|
|
791
|
+
)
|
|
792
|
+
result = _run_checker("--crate", str(in_comment))
|
|
793
|
+
assert result.returncode == 0, result.stderr
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def test_the_crate_mode_holds_example_headers_to_the_full_table(tmp_path: Path) -> None:
|
|
797
|
+
# Examples ship in the crate too, so their headers get the same table.
|
|
798
|
+
example = "examples/hello" + ".r" + "s"
|
|
799
|
+
crate = _seeded_crate(tmp_path, files={example: "//! See " + LEAKS["client-path"] + ".\nfn main() {}\n"})
|
|
800
|
+
result = _run_checker("--crate", str(crate))
|
|
801
|
+
assert result.returncode != 0
|
|
802
|
+
assert example + " module docs: client-path" in result.stderr, result.stderr
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _crate_with(tmp_path: Path, members: dict[str, bytes]) -> Path:
|
|
806
|
+
crate = tmp_path / "muse-code-sdk-1.0.0.crate"
|
|
807
|
+
with tarfile.open(crate, "w:gz") as archive:
|
|
808
|
+
for name, data in members.items():
|
|
809
|
+
info = tarfile.TarInfo(name)
|
|
810
|
+
info.size = len(data)
|
|
811
|
+
archive.addfile(info, io.BytesIO(data))
|
|
812
|
+
return crate
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
_CLEAN_MANIFEST = b'[package]\nname = "muse-code-sdk"\nversion = "1.0.0"\n'
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
@pytest.mark.parametrize(
|
|
819
|
+
("members", "message"),
|
|
820
|
+
[
|
|
821
|
+
(
|
|
822
|
+
{"a-1.0.0/README.md": b"# a\n", "b-1.0.0/README.md": b"# b\n"},
|
|
823
|
+
"has no single package root",
|
|
824
|
+
),
|
|
825
|
+
(
|
|
826
|
+
{
|
|
827
|
+
"muse-code-sdk-1.0.0/README.md": b"# clean\n",
|
|
828
|
+
"muse-code-sdk-1.0.0/Cargo.toml": _CLEAN_MANIFEST,
|
|
829
|
+
"muse-code-sdk-1.0.0/src/data.bin": b"\xff\xfe",
|
|
830
|
+
},
|
|
831
|
+
"not UTF-8 text",
|
|
832
|
+
),
|
|
833
|
+
(
|
|
834
|
+
{
|
|
835
|
+
"muse-code-sdk-1.0.0/README.md": b"# clean\n",
|
|
836
|
+
"muse-code-sdk-1.0.0/Cargo.toml": b"[package\nname = 1\n",
|
|
837
|
+
},
|
|
838
|
+
"not parseable TOML",
|
|
839
|
+
),
|
|
840
|
+
],
|
|
841
|
+
ids=["two-roots", "binary-member", "bad-manifest"],
|
|
842
|
+
)
|
|
843
|
+
def test_the_crate_mode_refuses_a_malformed_crate(
|
|
844
|
+
tmp_path: Path, members: dict[str, bytes], message: str
|
|
845
|
+
) -> None:
|
|
846
|
+
result = _run_checker("--crate", str(_crate_with(tmp_path, members)))
|
|
847
|
+
assert result.returncode != 0
|
|
848
|
+
assert message in result.stderr, result.stderr
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def test_the_crate_mode_refuses_a_file_that_is_no_crate_archive(tmp_path: Path) -> None:
|
|
852
|
+
not_a_crate = tmp_path / "muse-code-sdk-1.0.0.crate"
|
|
853
|
+
not_a_crate.write_text("not a tarball\n")
|
|
854
|
+
result = _run_checker("--crate", str(not_a_crate))
|
|
855
|
+
assert result.returncode != 0
|
|
856
|
+
assert "is not a readable crate archive" in result.stderr, result.stderr
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def test_the_crate_mode_refuses_a_crate_that_is_missing(tmp_path: Path) -> None:
|
|
860
|
+
# The publish script relies on this: it has no check of its own for the
|
|
861
|
+
# file `cargo package` writes.
|
|
862
|
+
result = _run_checker("--crate", str(tmp_path / "muse-code-sdk-1.0.0.crate"))
|
|
863
|
+
assert result.returncode != 0
|
|
864
|
+
assert "is not a readable crate archive" in result.stderr, result.stderr
|
|
@@ -433,6 +433,33 @@ def test_digest_tree_matches_across_layouts(tmp_path: Path) -> None:
|
|
|
433
433
|
assert digest_of(producer) == digest_of(mirror)
|
|
434
434
|
|
|
435
435
|
|
|
436
|
+
def test_digest_tree_matches_across_layouts_for_rust(tmp_path: Path) -> None:
|
|
437
|
+
# "Rust closure" entries map under rust/ the same way: the producer's
|
|
438
|
+
# verbatim crate and the mirror's rust/ copy digest identically.
|
|
439
|
+
producer = tmp_path / "producer"
|
|
440
|
+
mirror = tmp_path / "mirror"
|
|
441
|
+
notes = {
|
|
442
|
+
"clients/crate": "Rust closure. Marked for the mapping test.",
|
|
443
|
+
"scripts/publish.sh": "Rust closure. Marked for the mapping test.",
|
|
444
|
+
}
|
|
445
|
+
write_mirror_tree(
|
|
446
|
+
producer,
|
|
447
|
+
{"clients/crate/a.txt": "one\n", "scripts/publish.sh": "two\n"},
|
|
448
|
+
entries=["clients/crate", "scripts/publish.sh"],
|
|
449
|
+
notes=notes,
|
|
450
|
+
)
|
|
451
|
+
write_mirror_tree(
|
|
452
|
+
mirror,
|
|
453
|
+
{
|
|
454
|
+
"rust/clients/crate/a.txt": "one\n",
|
|
455
|
+
"rust/scripts/publish.sh": "two\n",
|
|
456
|
+
},
|
|
457
|
+
entries=["clients/crate", "scripts/publish.sh"],
|
|
458
|
+
notes=notes,
|
|
459
|
+
)
|
|
460
|
+
assert digest_of(producer) == digest_of(mirror)
|
|
461
|
+
|
|
462
|
+
|
|
436
463
|
def test_digest_tree_refuses_symlink_inside_directory(tmp_path: Path) -> None:
|
|
437
464
|
# A symlink anywhere under a walked entry refuses instead of
|
|
438
465
|
# resolving: link following could escape the closure.
|
|
@@ -573,6 +600,16 @@ def test_closure_python_marked_set_is_frozen() -> None:
|
|
|
573
600
|
}
|
|
574
601
|
|
|
575
602
|
|
|
603
|
+
def test_closure_rust_marked_set_is_frozen() -> None:
|
|
604
|
+
# LOAD-BEARING FREEZE, like the Python one: "Rust closure" notes map
|
|
605
|
+
# entries under rust/. None exist until the Rust SDK joins the mirror
|
|
606
|
+
# closure; that change updates this pin deliberately.
|
|
607
|
+
manifest = json.loads(CLOSURE_MANIFEST.read_text(encoding="utf-8"))
|
|
608
|
+
notes = manifest["path_notes"]
|
|
609
|
+
marked = {path for path, note in notes.items() if note.startswith("Rust closure")}
|
|
610
|
+
assert marked == set()
|
|
611
|
+
|
|
612
|
+
|
|
576
613
|
def test_scan_text_refuses_undecodable_file(tmp_path: Path) -> None:
|
|
577
614
|
binary = tmp_path / "bytes.bin"
|
|
578
615
|
binary.write_bytes(b"\xff\xfe\x00bad")
|
|
@@ -69,17 +69,19 @@ def turn_completed(
|
|
|
69
69
|
return {"method": "turn/completed", "params": params}
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def turn_retracted(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
},
|
|
72
|
+
def turn_retracted(
|
|
73
|
+
turn_id: str, command_id: str, view_cursor: str, outcome: str | None = None
|
|
74
|
+
) -> dict[str, Any]:
|
|
75
|
+
params: dict[str, Any] = {
|
|
76
|
+
"commandId": command_id,
|
|
77
|
+
"sessionId": SESSION,
|
|
78
|
+
"sourceRange": SOURCE,
|
|
79
|
+
"turnId": turn_id,
|
|
80
|
+
"viewCursor": view_cursor,
|
|
82
81
|
}
|
|
82
|
+
if outcome is not None:
|
|
83
|
+
params["outcome"] = outcome
|
|
84
|
+
return {"method": "turn/retracted", "params": params}
|
|
83
85
|
|
|
84
86
|
|
|
85
87
|
def turn_unqueued(turn_id: str, command_id: str, view_cursor: str) -> dict[str, Any]:
|
|
@@ -443,6 +445,79 @@ def test_a_reclaimed_turn_stays_unqueued_no_turn_completed_ever_settles_it() ->
|
|
|
443
445
|
assert held.terminal is None, "a reclaim is not a terminal"
|
|
444
446
|
|
|
445
447
|
|
|
448
|
+
# A declined retract means the submission stood: the fold keeps a turn's prior
|
|
449
|
+
# state, so turn/completed settles a running turn in either order. On a turn
|
|
450
|
+
# the fold holds it records the retract's commandId (the standing
|
|
451
|
+
# submission's).
|
|
452
|
+
def test_a_declined_retract_after_turn_completed_leaves_the_turn_settled_and_records_its_command_id() -> None:
|
|
453
|
+
fold = SessionFold()
|
|
454
|
+
# First seen through its completion, so only the retract names the command.
|
|
455
|
+
fold.apply(turn_completed("turn-1", "completed", "v:s:1"))
|
|
456
|
+
outcome = fold.apply(turn_retracted("turn-1", "cmd-c", "v:s:2", "declined"))
|
|
457
|
+
|
|
458
|
+
assert outcome == TurnFold("turn-1", "settled")
|
|
459
|
+
turn = fold.turn("turn-1")
|
|
460
|
+
assert turn is not None
|
|
461
|
+
assert turn.state == "settled", "the submission stood"
|
|
462
|
+
assert turn.terminal == "completed"
|
|
463
|
+
assert turn.command_id == "cmd-c"
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def test_a_declined_retract_while_the_turn_runs_leaves_it_running_and_turn_completed_settles_it() -> None:
|
|
467
|
+
fold = SessionFold()
|
|
468
|
+
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
469
|
+
outcome = fold.apply(turn_retracted("turn-1", "cmd-1", "v:s:2", "declined"))
|
|
470
|
+
|
|
471
|
+
assert outcome == TurnFold("turn-1", "running")
|
|
472
|
+
turn = fold.turn("turn-1")
|
|
473
|
+
assert turn is not None and turn.state == "running", "the submission stood"
|
|
474
|
+
assert fold.active_turn_id == "turn-1", "the turn is still the active one"
|
|
475
|
+
|
|
476
|
+
completed = fold.apply(turn_completed("turn-1", "completed", "v:s:3"))
|
|
477
|
+
assert completed == TurnFold("turn-1", "settled")
|
|
478
|
+
turn = fold.turn("turn-1")
|
|
479
|
+
assert turn is not None and turn.terminal == "completed", "the terminal is not dropped"
|
|
480
|
+
assert fold.active_turn_id is None
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def test_a_declined_retract_leaves_a_reclaimed_turn_unqueued_and_records_its_command_id() -> None:
|
|
484
|
+
# The protocol never pairs these frames. The test pins the commandId rule
|
|
485
|
+
# for a held turn in any state, with a distinct id so the record shows.
|
|
486
|
+
fold = SessionFold()
|
|
487
|
+
fold.apply(turn_unqueued("turn-2", "cmd-2", "v:s:1"))
|
|
488
|
+
fold.apply(turn_retracted("turn-2", "cmd-c", "v:s:2", "declined"))
|
|
489
|
+
|
|
490
|
+
turn = fold.turn("turn-2")
|
|
491
|
+
assert turn is not None
|
|
492
|
+
assert turn.state == "unqueued"
|
|
493
|
+
assert turn.command_id == "cmd-c"
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def test_a_declined_retract_for_a_turn_the_fold_has_not_seen_creates_no_entry() -> None:
|
|
497
|
+
fold = SessionFold()
|
|
498
|
+
# A client that joined after this turn's completion: the declined retract
|
|
499
|
+
# carries no lifecycle fact, so the fold must not invent a running turn.
|
|
500
|
+
outcome = fold.apply(turn_retracted("turn-9", "cmd-9", "v:s:1", "declined"))
|
|
501
|
+
|
|
502
|
+
assert outcome == IgnoredStaleFrame("turn/retracted", "turn-9")
|
|
503
|
+
assert fold.turn("turn-9") is None
|
|
504
|
+
assert fold.active_turn_id is None
|
|
505
|
+
|
|
506
|
+
fold.apply(turn_completed("turn-9", "completed", "v:s:2"))
|
|
507
|
+
turn = fold.turn("turn-9")
|
|
508
|
+
assert turn is not None and turn.state == "settled", "a later completion still settles it"
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def test_an_unrecognized_retract_outcome_reads_as_accepted() -> None:
|
|
512
|
+
fold = SessionFold()
|
|
513
|
+
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
514
|
+
fold.apply(turn_retracted("turn-1", "cmd-1", "v:s:2", "future-outcome"))
|
|
515
|
+
|
|
516
|
+
turn = fold.turn("turn-1")
|
|
517
|
+
assert turn is not None and turn.state == "retracted"
|
|
518
|
+
assert fold.active_turn_id is None
|
|
519
|
+
|
|
520
|
+
|
|
446
521
|
def test_turn_retracted_carries_the_command_id_when_the_retract_is_the_first_frame() -> None:
|
|
447
522
|
fold = SessionFold()
|
|
448
523
|
# A client joining mid-session can see `turn/retracted` as its first
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{muse_code_sdk-1.4.9 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|