mlspace-plugin 0.3.3__tar.gz → 0.3.4__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.
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/INSTALL.md +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/PKG-INFO +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/RELEASE.md +5 -5
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/.claude-plugin/plugin.json +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/.codex-plugin/plugin.json +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/.mcp.json +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/mcp.json +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/plugin.json +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-diagnose-data-transfer/SKILL.md +12 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/pyproject.toml +1 -1
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/__init__.py +1 -1
- mlspace_plugin-0.3.4/src/mlspace_mcp/data_transfer_hooks.py +68 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/init_cli.py +20 -17
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/registry.py +5 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/setup_ui.py +18 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/data_transfer.py +10 -2
- mlspace_plugin-0.3.4/tests/test_acceptance_regressions.py +177 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/.agents/plugins/marketplace.json +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/.claude-plugin/marketplace.json +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/.env.example +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/.gitignore +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/AGENTS.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/CONTRIBUTING.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/INSTALL_AGENT.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/LICENSE +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/README.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/README.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-call-deployed-model-predict/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-deploy-inference-service/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-diagnose-failed-training-job/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-diagnose-job-launch-rejection/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-diagnose-unhealthy-inference/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-discover-launchable-resources/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-launch-training-job/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-list-running-jobs/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-resume-notebook-and-set-autoshutdown/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-stage-s3-to-nfs/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-transfer-compute-node/SKILL.md +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/scripts/build_plugin.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/scripts/demo_multi_workspace.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/scripts/gen_body_schemas.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/scripts/gen_plugin.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/scripts/mcp_call.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/scripts/serve_stdio.sh +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/scripts/serve_stdio_dryrun.sh +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/__main__.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/_status.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/auth.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/bodyspec.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/client.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/client_setup.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/config.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/errors.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/formatting.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/job_hooks.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/jsonc.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/native_plugins.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/overview.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/resource_refs.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/server.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/setup_conflicts.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/setup_verification.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/spec/body_schemas.json +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/spec/openapi.json +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tls.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/__init__.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/allocations.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/async_inference.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/build_image.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/dalle.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/docker_registry.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/inference.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/jobs.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/notebooks.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/queues.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/resources.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/tensorboards.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/tools/workspaces.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/src/mlspace_mcp/workspace_context.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/conftest.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/specutil.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_allocations.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_artifact_sync.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_async_inference.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_auth.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_autofill.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_bodyspec.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_build_image.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_client.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_client_setup.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_configs_shrink.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_credential_pointer.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_dalle.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_data_transfer.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_docker_registry.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_drift.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_dry_run.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_dry_run_fidelity.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_env_file.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_errors.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_false_success.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_formatting.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_init_cli.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_integration.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_job_status.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_jobs.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_log_normalisation.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_mcp_error_contract.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_native_cli_integration.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_native_plugins.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_notebooks.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_onboarding_init.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_onboarding_tls.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_opencode_journey.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_overview.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_plugin_bundle.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_plugin_cli.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_plugin_release.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_preflight.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_queue_scope.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_queues.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_registry.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_resource_refs.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_resources.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_route_confusion.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_secret_disclosure.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_setup_conflicts.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_setup_native_journey.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_setup_ui.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_setup_verification.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_skills.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_tensorboards.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_user_agent.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_workspace_context.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_workspaces.py +0 -0
- {mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/tests/test_wrapper_hardening.py +0 -0
|
@@ -21,7 +21,7 @@ mlspace-plugin setup
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Если пакет уже установлен: `uv tool install --force .`.
|
|
24
|
-
При установке из wheel: `uv tool install /путь/к/mlspace_plugin-0.3.
|
|
24
|
+
При установке из wheel: `uv tool install /путь/к/mlspace_plugin-0.3.4-py3-none-any.whl`.
|
|
25
25
|
Не используйте editable-установку из `/tmp` для постоянной работы.
|
|
26
26
|
Далее команды показаны для PyPI. После установки исходников или wheel через
|
|
27
27
|
`uv tool install` заменяйте `uvx mlspace-plugin@latest` на `mlspace-plugin`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mlspace-plugin
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Cloud.ru MLSpace plugin: MCP server, agent skills and setup
|
|
5
5
|
Project-URL: Repository, https://github.com/noesskeetit/mlspace-plugin
|
|
6
6
|
Project-URL: Issues, https://github.com/noesskeetit/mlspace-plugin/issues
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
`uv lock` и `uv run python scripts/gen_plugin.py`. Обновите примеры имён wheel/ZIP
|
|
8
8
|
в инструкциях, если версия присутствует в имени файла.
|
|
9
9
|
2. Выполните проверки из `CONTRIBUTING.md` на Linux и macOS.
|
|
10
|
-
3. Соберите пакет (в командах ниже замените 0.3.
|
|
10
|
+
3. Соберите пакет (в командах ниже замените 0.3.4 на выпускаемую версию):
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
|
-
uv build --out-dir dist/release-0.3.
|
|
14
|
-
uv run python scripts/build_plugin.py --output dist/plugin-release-0.3.
|
|
13
|
+
uv build --out-dir dist/release-0.3.4
|
|
14
|
+
uv run python scripts/build_plugin.py --output dist/plugin-release-0.3.4
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
4. Просмотрите содержимое wheel, sdist и ZIP. В них не должно быть credentials,
|
|
@@ -23,11 +23,11 @@ uv run python scripts/build_plugin.py --output dist/plugin-release-0.3.3
|
|
|
23
23
|
`UV_PUBLISH_TOKEN` средствами вашего хранилища секретов, не аргументом команды:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
uv publish --trusted-publishing never dist/release-0.3.
|
|
26
|
+
uv publish --trusted-publishing never dist/release-0.3.4/mlspace_plugin-0.3.4-py3-none-any.whl dist/release-0.3.4/mlspace_plugin-0.3.4.tar.gz
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
6. Проверьте версию и SHA256 обоих файлов через PyPI JSON API; проверьте установку
|
|
30
|
-
`uvx --from mlspace-plugin==0.3.
|
|
30
|
+
`uvx --from mlspace-plugin==0.3.4 mlspace-plugin --version` из нового кеша.
|
|
31
31
|
7. Только после проверки точной версии в PyPI обновите рабочую ветку GitHub
|
|
32
32
|
marketplace и создайте тег релиза. До этого проверяйте изменения на отдельной
|
|
33
33
|
release-ветке или локальном Git marketplace: main не должен ссылаться на
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "mlspace",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"description": "Cloud.ru MLSpace: training jobs, inference, notebooks, data transfer, registry and compute catalog — an MCP server plus skills.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Cloud.ru"
|
{mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-diagnose-data-transfer/SKILL.md
RENAMED
|
@@ -23,6 +23,18 @@ carried out any other way.
|
|
|
23
23
|
|
|
24
24
|
## Procedure
|
|
25
25
|
|
|
26
|
+
Final-answer contract:
|
|
27
|
+
- **Status and reported counters**: observations returned by the API.
|
|
28
|
+
- **Failure cause and phase**: use explicit matching log evidence; otherwise unknown.
|
|
29
|
+
- **Data impact**: unknown until an independent source/destination contents check
|
|
30
|
+
establishes it. Even a known cause such as AccessDenied at PutObject does not
|
|
31
|
+
establish whether other writes in the run already happened. A known cause and
|
|
32
|
+
unknown data impact can both be true. Zero counters are not that contents check.
|
|
33
|
+
|
|
34
|
+
The tool's `_mcp_evidence` describes the limits of those observations, not an API
|
|
35
|
+
defect or a separate user-facing warning. Never translate zero counters into
|
|
36
|
+
"the run failed before transfer started" or "nothing was read/written".
|
|
37
|
+
|
|
26
38
|
Goal: determine whether a data transfer truly failed / was partial, and why.
|
|
27
39
|
1. Resolve the transfer: if transfer_id given use it, else data_transfer list_transfers
|
|
28
40
|
and match transfer_name.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Evidence boundaries alongside Data Transfer observations, without new API calls."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
_DATA_IMPACT_GUIDANCE = (
|
|
7
|
+
'In the final diagnosis explicitly state: Data impact is unverified. '
|
|
8
|
+
'Do not claim that the source was read, nothing was written, or destination '
|
|
9
|
+
'data is unchanged without an independent contents check. A failed PutObject '
|
|
10
|
+
'describes that request, not other objects or earlier attempts in the run.'
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _unknown_evidence() -> dict[str, str]:
|
|
15
|
+
return {
|
|
16
|
+
'failure_cause': 'unknown',
|
|
17
|
+
'failure_phase': 'unknown',
|
|
18
|
+
'data_impact': 'unknown',
|
|
19
|
+
'reporting_requirement': _DATA_IMPACT_GUIDANCE,
|
|
20
|
+
'basis': 'Counters/empty logs alone; independent evidence may establish more.',
|
|
21
|
+
'interpretation': (
|
|
22
|
+
'Reported counters (including zero) and absent logs do not prove that '
|
|
23
|
+
'the run stopped before reading/writing or left destination data unchanged.'
|
|
24
|
+
),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def history_evidence(data: Any, values: dict[str, Any]) -> Any:
|
|
29
|
+
"""Preserve history shape and counters; failed/cancelled entries lack a diagnosis."""
|
|
30
|
+
if isinstance(data, list):
|
|
31
|
+
return [history_evidence(row, values) for row in data]
|
|
32
|
+
if isinstance(data, dict):
|
|
33
|
+
if isinstance(data.get('data'), list):
|
|
34
|
+
return {**data, 'data': history_evidence(data['data'], values)}
|
|
35
|
+
if data.get('status') in ('failed', 'cancelled'):
|
|
36
|
+
return {**data, '_mcp_evidence': _unknown_evidence()}
|
|
37
|
+
return data
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def log_evidence(data: Any, values: dict[str, Any]) -> Any:
|
|
41
|
+
"""Keep log evidence intact; individual events are not a destination-data audit."""
|
|
42
|
+
empty = data is None or data == [] or data == {} or data == ''
|
|
43
|
+
recognized = isinstance(data, list)
|
|
44
|
+
if isinstance(data, dict) and data:
|
|
45
|
+
collections = {'items', 'data', 'events', 'logs'}
|
|
46
|
+
metadata = {'count', 'total_count', 'offset', 'limit', 'page', 'page_size', 'status'}
|
|
47
|
+
present = collections & data.keys()
|
|
48
|
+
recognized = any(isinstance(data[key], list) for key in present)
|
|
49
|
+
empty = bool(present) and all(data[key] == [] for key in present) and (
|
|
50
|
+
not (data.keys() - collections - metadata)
|
|
51
|
+
) and all(not data.get(key) for key in ('count', 'total_count'))
|
|
52
|
+
if empty:
|
|
53
|
+
return {'result': data, '_mcp_evidence': _unknown_evidence()}
|
|
54
|
+
if recognized:
|
|
55
|
+
evidence = {
|
|
56
|
+
'data_impact': 'unknown',
|
|
57
|
+
'reporting_requirement': _DATA_IMPACT_GUIDANCE,
|
|
58
|
+
'basis': 'Logs describe individual events, not a complete source/destination audit.',
|
|
59
|
+
'interpretation': (
|
|
60
|
+
'Use explicit log messages to identify cause and failure phase. '
|
|
61
|
+
'Report data impact separately as unknown until source/destination contents '
|
|
62
|
+
'are independently checked. Even AccessDenied and all-zero counters do not '
|
|
63
|
+
'establish that this entire run left destination data unchanged.'
|
|
64
|
+
),
|
|
65
|
+
}
|
|
66
|
+
result = data if isinstance(data, dict) else {'result': data}
|
|
67
|
+
return {**result, '_mcp_evidence': evidence}
|
|
68
|
+
return data
|
|
@@ -9,6 +9,7 @@ import re
|
|
|
9
9
|
import stat
|
|
10
10
|
import sys
|
|
11
11
|
import tempfile
|
|
12
|
+
from contextlib import nullcontext
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any
|
|
14
15
|
from urllib.parse import urlsplit
|
|
@@ -35,7 +36,7 @@ from .config import (
|
|
|
35
36
|
user_config_file,
|
|
36
37
|
)
|
|
37
38
|
from .setup_conflicts import Resolutions, choose_resolutions
|
|
38
|
-
from .setup_ui import choose_workspaces, display, masked_prompt
|
|
39
|
+
from .setup_ui import choose_workspaces, display, hidden_input, masked_prompt
|
|
39
40
|
from .setup_verification import verify_client
|
|
40
41
|
from .tls import safe_error as _safe_error
|
|
41
42
|
from .tls import tls_context
|
|
@@ -44,14 +45,15 @@ HEADER = f'# MLSpace credentials, written by mlspace-plugin setup.\n{LITERAL_DOT
|
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def _ask(label: str, secret: bool = True, hidden: bool = True) -> str:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
with hidden_input() if hidden else nullcontext():
|
|
49
|
+
while True:
|
|
50
|
+
value = masked_prompt(f' {label}: ') if hidden else input(f' {label}: ')
|
|
51
|
+
if re.match(r'^\s*(?:export\s+)?MLSPACE_\w+\s*=', value):
|
|
52
|
+
print('Вставьте только значение ключа: без MLSPACE_...=, export и кавычек вокруг значения.', file=sys.stderr)
|
|
53
|
+
elif value.strip():
|
|
54
|
+
return value if secret else value.strip()
|
|
55
|
+
else:
|
|
56
|
+
print('Поле обязательно. Вставьте значение ключа; звёздочки подтверждают ввод и скрывают его содержимое.', file=sys.stderr)
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
def _resolve_base_url(target: Path, override: str | None) -> str:
|
|
@@ -315,14 +317,15 @@ def _run_init(path: Path | None, **options: Any) -> int:
|
|
|
315
317
|
else:
|
|
316
318
|
source = {}
|
|
317
319
|
keys = {}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
320
|
+
with hidden_input():
|
|
321
|
+
for name, label in [('CLIENT_ID', 'Cloud.ru Key ID'), ('CLIENT_SECRET', 'Cloud.ru Key Secret')]:
|
|
322
|
+
value = source.get('MLSPACE_' + name)
|
|
323
|
+
if not value or not value.strip():
|
|
324
|
+
if non_interactive or options['credentials_file'] or options['from_env']:
|
|
325
|
+
raise ValueError('Для этого адреса API отсутствуют ключи. Введите их через setup в терминале '
|
|
326
|
+
'либо явно укажите --from-env или --credentials-file.')
|
|
327
|
+
value = _ask(label, secret=name == 'CLIENT_SECRET')
|
|
328
|
+
keys[name] = value
|
|
326
329
|
ca = str(options['ca_file'] or saved.get('MLSPACE_CA_FILE') or os.environ.get('SSL_CERT_FILE') or '')
|
|
327
330
|
ca = str(Path(ca).expanduser().absolute()) if ca else ''
|
|
328
331
|
if not listing:
|
|
@@ -25,6 +25,7 @@ from pydantic import Field
|
|
|
25
25
|
|
|
26
26
|
from . import bodyspec, formatting
|
|
27
27
|
from .config import Settings
|
|
28
|
+
from .data_transfer_hooks import history_evidence, log_evidence
|
|
28
29
|
from .errors import MLSpaceError
|
|
29
30
|
from .job_hooks import (
|
|
30
31
|
guard_job_delete,
|
|
@@ -322,6 +323,10 @@ PREFLIGHT[("POST", "/public/v2/jobs")] = preflight_job_run
|
|
|
322
323
|
PREFLIGHT[("GET", "/public/v2/jobs")] = preflight_jobs_list
|
|
323
324
|
GUARDS[("DELETE", "/public/v2/jobs/{job_name}")] = guard_job_delete
|
|
324
325
|
POSTPROCESS[("DELETE", "/public/v2/jobs/{job_name}")] = postprocess_job_delete
|
|
326
|
+
POSTPROCESS[("GET", "/public/v2/data_transfer/v3/history")] = history_evidence
|
|
327
|
+
POSTPROCESS[("GET", "/public/v2/data_transfer/v3/history/status/stream")] = history_evidence
|
|
328
|
+
POSTPROCESS[("GET", "/public/v2/data_transfer/v2/events/list")] = log_evidence
|
|
329
|
+
POSTPROCESS[("GET", "/public/v2/data_transfer/v3/connectors/{connector_type}/{id_}/try/logs")] = log_evidence
|
|
325
330
|
|
|
326
331
|
|
|
327
332
|
def _postprocess_delete_connectors(data: Any, values: dict[str, Any]) -> Any:
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from contextlib import closing, contextmanager
|
|
5
7
|
from dataclasses import dataclass, field
|
|
6
8
|
from typing import Any
|
|
7
9
|
|
|
8
10
|
from prompt_toolkit import Application, PromptSession
|
|
11
|
+
from prompt_toolkit.input import create_input
|
|
9
12
|
from prompt_toolkit.key_binding import KeyBindings
|
|
10
13
|
from prompt_toolkit.layout import HSplit, Layout, Window
|
|
11
14
|
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
@@ -24,6 +27,21 @@ def masked_prompt(label: str, **kwargs: Any) -> str:
|
|
|
24
27
|
**kwargs).prompt(label))
|
|
25
28
|
|
|
26
29
|
|
|
30
|
+
@contextmanager
|
|
31
|
+
def hidden_input() -> Iterator[None]:
|
|
32
|
+
"""Keep type-ahead hidden between credential prompts, restoring the TTY on exit.
|
|
33
|
+
|
|
34
|
+
PromptSession restores its caller's terminal mode after each prompt. Keep an
|
|
35
|
+
outer raw mode across validation/retries and both credentials so it restores
|
|
36
|
+
to a non-echoing mode, not the shell's echoing mode. Nested scopes are safe.
|
|
37
|
+
"""
|
|
38
|
+
if not sys.stdin.isatty():
|
|
39
|
+
yield
|
|
40
|
+
return
|
|
41
|
+
with closing(create_input()) as terminal, terminal.raw_mode():
|
|
42
|
+
yield
|
|
43
|
+
|
|
44
|
+
|
|
27
45
|
@dataclass
|
|
28
46
|
class WorkspaceSelection:
|
|
29
47
|
rows: list[dict[str, str]]
|
|
@@ -184,6 +184,9 @@ ACTIONS: dict[str, Op] = {
|
|
|
184
184
|
required=("offset", "limit"),
|
|
185
185
|
kind="list",
|
|
186
186
|
help="Get transfer event logs (needs offset+limit and either history_id or transfer_id). "
|
|
187
|
+
"In a diagnosis, report cause and data impact separately. Explicitly state "
|
|
188
|
+
"that data impact is unverified unless source/destination contents were checked; "
|
|
189
|
+
"AccessDenied or zero counters cannot prove that nothing was written. "
|
|
187
190
|
"If empty, cross-check run status and connector logs; do not infer success "
|
|
188
191
|
"or a specific cause from absent events. Rerunning changes state; never "
|
|
189
192
|
"recommend a rerun as a read-only diagnostic check.",
|
|
@@ -271,8 +274,13 @@ DOMAIN = DomainTool(
|
|
|
271
274
|
name="mlspace_data_transfer",
|
|
272
275
|
title="MLSpace Data Transfer",
|
|
273
276
|
summary=(
|
|
274
|
-
"
|
|
275
|
-
"
|
|
277
|
+
"Inspect connectors, transfers, run history and logs; manage transfers and user connectors. "
|
|
278
|
+
"Capability boundary: creating connectors is supported ONLY for postgresql, mssql, mysql, "
|
|
279
|
+
"clickhouse, oracledb, s3amazon, s3custom, s3google. Existing system connectors "
|
|
280
|
+
"(NFS, S3 MLSpace/Evolution/DataHub, HDFS) can be inspected and used by transfers, "
|
|
281
|
+
"but this tool cannot create them. Do not describe source discovery as creation support. "
|
|
282
|
+
"For a service overview, briefly describe connectors, transfer configurations, and "
|
|
283
|
+
"run history/logs. Diagnostic caveats belong in an actual investigation. "
|
|
276
284
|
"For failed-run diagnosis, use the mlspace-diagnose-data-transfer skill when available."
|
|
277
285
|
),
|
|
278
286
|
actions=ACTIONS,
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""User-visible regressions found by the OpenCode acceptance journey."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import select
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
import pytest
|
|
13
|
+
import respx
|
|
14
|
+
from conftest import AUTH_URL, BASE_URL, make_settings, runtime_ctx
|
|
15
|
+
|
|
16
|
+
from mlspace_mcp.registry import _build_handler, available_actions
|
|
17
|
+
from mlspace_mcp.tools.data_transfer import DOMAIN
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.parametrize('invalid', [b'\r', b'MLSPACE_CLIENT_SECRET=wrong\r'])
|
|
21
|
+
@pytest.mark.parametrize('cancel', [False, True])
|
|
22
|
+
def test_early_secret_input_is_masked_during_retry_and_echo_is_restored(invalid, cancel):
|
|
23
|
+
master, slave = os.openpty()
|
|
24
|
+
code = '''
|
|
25
|
+
import termios
|
|
26
|
+
from mlspace_mcp.init_cli import _ask
|
|
27
|
+
before = termios.tcgetattr(0)
|
|
28
|
+
try:
|
|
29
|
+
assert _ask('Secret') == 'SYNTHETIC-EARLY-PASTE'
|
|
30
|
+
print('ACCEPTED')
|
|
31
|
+
except KeyboardInterrupt:
|
|
32
|
+
print('CANCELLED')
|
|
33
|
+
finally:
|
|
34
|
+
after = termios.tcgetattr(0)
|
|
35
|
+
# macOS sets PENDIN itself when canonical input is restored.
|
|
36
|
+
before[3] &= ~getattr(termios, 'PENDIN', 0)
|
|
37
|
+
after[3] &= ~getattr(termios, 'PENDIN', 0)
|
|
38
|
+
assert after == before
|
|
39
|
+
print('TTY_RESTORED')
|
|
40
|
+
'''
|
|
41
|
+
process = subprocess.Popen([sys.executable, '-c', code], stdin=slave, stdout=slave,
|
|
42
|
+
stderr=slave, env=dict(os.environ, TERM='xterm',
|
|
43
|
+
PROMPT_TOOLKIT_NO_CPR='1'))
|
|
44
|
+
os.close(slave)
|
|
45
|
+
transcript = b''
|
|
46
|
+
stage = 0
|
|
47
|
+
try:
|
|
48
|
+
deadline = time.monotonic() + 12
|
|
49
|
+
while time.monotonic() < deadline:
|
|
50
|
+
if select.select([master], [], [], 0.05)[0]:
|
|
51
|
+
try:
|
|
52
|
+
chunk = os.read(master, 65536)
|
|
53
|
+
except OSError:
|
|
54
|
+
break
|
|
55
|
+
transcript += chunk
|
|
56
|
+
if stage == 0 and b'Secret:' in transcript:
|
|
57
|
+
os.write(master, invalid)
|
|
58
|
+
stage = 1
|
|
59
|
+
if stage == 1 and any(x.encode() in transcript for x in
|
|
60
|
+
('Поле обязательно', 'Вставьте только значение')):
|
|
61
|
+
# Type ahead immediately, before the next PromptSession is ready.
|
|
62
|
+
os.write(master, b'\x03' if cancel else
|
|
63
|
+
b'\x1b[200~SYNTHETIC-EARLY-PASTE\x1b[201~\r')
|
|
64
|
+
stage = 2
|
|
65
|
+
if process.poll() is not None:
|
|
66
|
+
break
|
|
67
|
+
assert process.wait(timeout=2) == 0, transcript.decode(errors="replace")
|
|
68
|
+
assert b'TTY_RESTORED' in transcript
|
|
69
|
+
assert (b'CANCELLED' if cancel else b'ACCEPTED') in transcript
|
|
70
|
+
assert b'SYNTHETIC-EARLY-PASTE' not in transcript
|
|
71
|
+
finally:
|
|
72
|
+
if process.poll() is None:
|
|
73
|
+
process.kill()
|
|
74
|
+
process.wait()
|
|
75
|
+
os.close(master)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.parametrize('action,body,kwargs', [
|
|
79
|
+
('list_history', {'data': [{'history_id': 'h', 'status': 'failed', 'size_bytes': 0,
|
|
80
|
+
'rows_count': 0, 'progress': 0}], 'total_count': 1}, {}),
|
|
81
|
+
('list_history', {'data': [{'history_id': 'h', 'status': 'failed', 'size_bytes': 512}],
|
|
82
|
+
'total_count': 1}, {}),
|
|
83
|
+
('get_history_status', [{'history_id': 'h', 'status': 'cancelled'}], {'ids': ['h']}),
|
|
84
|
+
])
|
|
85
|
+
async def test_failed_run_returns_explicit_evidence_limits_without_changing_observations(action, body, kwargs):
|
|
86
|
+
handler = _build_handler(DOMAIN, available_actions(DOMAIN, True), make_settings())
|
|
87
|
+
with respx.mock as mock:
|
|
88
|
+
mock.post(AUTH_URL).respond(200, json={'token': {'access_token': 't'}})
|
|
89
|
+
mock.get(BASE_URL + DOMAIN.actions[action].path).respond(200, json=body)
|
|
90
|
+
async with httpx.AsyncClient() as http:
|
|
91
|
+
result = json.loads(await handler(action=action, ctx=runtime_ctx(http), **kwargs))
|
|
92
|
+
original_row = body['data'][0] if isinstance(body, dict) else body[0]
|
|
93
|
+
row = result['data'][0] if isinstance(result, dict) else result[0]
|
|
94
|
+
assert {k: row[k] for k in original_row} == original_row
|
|
95
|
+
assert row['_mcp_evidence']['data_impact'] == 'unknown'
|
|
96
|
+
assert row['_mcp_evidence']['failure_phase'] == 'unknown'
|
|
97
|
+
assert 'Counters/empty logs alone' in row['_mcp_evidence']['basis']
|
|
98
|
+
if isinstance(body, dict):
|
|
99
|
+
assert result['total_count'] == body['total_count']
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.parametrize('action,kwargs', [
|
|
103
|
+
('get_event_logs', {'history_id': 'h', 'offset': 0, 'limit': 20}),
|
|
104
|
+
('get_connector_logs', {'connector_type': 's3custom', 'id_': 'c'}),
|
|
105
|
+
])
|
|
106
|
+
async def test_empty_diagnostic_logs_do_not_imply_no_data_changes(action, kwargs):
|
|
107
|
+
handler = _build_handler(DOMAIN, available_actions(DOMAIN, True), make_settings())
|
|
108
|
+
with respx.mock as mock:
|
|
109
|
+
mock.post(AUTH_URL).respond(200, json={'token': {'access_token': 't'}})
|
|
110
|
+
mock.get(url__regex=BASE_URL + '/public/v2/data_transfer/.*').respond(200, json=[])
|
|
111
|
+
async with httpx.AsyncClient() as http:
|
|
112
|
+
result = json.loads(await handler(action=action, ctx=runtime_ctx(http), **kwargs))
|
|
113
|
+
assert result['result'] == []
|
|
114
|
+
assert result['_mcp_evidence']['data_impact'] == 'unknown'
|
|
115
|
+
assert result['_mcp_evidence']['failure_phase'] == 'unknown'
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize('payload', [
|
|
119
|
+
{'data': [], 'total_count': 0},
|
|
120
|
+
{'data': [{'history_id': 'ok', 'status': 'success', 'size_bytes': 42}], 'total_count': 1},
|
|
121
|
+
{'unexpected': [{'status': 'failed'}]},
|
|
122
|
+
'unexpected upstream text',
|
|
123
|
+
])
|
|
124
|
+
def test_history_evidence_leaves_unrelated_shapes_and_success_unchanged(payload):
|
|
125
|
+
from copy import deepcopy
|
|
126
|
+
|
|
127
|
+
from mlspace_mcp.data_transfer_hooks import history_evidence
|
|
128
|
+
before = deepcopy(payload)
|
|
129
|
+
assert history_evidence(payload, {}) == before
|
|
130
|
+
assert payload == before
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.mark.parametrize('payload', [
|
|
134
|
+
{'items': [{'message': 'Access denied'}], 'count': 1},
|
|
135
|
+
{'items': [], 'error_message': 'Access denied'},
|
|
136
|
+
{'items': [], 'total_count': 4},
|
|
137
|
+
'Access denied',
|
|
138
|
+
['Access denied'],
|
|
139
|
+
{'status': 'failed', 'logs': ['Access denied']},
|
|
140
|
+
])
|
|
141
|
+
def test_log_evidence_preserves_real_errors_and_nonempty_or_unknown_shapes(payload):
|
|
142
|
+
from mlspace_mcp.data_transfer_hooks import log_evidence
|
|
143
|
+
result = log_evidence(payload, {})
|
|
144
|
+
if isinstance(payload, dict):
|
|
145
|
+
assert {key: result[key] for key in payload} == payload
|
|
146
|
+
assert result["_mcp_evidence"]["data_impact"] == "unknown"
|
|
147
|
+
assert "failure_cause" not in result["_mcp_evidence"]
|
|
148
|
+
elif isinstance(payload, list):
|
|
149
|
+
assert result["result"] == payload
|
|
150
|
+
assert result["_mcp_evidence"]["data_impact"] == "unknown"
|
|
151
|
+
else:
|
|
152
|
+
assert result == payload
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.mark.parametrize('payload', [None, '', {}, {'items': [], 'count': 0},
|
|
156
|
+
{'status': 'failed', 'logs': []}])
|
|
157
|
+
def test_log_evidence_handles_other_empty_shapes(payload):
|
|
158
|
+
from mlspace_mcp.data_transfer_hooks import log_evidence
|
|
159
|
+
result = log_evidence(payload, {})
|
|
160
|
+
assert result['result'] == payload
|
|
161
|
+
assert result['_mcp_evidence']['data_impact'] == 'unknown'
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def test_known_error_log_preserves_cause_but_does_not_certify_data_impact():
|
|
165
|
+
body = {'items': [{'last_log': 'Destination returned AccessDenied during PutObject.'}], 'count': 1}
|
|
166
|
+
handler = _build_handler(DOMAIN, available_actions(DOMAIN, True), make_settings())
|
|
167
|
+
with respx.mock as mock:
|
|
168
|
+
mock.post(AUTH_URL).respond(200, json={'token': {'access_token': 't'}})
|
|
169
|
+
mock.get(BASE_URL + DOMAIN.actions['get_event_logs'].path).respond(200, json=body)
|
|
170
|
+
async with httpx.AsyncClient() as http:
|
|
171
|
+
result = json.loads(await handler(action='get_event_logs', history_id='h', offset=0,
|
|
172
|
+
limit=20, ctx=runtime_ctx(http)))
|
|
173
|
+
assert result['items'] == body['items']
|
|
174
|
+
assert result['count'] == 1
|
|
175
|
+
assert result['_mcp_evidence']['data_impact'] == 'unknown'
|
|
176
|
+
assert 'failure_cause' not in result['_mcp_evidence']
|
|
177
|
+
assert 'failure_phase' not in result['_mcp_evidence']
|
|
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
|
{mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-launch-training-job/SKILL.md
RENAMED
|
File without changes
|
{mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-list-running-jobs/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-stage-s3-to-nfs/SKILL.md
RENAMED
|
File without changes
|
{mlspace_plugin-0.3.3 → mlspace_plugin-0.3.4}/plugin/skills/mlspace-transfer-compute-node/SKILL.md
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
|
|
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
|