muse-code-sdk 1.4.8__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.8/src/muse_code_sdk.egg-info → muse_code_sdk-1.4.10}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/pyproject.toml +1 -1
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/__init__.py +2 -2
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/fold/__init__.py +2 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/fold/session_fold.py +40 -8
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10/src/muse_code_sdk.egg-info}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_external_audience.py +188 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_public_mirror_text.py +37 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_session_fold.py +108 -10
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/LICENSE +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/README.md +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/setup.cfg +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/connection/__init__.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/connection/connection.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/connection/discovery.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/connection/spawn.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/connection/validation.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/errors.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/__init__.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/approval.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/client.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/discarded.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/gap_fill.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/host_death.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/host_start.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/session.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/turn_handle.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/facade/turn_submit.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/fold/item_store.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/fold/state_store.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/pending/__init__.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/pending/pending_command_set.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/py.typed +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/replay.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code/sync_facade.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/SOURCES.txt +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/dependency_links.txt +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/requires.txt +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/src/muse_code_sdk.egg-info/top_level.txt +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_asyncio_cancellation.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_compatibility_row.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_connection.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_connection_error_arms.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_docs_generation.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_facade_approval.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_facade_gap_fill.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_facade_open_same_chunk.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_facade_persistent_approval.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_facade_required_selection.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_facade_turn.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_host_discovery.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_item_store.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_manifests.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_muse_serve_child.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_pending_command_corpus.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_pending_command_set.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_pending_command_wire_binding.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_permission_state_wire_types.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_publish_shape.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_source_closure.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_spawn_handshake.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_spawn_host_lost.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_state_store.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_sync_facade.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_tolerance_corpus.py +0 -0
- {muse_code_sdk-1.4.8 → muse_code_sdk-1.4.10}/tests/test_transcript_replay.py +0 -0
- {muse_code_sdk-1.4.8 → 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"
|
|
@@ -80,7 +80,7 @@ __all__ = [
|
|
|
80
80
|
]
|
|
81
81
|
|
|
82
82
|
EXPECTED_SCHEMA_FINGERPRINT: Final[str] = (
|
|
83
|
-
"sha256:
|
|
83
|
+
"sha256:5d3b43e5ccdb7dc4f5a32c380f5f2cde0f10e3059b93610afb2e73f73b1d914c"
|
|
84
84
|
)
|
|
85
85
|
"""The stable schema-bundle fingerprint this SDK was built against.
|
|
86
86
|
|
|
@@ -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
|
|
@@ -21,6 +21,7 @@ from .session_fold import (
|
|
|
21
21
|
LIVE_HOST_STATE_PROJECTIONS,
|
|
22
22
|
VIEW_EVENT_METHODS,
|
|
23
23
|
ApprovalPending,
|
|
24
|
+
ApprovalReviewingNotice,
|
|
24
25
|
ApprovalResolvedFold,
|
|
25
26
|
DeliveryGap,
|
|
26
27
|
FoldItems,
|
|
@@ -48,6 +49,7 @@ __all__ = [
|
|
|
48
49
|
"VIEW_EVENT_METHODS",
|
|
49
50
|
"Appended",
|
|
50
51
|
"ApprovalPending",
|
|
52
|
+
"ApprovalReviewingNotice",
|
|
51
53
|
"ApprovalResolvedFold",
|
|
52
54
|
"BufferedForAbsentItem",
|
|
53
55
|
"DeliveryGap",
|
|
@@ -75,6 +75,9 @@ VIEW_EVENT_METHODS: frozenset[str] = frozenset(
|
|
|
75
75
|
"turn/modelRetryScheduled",
|
|
76
76
|
"turn/unqueued",
|
|
77
77
|
"approval/requested",
|
|
78
|
+
# The reviewer-held twin of `approval/requested`: routed to its own
|
|
79
|
+
# non-storing notice.
|
|
80
|
+
"approval/reviewing",
|
|
78
81
|
"approval/updated",
|
|
79
82
|
"approval/resolved",
|
|
80
83
|
"userInput/requested",
|
|
@@ -281,6 +284,19 @@ class ModelRouteUnservedNotice:
|
|
|
281
284
|
provider_id: str | None
|
|
282
285
|
|
|
283
286
|
|
|
287
|
+
@dataclass(frozen=True)
|
|
288
|
+
class ApprovalReviewingNotice:
|
|
289
|
+
"""The ``approval/reviewing`` typed-activity signal.
|
|
290
|
+
|
|
291
|
+
The host's built-in reviewer holds this approval. A notice, never a
|
|
292
|
+
pending entry: it seeds no store and opens no decision — the approval is
|
|
293
|
+
absent from ``pending_approvals`` until ``approval/requested`` releases it
|
|
294
|
+
to the human (or ``approval/resolved`` settles it) on the same id.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
approval_id: str
|
|
298
|
+
|
|
299
|
+
|
|
284
300
|
@dataclass(frozen=True)
|
|
285
301
|
class IgnoredUnrecognizedMethod:
|
|
286
302
|
"""A newer host's method this SDK has never heard of."""
|
|
@@ -307,9 +323,12 @@ class IgnoredMissingParams:
|
|
|
307
323
|
class IgnoredStaleFrame:
|
|
308
324
|
"""A recognized frame the fold deliberately dropped, state unchanged.
|
|
309
325
|
|
|
310
|
-
A ``turn/started``
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
313
332
|
``approval/requested`` / ``userInput/requested`` for an approval/prompt
|
|
314
333
|
that already has its durable terminal. Truthful by contract: a "pending"
|
|
315
334
|
outcome here would make a renderer show an entry the fold does not hold.
|
|
@@ -330,6 +349,7 @@ FoldOutcome = (
|
|
|
330
349
|
| UserInputSettledFold
|
|
331
350
|
| DeliveryGap
|
|
332
351
|
| ModelRouteUnservedNotice
|
|
352
|
+
| ApprovalReviewingNotice
|
|
333
353
|
| IgnoredUnrecognizedMethod
|
|
334
354
|
| IgnoredMissingParams
|
|
335
355
|
| IgnoredStaleFrame
|
|
@@ -585,6 +605,8 @@ class SessionFold:
|
|
|
585
605
|
return self._turn_foreground_completed(params)
|
|
586
606
|
if method == "approval/requested":
|
|
587
607
|
return self._approval_requested(params)
|
|
608
|
+
if method == "approval/reviewing":
|
|
609
|
+
return ApprovalReviewingNotice(approval_id=str(params["approvalId"]))
|
|
588
610
|
if method == "approval/updated":
|
|
589
611
|
return self._approval_updated(params)
|
|
590
612
|
if method == "approval/resolved":
|
|
@@ -695,15 +717,25 @@ class SessionFold:
|
|
|
695
717
|
|
|
696
718
|
def _turn_retracted(self, params: Params) -> FoldOutcome:
|
|
697
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)
|
|
698
734
|
turn = self._turn_for(turn_id)
|
|
699
735
|
turn.command_id = params.get("commandId")
|
|
700
736
|
# A retract is NOT a TurnTerminal; leaving `terminal` unset is the
|
|
701
737
|
# whole point. On accepted, the retracted user message re-arrives via
|
|
702
|
-
# `item/updated` with `retracted: true`, which the item half folds
|
|
703
|
-
# (a declined outcome also lands here for now: the entry reads
|
|
704
|
-
# retracted and the later turn/completed arrives as a stale frame,
|
|
705
|
-
# so its terminal is dropped too - a known gap in this fold,
|
|
706
|
-
# behavior unchanged).
|
|
738
|
+
# `item/updated` with `retracted: true`, which the item half folds.
|
|
707
739
|
turn.state = "retracted"
|
|
708
740
|
self._clear_active(turn_id)
|
|
709
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")
|
|
@@ -15,6 +15,8 @@ from typing import Any
|
|
|
15
15
|
from muse_code.fold import (
|
|
16
16
|
LIVE_HOST_STATE_PROJECTIONS,
|
|
17
17
|
VIEW_EVENT_METHODS,
|
|
18
|
+
ApprovalPending,
|
|
19
|
+
ApprovalReviewingNotice,
|
|
18
20
|
ApprovalResolvedFold,
|
|
19
21
|
DeliveryGap,
|
|
20
22
|
FoldItems,
|
|
@@ -67,17 +69,19 @@ def turn_completed(
|
|
|
67
69
|
return {"method": "turn/completed", "params": params}
|
|
68
70
|
|
|
69
71
|
|
|
70
|
-
def turn_retracted(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
},
|
|
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,
|
|
80
81
|
}
|
|
82
|
+
if outcome is not None:
|
|
83
|
+
params["outcome"] = outcome
|
|
84
|
+
return {"method": "turn/retracted", "params": params}
|
|
81
85
|
|
|
82
86
|
|
|
83
87
|
def turn_unqueued(turn_id: str, command_id: str, view_cursor: str) -> dict[str, Any]:
|
|
@@ -178,6 +182,13 @@ def approval_requested(
|
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
|
|
185
|
+
def approval_reviewing(approval_id: str, view_cursor: str) -> dict[str, Any]:
|
|
186
|
+
# The reviewer-held twin of `approval/requested`: same params shape,
|
|
187
|
+
# different method.
|
|
188
|
+
event = approval_requested(approval_id, view_cursor)
|
|
189
|
+
return {"method": "approval/reviewing", "params": event["params"]}
|
|
190
|
+
|
|
191
|
+
|
|
181
192
|
def approval_updated(
|
|
182
193
|
approval_id: str, view_cursor: str, source_index: int = 1, command: str | None = None
|
|
183
194
|
) -> dict[str, Any]:
|
|
@@ -434,6 +445,79 @@ def test_a_reclaimed_turn_stays_unqueued_no_turn_completed_ever_settles_it() ->
|
|
|
434
445
|
assert held.terminal is None, "a reclaim is not a terminal"
|
|
435
446
|
|
|
436
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
|
+
|
|
437
521
|
def test_turn_retracted_carries_the_command_id_when_the_retract_is_the_first_frame() -> None:
|
|
438
522
|
fold = SessionFold()
|
|
439
523
|
# A client joining mid-session can see `turn/retracted` as its first
|
|
@@ -1072,6 +1156,19 @@ def test_the_items_surface_is_an_allowlist_pinned_by_member_set() -> None:
|
|
|
1072
1156
|
assert state_public == {"families", "get", "has"}
|
|
1073
1157
|
|
|
1074
1158
|
|
|
1159
|
+
def test_approval_reviewing_is_a_notice_that_opens_no_pending_approval() -> None:
|
|
1160
|
+
# The host's built-in reviewer holds the approval; opening a pending
|
|
1161
|
+
# entry here would defeat the presentation hold. The release arrives as
|
|
1162
|
+
# `approval/requested` on the same id.
|
|
1163
|
+
fold = SessionFold()
|
|
1164
|
+
outcome = fold.apply(approval_reviewing("a-1", "v:s:1"))
|
|
1165
|
+
assert outcome == ApprovalReviewingNotice(approval_id="a-1")
|
|
1166
|
+
assert fold.pending_approvals() == []
|
|
1167
|
+
released = fold.apply(approval_requested("a-1", "v:s:2"))
|
|
1168
|
+
assert isinstance(released, ApprovalPending)
|
|
1169
|
+
assert len(fold.pending_approvals()) == 1
|
|
1170
|
+
|
|
1171
|
+
|
|
1075
1172
|
def test_every_generated_view_notification_is_routed_never_the_default() -> None:
|
|
1076
1173
|
# The runtime twin of the TS AssertNever coverage pins, PLUS the routing
|
|
1077
1174
|
# table: every generated notification except the handshake's
|
|
@@ -1156,6 +1253,7 @@ def test_every_generated_view_notification_is_routed_never_the_default() -> None
|
|
|
1156
1253
|
"turn/foregroundCompleted": foreground_completed("turn-t5", "v:t:23"),
|
|
1157
1254
|
"turn/unqueued": turn_unqueued("turn-t4", "cmd-t4", "v:t:9"),
|
|
1158
1255
|
"approval/requested": approval_requested("a-t", "v:t:10"),
|
|
1256
|
+
"approval/reviewing": approval_reviewing("a-r", "v:t:23"),
|
|
1159
1257
|
"approval/updated": approval_updated("a-t", "v:t:10b"),
|
|
1160
1258
|
"approval/resolved": approval_resolved("a-t", "approved", "v:t:11"),
|
|
1161
1259
|
"userInput/requested": user_input_requested("u-t", "v:t:12"),
|
|
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.8 → 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
|