fred-runtime 3.1.0__tar.gz → 3.1.1__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.
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/PKG-INFO +1 -1
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/integrations/v2_runtime/adapters.py +78 -7
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime.egg-info/PKG-INFO +1 -1
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/pyproject.toml +1 -1
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_fred_workspace_fs.py +98 -14
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_kf_workspace_client.py +3 -1
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/README.md +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/agent_app.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/config.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/container.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/context.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/observability_factory.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/app/openai_compat_router.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/completion.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/history_display.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/pod_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/repl.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/repl_helpers.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/context_aware_tool.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/kf_base_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/kf_logs_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/kf_workspace_client.py +1 -1
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/mcp_interceptors.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/mcp_runtime.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/mcp_utils.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/deep/deep_runtime.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/eval/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/eval/collector.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/graph/graph_runtime.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/model_routing/contracts.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/model_routing/provider.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_langchain_adapter.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_message_codec.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_model_adapter.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_prompting.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_runtime.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_stream_adapter.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_tool_binding.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_tool_loop.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_tool_resolution.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_context.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/support/thinking.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/support/tool_approval.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/support/tool_loop.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime.egg-info/SOURCES.txt +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime.egg-info/entry_points.txt +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime.egg-info/requires.txt +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/setup.cfg +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_agent_app.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_config_loader.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_context.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_context_aware_tool.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_conversational_memory.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_eval_collector.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_eval_trace.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_graph_runtime_invoke_agent.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_graph_runtime_observability.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_history.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_kpi_display.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_mcp_config.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_model_routing.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_openai_compat_router.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_pod_client.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_react_thinking.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_repl_helpers.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_smoke.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_token_expiry.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_url_helpers.py +0 -0
- {fred_runtime-3.1.0 → fred_runtime-3.1.1}/tests/test_user_token_refresher.py +0 -0
|
@@ -923,18 +923,35 @@ class FredWorkspaceFs(WorkspaceFsPort):
|
|
|
923
923
|
)
|
|
924
924
|
return str(uid)
|
|
925
925
|
|
|
926
|
+
def _session_agent_instance_id(self) -> str:
|
|
927
|
+
# The agents subtree is keyed by the immutable per-team agent_instance_id
|
|
928
|
+
# (FILES-04 / AGENT-FILESYSTEM-RFC §3.1), injected from the execution grant
|
|
929
|
+
# — never the template agent_id, never agent-supplied.
|
|
930
|
+
aid = getattr(self._binding.runtime_context, "agent_instance_id", None)
|
|
931
|
+
if not aid:
|
|
932
|
+
raise RuntimeError(
|
|
933
|
+
"Workspace filesystem requires an agent instance in the session context."
|
|
934
|
+
)
|
|
935
|
+
return str(aid)
|
|
936
|
+
|
|
926
937
|
def _token(self) -> str:
|
|
927
938
|
return _workspace_access_token(self._binding.runtime_context)
|
|
928
939
|
|
|
929
940
|
# ---- path relativization (§7.1 security rule) ----
|
|
930
941
|
def _resolve(self, path: str, *, allow_root: bool = False) -> str:
|
|
931
942
|
team = self._session_team()
|
|
943
|
+
# Bare agent paths resolve to the running agent's own per-user space
|
|
944
|
+
# (FILES-04 / AGENT-FILESYSTEM-RFC §3, §6), not Mon espace.
|
|
945
|
+
agent_root = (
|
|
946
|
+
f"teams/{team}/agents/{self._session_agent_instance_id()}"
|
|
947
|
+
f"/users/{self._session_user()}"
|
|
948
|
+
)
|
|
932
949
|
parts = [p for p in (path or "").strip().replace("\\", "/").split("/") if p]
|
|
933
950
|
if ".." in parts:
|
|
934
951
|
raise ValueError("Path cannot contain parent path segments")
|
|
935
952
|
if not parts:
|
|
936
953
|
if allow_root:
|
|
937
|
-
return
|
|
954
|
+
return agent_root
|
|
938
955
|
raise ValueError("Path cannot be empty")
|
|
939
956
|
head = parts[0]
|
|
940
957
|
if head == "teams":
|
|
@@ -946,24 +963,78 @@ class FredWorkspaceFs(WorkspaceFsPort):
|
|
|
946
963
|
)
|
|
947
964
|
return "/".join(parts)
|
|
948
965
|
if head == "shared":
|
|
966
|
+
# Team-shared reads (e.g. resolve_template's team step) stay addressable;
|
|
967
|
+
# write/delete into shared is rejected separately (agents never share).
|
|
949
968
|
return f"teams/{team}/" + "/".join(parts)
|
|
950
|
-
return f"
|
|
969
|
+
return f"{agent_root}/" + "/".join(parts)
|
|
970
|
+
|
|
971
|
+
def _agent_root(self) -> str:
|
|
972
|
+
return (
|
|
973
|
+
f"teams/{self._session_team()}/agents/{self._session_agent_instance_id()}"
|
|
974
|
+
f"/users/{self._session_user()}"
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
def _resolve_owned(self, path: str) -> str:
|
|
978
|
+
"""
|
|
979
|
+
Resolve a path the agent must own — used for write and delete.
|
|
980
|
+
|
|
981
|
+
Agents read team-shared files and their own space, but may only *mutate*
|
|
982
|
+
inside their own agents subtree. A path resolving outside it — into
|
|
983
|
+
``shared/`` (G3: agents never share), Mon espace, or a sibling agent's
|
|
984
|
+
subtree (G2) — is a hard ``PermissionError`` (AGENT-FILESYSTEM-RFC §6).
|
|
985
|
+
"""
|
|
986
|
+
resolved = self._resolve(path)
|
|
987
|
+
root = self._agent_root()
|
|
988
|
+
if resolved != root and not resolved.startswith(root + "/"):
|
|
989
|
+
raise PermissionError(
|
|
990
|
+
f"Agents may only write inside their own space; '{path}' resolves outside it."
|
|
991
|
+
)
|
|
992
|
+
return resolved
|
|
993
|
+
|
|
994
|
+
def _clean_parts(self, path: str) -> list[str]:
|
|
995
|
+
parts = [p for p in (path or "").strip().replace("\\", "/").split("/") if p]
|
|
996
|
+
if ".." in parts:
|
|
997
|
+
raise ValueError("Path cannot contain parent path segments")
|
|
998
|
+
return parts
|
|
999
|
+
|
|
1000
|
+
def _resolve_user(self, path: str) -> str:
|
|
1001
|
+
# Explicit read of the run user's Mon espace (AGENT-FILESYSTEM-RFC §7) — same
|
|
1002
|
+
# user the agent acts for; KF enforces own-uid ownership. v1 reads the whole
|
|
1003
|
+
# Mon espace; selection-scoping (§7.3) is deferred hardening, like G1b.
|
|
1004
|
+
return f"teams/{self._session_team()}/users/{self._session_user()}/" + "/".join(
|
|
1005
|
+
self._clean_parts(path)
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
def _resolve_team(self, path: str) -> str:
|
|
1009
|
+
# Explicit read of the team's Espace d'equipe; governed by the user's team read.
|
|
1010
|
+
return f"teams/{self._session_team()}/shared/" + "/".join(
|
|
1011
|
+
self._clean_parts(path)
|
|
1012
|
+
)
|
|
951
1013
|
|
|
952
1014
|
# ---- operations ----
|
|
953
|
-
async def
|
|
1015
|
+
async def _download(self, resolved: str, original: str) -> bytes:
|
|
954
1016
|
try:
|
|
955
1017
|
blob = await self._workspace_client.fs_download_blob(
|
|
956
|
-
|
|
1018
|
+
resolved, self._token()
|
|
957
1019
|
)
|
|
958
1020
|
except WorkspaceRetrievalError as e:
|
|
959
1021
|
if e.status_code == 404:
|
|
960
|
-
raise WorkspaceFileNotFound(
|
|
1022
|
+
raise WorkspaceFileNotFound(original) from e
|
|
961
1023
|
raise
|
|
962
1024
|
return blob.bytes
|
|
963
1025
|
|
|
1026
|
+
async def read_bytes(self, path: str) -> bytes:
|
|
1027
|
+
return await self._download(self._resolve(path), path)
|
|
1028
|
+
|
|
964
1029
|
async def read_text(self, path: str) -> str:
|
|
965
1030
|
return (await self.read_bytes(path)).decode("utf-8")
|
|
966
1031
|
|
|
1032
|
+
async def read_user_bytes(self, path: str) -> bytes:
|
|
1033
|
+
return await self._download(self._resolve_user(path), path)
|
|
1034
|
+
|
|
1035
|
+
async def read_team_bytes(self, path: str) -> bytes:
|
|
1036
|
+
return await self._download(self._resolve_team(path), path)
|
|
1037
|
+
|
|
967
1038
|
async def write(
|
|
968
1039
|
self,
|
|
969
1040
|
path: str,
|
|
@@ -972,7 +1043,7 @@ class FredWorkspaceFs(WorkspaceFsPort):
|
|
|
972
1043
|
content_type: str | None = None,
|
|
973
1044
|
title: str | None = None,
|
|
974
1045
|
) -> PublishedArtifact:
|
|
975
|
-
resolved = self.
|
|
1046
|
+
resolved = self._resolve_owned(path)
|
|
976
1047
|
file_name = resolved.rsplit("/", 1)[-1]
|
|
977
1048
|
result = await self._workspace_client.fs_upload(
|
|
978
1049
|
resolved, content, file_name, content_type
|
|
@@ -997,7 +1068,7 @@ class FredWorkspaceFs(WorkspaceFsPort):
|
|
|
997
1068
|
]
|
|
998
1069
|
|
|
999
1070
|
async def delete(self, path: str) -> None:
|
|
1000
|
-
await self._workspace_client.fs_delete(self.
|
|
1071
|
+
await self._workspace_client.fs_delete(self._resolve_owned(path), self._token())
|
|
1001
1072
|
|
|
1002
1073
|
async def link_for(self, path: str) -> PublishedArtifact:
|
|
1003
1074
|
resolved = self._resolve(path)
|
|
@@ -67,7 +67,10 @@ def _fs(client: _FakeClient | None = None) -> FredWorkspaceFs:
|
|
|
67
67
|
fs = object.__new__(FredWorkspaceFs)
|
|
68
68
|
fs._binding = SimpleNamespace( # type: ignore[assignment]
|
|
69
69
|
runtime_context=SimpleNamespace(
|
|
70
|
-
team_id="acme",
|
|
70
|
+
team_id="acme",
|
|
71
|
+
user_id="u-1",
|
|
72
|
+
agent_instance_id="inst-7",
|
|
73
|
+
access_token="tok",
|
|
71
74
|
)
|
|
72
75
|
)
|
|
73
76
|
fs._settings = SimpleNamespace(team_id="acme") # type: ignore[assignment]
|
|
@@ -78,8 +81,20 @@ def _fs(client: _FakeClient | None = None) -> FredWorkspaceFs:
|
|
|
78
81
|
# ---- relativization (the §7.1 security rule) ----
|
|
79
82
|
|
|
80
83
|
|
|
81
|
-
def
|
|
82
|
-
|
|
84
|
+
def test_resolve_bare_path_goes_to_agent_space():
|
|
85
|
+
# FILES-04 §3/§6: a bare agent write lands in the agent's own per-user space,
|
|
86
|
+
# keyed by agent_instance_id — not Mon espace.
|
|
87
|
+
assert (
|
|
88
|
+
_fs()._resolve("outputs/q3.pptx")
|
|
89
|
+
== "teams/acme/agents/inst-7/users/u-1/outputs/q3.pptx"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_resolve_missing_agent_instance_raises():
|
|
94
|
+
fs = _fs()
|
|
95
|
+
fs._binding.runtime_context.agent_instance_id = None
|
|
96
|
+
with pytest.raises(RuntimeError, match="agent instance"):
|
|
97
|
+
fs._resolve("outputs/q3.pptx")
|
|
83
98
|
|
|
84
99
|
|
|
85
100
|
def test_resolve_shared_prefix_goes_to_team_space():
|
|
@@ -107,7 +122,7 @@ def test_resolve_empty_requires_allow_root():
|
|
|
107
122
|
fs = _fs()
|
|
108
123
|
with pytest.raises(ValueError, match="empty"):
|
|
109
124
|
fs._resolve("")
|
|
110
|
-
assert fs._resolve("", allow_root=True) == "teams/acme/users/u-1"
|
|
125
|
+
assert fs._resolve("", allow_root=True) == "teams/acme/agents/inst-7/users/u-1"
|
|
111
126
|
|
|
112
127
|
|
|
113
128
|
# ---- operations ----
|
|
@@ -120,6 +135,32 @@ async def test_read_bytes_maps_404_to_not_found():
|
|
|
120
135
|
await fs.read_bytes("outputs/missing.bin")
|
|
121
136
|
|
|
122
137
|
|
|
138
|
+
@pytest.mark.asyncio
|
|
139
|
+
async def test_read_user_bytes_resolves_to_mon_espace():
|
|
140
|
+
# G7: explicit read of the run user's Mon espace.
|
|
141
|
+
client = _FakeClient()
|
|
142
|
+
fs = _fs(client)
|
|
143
|
+
await fs.read_user_bytes("templates/brand.pptx")
|
|
144
|
+
assert client.calls[-1] == (
|
|
145
|
+
"download",
|
|
146
|
+
"teams/acme/users/u-1/templates/brand.pptx",
|
|
147
|
+
"tok",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.asyncio
|
|
152
|
+
async def test_read_team_bytes_resolves_to_shared():
|
|
153
|
+
# G7: explicit read of Espace d'equipe.
|
|
154
|
+
client = _FakeClient()
|
|
155
|
+
fs = _fs(client)
|
|
156
|
+
await fs.read_team_bytes("templates/brand.pptx")
|
|
157
|
+
assert client.calls[-1] == (
|
|
158
|
+
"download",
|
|
159
|
+
"teams/acme/shared/templates/brand.pptx",
|
|
160
|
+
"tok",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
123
164
|
@pytest.mark.asyncio
|
|
124
165
|
async def test_read_bytes_resolves_and_returns_content():
|
|
125
166
|
client = _FakeClient()
|
|
@@ -134,7 +175,7 @@ async def test_read_bytes_resolves_and_returns_content():
|
|
|
134
175
|
|
|
135
176
|
|
|
136
177
|
@pytest.mark.asyncio
|
|
137
|
-
async def
|
|
178
|
+
async def test_write_uploads_to_agent_space_and_returns_artifact():
|
|
138
179
|
client = _FakeClient()
|
|
139
180
|
fs = _fs(client)
|
|
140
181
|
artifact = await fs.write(
|
|
@@ -142,25 +183,65 @@ async def test_write_uploads_to_user_space_and_returns_artifact():
|
|
|
142
183
|
)
|
|
143
184
|
assert isinstance(artifact, PublishedArtifact)
|
|
144
185
|
assert artifact.file_name == "q3.pptx"
|
|
145
|
-
assert artifact.href == "/dl/teams/acme/users/u-1/outputs/q3.pptx"
|
|
146
|
-
assert client.calls[-1][0:2] == (
|
|
186
|
+
assert artifact.href == "/dl/teams/acme/agents/inst-7/users/u-1/outputs/q3.pptx"
|
|
187
|
+
assert client.calls[-1][0:2] == (
|
|
188
|
+
"upload",
|
|
189
|
+
"teams/acme/agents/inst-7/users/u-1/outputs/q3.pptx",
|
|
190
|
+
)
|
|
147
191
|
|
|
148
192
|
|
|
149
193
|
@pytest.mark.asyncio
|
|
150
|
-
async def
|
|
194
|
+
async def test_ls_lists_agent_root_by_default():
|
|
151
195
|
client = _FakeClient()
|
|
152
196
|
fs = _fs(client)
|
|
153
197
|
entries = await fs.ls()
|
|
154
198
|
assert [e.path for e in entries] == ["deck.pptx"]
|
|
155
|
-
assert client.calls[-1] == ("list", "teams/acme/users/u-1", "tok")
|
|
199
|
+
assert client.calls[-1] == ("list", "teams/acme/agents/inst-7/users/u-1", "tok")
|
|
156
200
|
|
|
157
201
|
|
|
158
202
|
@pytest.mark.asyncio
|
|
159
|
-
async def
|
|
203
|
+
async def test_delete_resolves_owned_path():
|
|
160
204
|
client = _FakeClient()
|
|
161
205
|
fs = _fs(client)
|
|
162
|
-
await fs.delete("
|
|
163
|
-
assert client.calls[-1] == (
|
|
206
|
+
await fs.delete("outputs/x.txt")
|
|
207
|
+
assert client.calls[-1] == (
|
|
208
|
+
"delete",
|
|
209
|
+
"teams/acme/agents/inst-7/users/u-1/outputs/x.txt",
|
|
210
|
+
"tok",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ---- write/delete isolation: agents mutate only their own subtree (G2/G3) ----
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.asyncio
|
|
218
|
+
async def test_write_rejects_shared_path():
|
|
219
|
+
# G3: an agent cannot write into Espace d'equipe even though it can read it.
|
|
220
|
+
fs = _fs()
|
|
221
|
+
with pytest.raises(PermissionError, match="own space"):
|
|
222
|
+
await fs.write("shared/templates/brand.pptx", b"x")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@pytest.mark.asyncio
|
|
226
|
+
async def test_delete_rejects_shared_path():
|
|
227
|
+
fs = _fs()
|
|
228
|
+
with pytest.raises(PermissionError, match="own space"):
|
|
229
|
+
await fs.delete("shared/x.txt")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@pytest.mark.asyncio
|
|
233
|
+
async def test_write_rejects_sibling_agent_absolute_path():
|
|
234
|
+
# G2: an absolute path into another agent instance's subtree is rejected.
|
|
235
|
+
fs = _fs()
|
|
236
|
+
with pytest.raises(PermissionError, match="own space"):
|
|
237
|
+
await fs.write("/teams/acme/agents/inst-OTHER/users/u-1/outputs/x.pptx", b"x")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@pytest.mark.asyncio
|
|
241
|
+
async def test_write_rejects_cross_user_absolute_path():
|
|
242
|
+
fs = _fs()
|
|
243
|
+
with pytest.raises(PermissionError, match="own space"):
|
|
244
|
+
await fs.write("/teams/acme/agents/inst-7/users/u-OTHER/outputs/x.pptx", b"x")
|
|
164
245
|
|
|
165
246
|
|
|
166
247
|
@pytest.mark.asyncio
|
|
@@ -170,10 +251,13 @@ async def test_link_for_resolves_and_returns_signed_artifact():
|
|
|
170
251
|
artifact = await fs.link_for("uploads/report.xlsx")
|
|
171
252
|
assert isinstance(artifact, PublishedArtifact)
|
|
172
253
|
assert artifact.file_name == "report.xlsx"
|
|
173
|
-
assert
|
|
254
|
+
assert (
|
|
255
|
+
artifact.href
|
|
256
|
+
== "/dl/teams/acme/agents/inst-7/users/u-1/uploads/report.xlsx?token=sig"
|
|
257
|
+
)
|
|
174
258
|
assert artifact.mime == "application/octet-stream"
|
|
175
259
|
assert client.calls[-1] == (
|
|
176
260
|
"share",
|
|
177
|
-
"teams/acme/users/u-1/uploads/report.xlsx",
|
|
261
|
+
"teams/acme/agents/inst-7/users/u-1/uploads/report.xlsx",
|
|
178
262
|
"tok",
|
|
179
263
|
)
|
|
@@ -247,7 +247,9 @@ def test_fs_path_percent_encodes_reserved_chars_preserving_separators():
|
|
|
247
247
|
# Reserved chars (#, ?, space) must be encoded so the {path:path} route is not
|
|
248
248
|
# truncated, while "/" separators stay literal.
|
|
249
249
|
assert (
|
|
250
|
-
KfWorkspaceClient._fs_path(
|
|
250
|
+
KfWorkspaceClient._fs_path(
|
|
251
|
+
"download", "teams/acme/users/u-1/outputs/Q3 #1?.txt"
|
|
252
|
+
)
|
|
251
253
|
== "/fs/download/teams/acme/users/u-1/outputs/Q3%20%231%3F.txt"
|
|
252
254
|
)
|
|
253
255
|
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/request_context_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{fred_runtime-3.1.0 → fred_runtime-3.1.1}/fred_runtime/runtime_support/user_token_refresher.py
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|