adagio-server 0.1.0a1__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.
- adagio_server-0.1.0a1/.gitignore +130 -0
- adagio_server-0.1.0a1/LICENSE +21 -0
- adagio_server-0.1.0a1/PKG-INFO +102 -0
- adagio_server-0.1.0a1/README.md +75 -0
- adagio_server-0.1.0a1/RELEASING.md +110 -0
- adagio_server-0.1.0a1/adagio_server/__init__.py +8 -0
- adagio_server-0.1.0a1/adagio_server/agent/__init__.py +1 -0
- adagio_server-0.1.0a1/adagio_server/agent/artifacts.py +114 -0
- adagio_server-0.1.0a1/adagio_server/agent/client.py +305 -0
- adagio_server-0.1.0a1/adagio_server/agent/identity.py +10 -0
- adagio_server-0.1.0a1/adagio_server/agent/queues.py +52 -0
- adagio_server-0.1.0a1/adagio_server/agent/runner.py +340 -0
- adagio_server-0.1.0a1/adagio_server/agent/supervisor.py +846 -0
- adagio_server-0.1.0a1/adagio_server/api/__init__.py +0 -0
- adagio_server-0.1.0a1/adagio_server/api/api.py +20 -0
- adagio_server-0.1.0a1/adagio_server/api/endpoints/__init__.py +1 -0
- adagio_server-0.1.0a1/adagio_server/api/endpoints/loopback.py +83 -0
- adagio_server-0.1.0a1/adagio_server/cli.py +295 -0
- adagio_server-0.1.0a1/adagio_server/contracts/__init__.py +1 -0
- adagio_server-0.1.0a1/adagio_server/contracts/agent.py +12 -0
- adagio_server-0.1.0a1/adagio_server/core/__init__.py +1 -0
- adagio_server-0.1.0a1/adagio_server/core/config.py +110 -0
- adagio_server-0.1.0a1/adagio_server/main.py +118 -0
- adagio_server-0.1.0a1/adagio_server/services/__init__.py +0 -0
- adagio_server-0.1.0a1/adagio_server/services/timestamps.py +8 -0
- adagio_server-0.1.0a1/pyproject.toml +131 -0
- adagio_server-0.1.0a1/tests/conftest.py +153 -0
- adagio_server-0.1.0a1/tests/test_artifacts.py +43 -0
- adagio_server-0.1.0a1/tests/test_cli.py +223 -0
- adagio_server-0.1.0a1/tests/test_client.py +273 -0
- adagio_server-0.1.0a1/tests/test_loopback.py +101 -0
- adagio_server-0.1.0a1/tests/test_main.py +77 -0
- adagio_server-0.1.0a1/tests/test_runner.py +288 -0
- adagio_server-0.1.0a1/tests/test_supervisor.py +908 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
# lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
.DS_STORE
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
.python-version
|
|
87
|
+
|
|
88
|
+
# pipenv
|
|
89
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
90
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
91
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
92
|
+
# install all needed dependencies.
|
|
93
|
+
#Pipfile.lock
|
|
94
|
+
|
|
95
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
96
|
+
__pypackages__/
|
|
97
|
+
|
|
98
|
+
# Celery stuff
|
|
99
|
+
celerybeat-schedule
|
|
100
|
+
celerybeat.pid
|
|
101
|
+
|
|
102
|
+
# SageMath parsed files
|
|
103
|
+
*.sage.py
|
|
104
|
+
|
|
105
|
+
# Environments
|
|
106
|
+
.env
|
|
107
|
+
.venv
|
|
108
|
+
env/
|
|
109
|
+
venv/
|
|
110
|
+
ENV/
|
|
111
|
+
env.bak/
|
|
112
|
+
venv.bak/
|
|
113
|
+
|
|
114
|
+
# Spyder project settings
|
|
115
|
+
.spyderproject
|
|
116
|
+
.spyproject
|
|
117
|
+
|
|
118
|
+
# Rope project settings
|
|
119
|
+
.ropeproject
|
|
120
|
+
|
|
121
|
+
# mkdocs documentation
|
|
122
|
+
/site
|
|
123
|
+
|
|
124
|
+
# mypy
|
|
125
|
+
.mypy_cache/
|
|
126
|
+
.dmypy.json
|
|
127
|
+
dmypy.json
|
|
128
|
+
|
|
129
|
+
# Pyre type checker
|
|
130
|
+
.pyre/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cymis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: adagio-server
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Outbound self-hosted runtime server for Adagio
|
|
5
|
+
Project-URL: Homepage, https://adagio.run
|
|
6
|
+
Project-URL: Repository, https://github.com/cymis/adagio-local
|
|
7
|
+
Author-email: John Chase <johnhchase@protonmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: adagio,pipelines,runner,workflow
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: adagio-cli<0.2,>=0.1.0a10
|
|
19
|
+
Requires-Dist: adapter-schemas<0.4,>=0.3.1
|
|
20
|
+
Requires-Dist: fastapi[standard]<0.117,>=0.116
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.0.2
|
|
23
|
+
Requires-Dist: pydantic>=2.7
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: tomli-w>=1.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# adagio-server
|
|
29
|
+
|
|
30
|
+
Licensed under the [MIT License](LICENSE). This covers the server package only;
|
|
31
|
+
it does not change the license of the hosted Adagio application or sibling projects.
|
|
32
|
+
|
|
33
|
+
`adagio-server` is Adagio's outbound execution runner. It claims jobs over HTTPS,
|
|
34
|
+
executes them with the existing `adagio-cli`, and reports status and artifact paths.
|
|
35
|
+
It never requires an inbound network port. The initial server platform is Linux
|
|
36
|
+
with Python 3.11+ and user-level systemd; Docker is needed for container-backed
|
|
37
|
+
pipeline actions.
|
|
38
|
+
|
|
39
|
+
Each claimed job starts independently, even while other jobs are running. Adagio
|
|
40
|
+
does not impose a job-count limit or decide whether the host has spare CPU or
|
|
41
|
+
memory; the server owner manages resource usage. The outbound handoff is not a
|
|
42
|
+
resource scheduler. Slurm/SGE integration is separate future work.
|
|
43
|
+
|
|
44
|
+
For the QA prerelease, after `0.1.0a1` is published to PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python3 -m venv ~/.local/share/adagio-server-venv
|
|
48
|
+
source ~/.local/share/adagio-server-venv/bin/activate
|
|
49
|
+
python -m pip install "adagio-server==0.1.0a1"
|
|
50
|
+
adagio-server --version
|
|
51
|
+
|
|
52
|
+
export ADAGIO_ENROLLMENT_TOKEN='<code from Adagio>'
|
|
53
|
+
adagio-server install --url https://app.dev.adagio.run
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The install command exchanges the short-lived token for a machine credential,
|
|
57
|
+
stores that credential with user-only permissions, then installs and starts the
|
|
58
|
+
systemd user service. Jobs execute with the same Unix filesystem permissions as
|
|
59
|
+
the user who installed it, including supplementary groups used for shared data.
|
|
60
|
+
Docker-backed actions also run as that user. Images that require root during
|
|
61
|
+
startup are therefore unsupported on a Runtime Server.
|
|
62
|
+
Granting someone permission to use a server grants code execution as its Linux
|
|
63
|
+
account. An account with access to a rootful Docker daemon can administer the
|
|
64
|
+
host; this is a trusted execution environment, not a sandbox for untrusted users
|
|
65
|
+
or plugins. Supplementary groups are preserved for shared-data access. Do not
|
|
66
|
+
assume UID flags or dropped container capabilities isolate a Docker socket made
|
|
67
|
+
accessible through mounted paths. Docker socket isolation needs separate review.
|
|
68
|
+
The command prints a note when an administrator may need to enable
|
|
69
|
+
systemd user lingering for service operation after logout.
|
|
70
|
+
|
|
71
|
+
Install the package in a persistent virtual environment (or with `pipx`) rather
|
|
72
|
+
than an ephemeral `uvx` environment. The systemd unit records the interpreter
|
|
73
|
+
path that performed the installation.
|
|
74
|
+
|
|
75
|
+
New installations select a stable loopback port from the Linux user id so
|
|
76
|
+
different users on a shared host do not contend for port 8760. Pass
|
|
77
|
+
`--port 23456` to choose an explicit port. The listener remains bound to
|
|
78
|
+
`127.0.0.1` and requires a per-process bearer token.
|
|
79
|
+
|
|
80
|
+
For an interactive foreground process, use `adagio-server start`. Plain HTTP and
|
|
81
|
+
disabled TLS verification are refused unless `install --insecure` is passed for a
|
|
82
|
+
development deployment.
|
|
83
|
+
|
|
84
|
+
## Updates and reporting
|
|
85
|
+
|
|
86
|
+
Contract mismatches stop with exit code 42 and an update-required message;
|
|
87
|
+
credential rejection uses exit code 41. Neither condition should restart the
|
|
88
|
+
same configuration repeatedly. After upgrading an existing installation, rerun
|
|
89
|
+
`adagio-server install` with a fresh enrollment token to update its systemd unit
|
|
90
|
+
(older units only suppress restarts for code 41).
|
|
91
|
+
|
|
92
|
+
Telemetry uploads have bounded text, metadata, and batch sizes. Large logs are
|
|
93
|
+
sent in small batches with continued long lines; full logs remain on the server.
|
|
94
|
+
Oversized optional event/provenance data is replaced by a `telemetry_truncated`
|
|
95
|
+
marker rather than losing the job's lifecycle report. These limits do not apply
|
|
96
|
+
to input/output file sizes or job counts.
|
|
97
|
+
|
|
98
|
+
This revision requires the adapter-schemas 0.3.1 release candidate. Source builds
|
|
99
|
+
use the exact commit recorded in `vendor/adapter-schemas/VENDOR_PIN`; publication
|
|
100
|
+
and release-tag verification must be completed before package-index installation.
|
|
101
|
+
|
|
102
|
+
Release operators and QA testers should follow `RELEASING.md` in the repository.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# adagio-server
|
|
2
|
+
|
|
3
|
+
Licensed under the [MIT License](LICENSE). This covers the server package only;
|
|
4
|
+
it does not change the license of the hosted Adagio application or sibling projects.
|
|
5
|
+
|
|
6
|
+
`adagio-server` is Adagio's outbound execution runner. It claims jobs over HTTPS,
|
|
7
|
+
executes them with the existing `adagio-cli`, and reports status and artifact paths.
|
|
8
|
+
It never requires an inbound network port. The initial server platform is Linux
|
|
9
|
+
with Python 3.11+ and user-level systemd; Docker is needed for container-backed
|
|
10
|
+
pipeline actions.
|
|
11
|
+
|
|
12
|
+
Each claimed job starts independently, even while other jobs are running. Adagio
|
|
13
|
+
does not impose a job-count limit or decide whether the host has spare CPU or
|
|
14
|
+
memory; the server owner manages resource usage. The outbound handoff is not a
|
|
15
|
+
resource scheduler. Slurm/SGE integration is separate future work.
|
|
16
|
+
|
|
17
|
+
For the QA prerelease, after `0.1.0a1` is published to PyPI:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python3 -m venv ~/.local/share/adagio-server-venv
|
|
21
|
+
source ~/.local/share/adagio-server-venv/bin/activate
|
|
22
|
+
python -m pip install "adagio-server==0.1.0a1"
|
|
23
|
+
adagio-server --version
|
|
24
|
+
|
|
25
|
+
export ADAGIO_ENROLLMENT_TOKEN='<code from Adagio>'
|
|
26
|
+
adagio-server install --url https://app.dev.adagio.run
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The install command exchanges the short-lived token for a machine credential,
|
|
30
|
+
stores that credential with user-only permissions, then installs and starts the
|
|
31
|
+
systemd user service. Jobs execute with the same Unix filesystem permissions as
|
|
32
|
+
the user who installed it, including supplementary groups used for shared data.
|
|
33
|
+
Docker-backed actions also run as that user. Images that require root during
|
|
34
|
+
startup are therefore unsupported on a Runtime Server.
|
|
35
|
+
Granting someone permission to use a server grants code execution as its Linux
|
|
36
|
+
account. An account with access to a rootful Docker daemon can administer the
|
|
37
|
+
host; this is a trusted execution environment, not a sandbox for untrusted users
|
|
38
|
+
or plugins. Supplementary groups are preserved for shared-data access. Do not
|
|
39
|
+
assume UID flags or dropped container capabilities isolate a Docker socket made
|
|
40
|
+
accessible through mounted paths. Docker socket isolation needs separate review.
|
|
41
|
+
The command prints a note when an administrator may need to enable
|
|
42
|
+
systemd user lingering for service operation after logout.
|
|
43
|
+
|
|
44
|
+
Install the package in a persistent virtual environment (or with `pipx`) rather
|
|
45
|
+
than an ephemeral `uvx` environment. The systemd unit records the interpreter
|
|
46
|
+
path that performed the installation.
|
|
47
|
+
|
|
48
|
+
New installations select a stable loopback port from the Linux user id so
|
|
49
|
+
different users on a shared host do not contend for port 8760. Pass
|
|
50
|
+
`--port 23456` to choose an explicit port. The listener remains bound to
|
|
51
|
+
`127.0.0.1` and requires a per-process bearer token.
|
|
52
|
+
|
|
53
|
+
For an interactive foreground process, use `adagio-server start`. Plain HTTP and
|
|
54
|
+
disabled TLS verification are refused unless `install --insecure` is passed for a
|
|
55
|
+
development deployment.
|
|
56
|
+
|
|
57
|
+
## Updates and reporting
|
|
58
|
+
|
|
59
|
+
Contract mismatches stop with exit code 42 and an update-required message;
|
|
60
|
+
credential rejection uses exit code 41. Neither condition should restart the
|
|
61
|
+
same configuration repeatedly. After upgrading an existing installation, rerun
|
|
62
|
+
`adagio-server install` with a fresh enrollment token to update its systemd unit
|
|
63
|
+
(older units only suppress restarts for code 41).
|
|
64
|
+
|
|
65
|
+
Telemetry uploads have bounded text, metadata, and batch sizes. Large logs are
|
|
66
|
+
sent in small batches with continued long lines; full logs remain on the server.
|
|
67
|
+
Oversized optional event/provenance data is replaced by a `telemetry_truncated`
|
|
68
|
+
marker rather than losing the job's lifecycle report. These limits do not apply
|
|
69
|
+
to input/output file sizes or job counts.
|
|
70
|
+
|
|
71
|
+
This revision requires the adapter-schemas 0.3.1 release candidate. Source builds
|
|
72
|
+
use the exact commit recorded in `vendor/adapter-schemas/VENDOR_PIN`; publication
|
|
73
|
+
and release-tag verification must be completed before package-index installation.
|
|
74
|
+
|
|
75
|
+
Release operators and QA testers should follow `RELEASING.md` in the repository.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# adagio-server QA releases
|
|
2
|
+
|
|
3
|
+
Planned first QA release: **0.1.0a1**. The source repository remains private;
|
|
4
|
+
the published Python distributions and their included code are public and
|
|
5
|
+
MIT-licensed. This license applies to the server package, not the hosted Adagio
|
|
6
|
+
application or other repositories. Both wheel and source archives include LICENSE.
|
|
7
|
+
|
|
8
|
+
## One-time account setup
|
|
9
|
+
|
|
10
|
+
In https://pypi.org/manage/account/publishing/ add a pending GitHub publisher:
|
|
11
|
+
|
|
12
|
+
| Field | Value |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| PyPI project | `adagio-server` |
|
|
15
|
+
| GitHub owner | `cymis` |
|
|
16
|
+
| Repository | `adagio-local` |
|
|
17
|
+
| Workflow filename | `server-package.yml` |
|
|
18
|
+
| Environment | Leave blank |
|
|
19
|
+
|
|
20
|
+
The approved QA policy uses explicit GitHub release publication, without a GitHub
|
|
21
|
+
environment or a separate reviewer gate. Repository writers can initiate a
|
|
22
|
+
release; access to this workflow must therefore be treated as publishing access.
|
|
23
|
+
No PyPI API-token secret is needed. The package license is MIT.
|
|
24
|
+
|
|
25
|
+
The repository name above identifies where the workflow lives. It does not
|
|
26
|
+
publish `adagio-local`: only the `adagio-server` distributions built from
|
|
27
|
+
`adagio-local-adapter` are uploaded. Desktop and sibling projects are excluded.
|
|
28
|
+
|
|
29
|
+
The `release: published` event runs the workflow from the tagged release commit.
|
|
30
|
+
The tag must include this workflow, but it does not need to be on `main` first.
|
|
31
|
+
This allows QA releases from reviewed `dev` commits without promoting Desktop
|
|
32
|
+
changes to `main` or changing the default branch. Publishing a GitHub release is
|
|
33
|
+
the explicit authorization to publish its package to PyPI after checks pass;
|
|
34
|
+
saving a draft or pushing a tag is not. No PyPI publisher configuration changes
|
|
35
|
+
are needed. Only `server-v*` releases are eligible; Desktop `v*` releases are ignored.
|
|
36
|
+
|
|
37
|
+
## Release gates
|
|
38
|
+
|
|
39
|
+
1. Merge the reviewed runner and packaging changes into `dev` and deploy the
|
|
40
|
+
matching hosted changes to the development environment.
|
|
41
|
+
2. Publish `adapter-schemas==0.3.1` first. Confirm the already released
|
|
42
|
+
`adagio-cli==0.1.0a10` is available, and re-sync vendored contracts to the
|
|
43
|
+
verified release tag. A local `uv sync` does not prove a pip install will work.
|
|
44
|
+
3. Run Server package CI. It builds wheel and source archives with explicit file
|
|
45
|
+
allowlists, validates metadata, and tests installed wheels on Python 3.11/3.13
|
|
46
|
+
with the public CLI. Only pre-publication CI uses a locally built schema wheel.
|
|
47
|
+
4. Push `server-v0.1.0a1` on the reviewed commit. **Do not use `v0.1.0a1`: `v*`
|
|
48
|
+
tags belong to Desktop releases.** Prerelease commits must be in `dev`;
|
|
49
|
+
stable server release commits must be in `main`.
|
|
50
|
+
5. A separate clean job must install the server wheel using only PyPI for its
|
|
51
|
+
dependencies. No candidate schema wheel or private source checkout is available
|
|
52
|
+
in that job. If this fails, publication is blocked.
|
|
53
|
+
6. Explicitly publish the GitHub prerelease with the command below (or publish it
|
|
54
|
+
from the GitHub release UI). Only the publishing job
|
|
55
|
+
receives OIDC permission, after all required checks pass. There is no second
|
|
56
|
+
approval prompt. PR/branch/tag pushes and manual workflow dispatch never
|
|
57
|
+
publish. The `published` event covers both prereleases and stable releases.
|
|
58
|
+
7. Repeat installation from PyPI on a clean Linux/systemd machine and record
|
|
59
|
+
`adagio-server --version` and `python -m pip freeze` in the QA report.
|
|
60
|
+
|
|
61
|
+
Publishing command (do not run until release approval):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
gh release create server-v0.1.0a1 --repo cymis/adagio-local --verify-tag --prerelease --latest=false --title 'adagio-server 0.1.0a1' --notes 'First MIT-licensed adagio-server QA prerelease. Use with the Adagio dev deployment.'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For a build/test dry run, push the reviewed tag without publishing a GitHub
|
|
68
|
+
release. This still checks that dependencies are available from PyPI. A draft
|
|
69
|
+
release does not publish to PyPI. A failed publishing job can be rerun on the
|
|
70
|
+
same release; do not move the tag to different code.
|
|
71
|
+
|
|
72
|
+
Never replace a published artifact or move a released tag. Publish `0.1.0a2`,
|
|
73
|
+
`0.1.0a3`, etc. for subsequent QA builds and update the dev setup snippet's pin.
|
|
74
|
+
Publishing an alpha does not promote the application to `main` or release Desktop.
|
|
75
|
+
|
|
76
|
+
## QA workflow (after publication)
|
|
77
|
+
|
|
78
|
+
Use a dedicated Linux account with Python 3.11+, a working systemd user session,
|
|
79
|
+
and access to the execution environment (for example, Docker). These are host
|
|
80
|
+
prerequisites; pip does not install or configure Docker or systemd.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python3 -m venv ~/.local/share/adagio-server-venv
|
|
84
|
+
source ~/.local/share/adagio-server-venv/bin/activate
|
|
85
|
+
python -m pip install "adagio-server==0.1.0a1"
|
|
86
|
+
adagio-server --version
|
|
87
|
+
adagio-server install --url https://app.dev.adagio.run
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Create a Runtime Server in the dev app and paste its temporary enrollment token
|
|
91
|
+
at the hidden prompt. The same one-time token can instead be supplied through
|
|
92
|
+
`ADAGIO_ENROLLMENT_TOKEN`, as shown in the app. Keep this virtual environment:
|
|
93
|
+
the user service records its interpreter path. Do not use `sudo pip`.
|
|
94
|
+
|
|
95
|
+
Check the install output and the app's green connection indicator, then use
|
|
96
|
+
`systemctl --user status adagio-server` and
|
|
97
|
+
`journalctl --user -u adagio-server -n 100` for troubleshooting. If jobs need to
|
|
98
|
+
continue after logout or reboot, ask the host administrator to enable user lingering
|
|
99
|
+
with `loginctl enable-linger USERNAME` and test that behavior explicitly.
|
|
100
|
+
|
|
101
|
+
Test remote input/metadata and output paths, multiple simultaneous jobs, canceling
|
|
102
|
+
only one job, service stop/restart, interrupted-job reporting, re-enrollment, token
|
|
103
|
+
revocation, large logs, and an ordinary Desktop run against the same dev deployment.
|
|
104
|
+
For upgrades, wait for active work to finish (or explicitly cancel it), install the
|
|
105
|
+
new pinned version in the same environment, and rerun install with a fresh token.
|
|
106
|
+
Existing systemd units need the updated restart policy. Confirm the running process
|
|
107
|
+
actually restarts; pip installation alone does not restart an existing service.
|
|
108
|
+
|
|
109
|
+
Do not include enrollment tokens, machine credentials, or private scientific data
|
|
110
|
+
in QA reports. Record versions, sanitized logs, host OS, and the reproduction steps.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Adagio's outbound self-hosted runtime server."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("adagio-server")
|
|
7
|
+
except PackageNotFoundError: # Source checkout without an installed distribution.
|
|
8
|
+
__version__ = "unknown"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Outbound pull-agent core (register / claim / lease / run / report)."""
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""QIIME ``.qza``/``.qzv`` artifact introspection.
|
|
2
|
+
|
|
3
|
+
This is the **only** domain-aware code in the agent (design §5). It is kept
|
|
4
|
+
verbatim in behavior from the legacy ``jobs.py`` so that when the agent relays a
|
|
5
|
+
CLI ``output_saved`` event to action as an :class:`ArtifactReport`, it can fill in
|
|
6
|
+
``artifact_type`` (the QIIME semantic type) and ``provenance`` (the recorded
|
|
7
|
+
action graph). All parsing is best-effort and degrades gracefully - a file that
|
|
8
|
+
is not a QIIME archive simply yields ``(None, None)``.
|
|
9
|
+
|
|
10
|
+
Everything else in the agent treats artifacts as opaque URIs.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import zipfile
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import yaml
|
|
21
|
+
except Exception: # noqa: BLE001 - yaml is optional; degrade to no introspection
|
|
22
|
+
yaml = None # type: ignore[assignment]
|
|
23
|
+
|
|
24
|
+
#: QIIME repr tags that a plain YAML loader cannot resolve.
|
|
25
|
+
_Q2_REPRS = ("!ref", "!no-provenance", "!metadata", "!color", "!cite")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def read_qiime_archive(file_path: Path) -> tuple[str | None, dict[str, Any] | None]:
|
|
29
|
+
"""Return ``(artifact_type, provenance)`` for a QIIME archive, else ``(None, None)``.
|
|
30
|
+
|
|
31
|
+
``artifact_type`` comes from ``*/metadata.yaml``'s ``type`` field; ``provenance``
|
|
32
|
+
from ``*/provenance/action/action.yaml``. Non-archives and parse failures return
|
|
33
|
+
``(None, None)`` / partial results rather than raising.
|
|
34
|
+
"""
|
|
35
|
+
if not zipfile.is_zipfile(file_path):
|
|
36
|
+
return None, None
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
with zipfile.ZipFile(file_path, "r") as zf:
|
|
40
|
+
metadata = _read_yaml_member(zf, ["/metadata.yaml"])
|
|
41
|
+
provenance = _read_yaml_member(
|
|
42
|
+
zf,
|
|
43
|
+
["/provenance/action/action.yaml", "/action/action.yaml"],
|
|
44
|
+
)
|
|
45
|
+
except Exception: # noqa: BLE001
|
|
46
|
+
return None, None
|
|
47
|
+
|
|
48
|
+
archive_type: str | None = None
|
|
49
|
+
if isinstance(metadata, dict):
|
|
50
|
+
type_name = metadata.get("type")
|
|
51
|
+
if isinstance(type_name, str):
|
|
52
|
+
archive_type = type_name
|
|
53
|
+
|
|
54
|
+
if isinstance(provenance, dict):
|
|
55
|
+
return archive_type, provenance
|
|
56
|
+
return archive_type, None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _read_yaml_member(
|
|
60
|
+
zf: zipfile.ZipFile, suffixes: list[str]
|
|
61
|
+
) -> dict[str, Any] | None:
|
|
62
|
+
names = zf.namelist()
|
|
63
|
+
for suffix in suffixes:
|
|
64
|
+
match = next((name for name in names if name.endswith(suffix)), None)
|
|
65
|
+
if match is None:
|
|
66
|
+
continue
|
|
67
|
+
text = zf.read(match).decode("utf-8", errors="replace")
|
|
68
|
+
parsed = yaml_load_with_qiime_tags(text)
|
|
69
|
+
if isinstance(parsed, dict):
|
|
70
|
+
return parsed
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def yaml_load_with_qiime_tags(text: str) -> Any:
|
|
75
|
+
"""Load YAML tolerating QIIME repr tags; degrade progressively on failure."""
|
|
76
|
+
if yaml is None:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
class _Loader(yaml.SafeLoader):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def _unknown_tag(loader: yaml.SafeLoader, node: yaml.Node) -> Any:
|
|
83
|
+
if isinstance(node, yaml.ScalarNode):
|
|
84
|
+
return loader.construct_scalar(node)
|
|
85
|
+
if isinstance(node, yaml.SequenceNode):
|
|
86
|
+
return loader.construct_sequence(node)
|
|
87
|
+
if isinstance(node, yaml.MappingNode):
|
|
88
|
+
return loader.construct_mapping(node)
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
_Loader.add_constructor(None, _unknown_tag)
|
|
92
|
+
try:
|
|
93
|
+
return yaml.load(text, Loader=_Loader)
|
|
94
|
+
except Exception: # noqa: BLE001
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
stripped = text
|
|
98
|
+
for qrepr in _Q2_REPRS:
|
|
99
|
+
stripped = stripped.replace(qrepr, "")
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
return yaml.safe_load(stripped)
|
|
103
|
+
except Exception: # noqa: BLE001
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
return yaml.load(stripped, Loader=yaml.BaseLoader)
|
|
108
|
+
except Exception: # noqa: BLE001
|
|
109
|
+
return {
|
|
110
|
+
"_parse_warning": (
|
|
111
|
+
"Unable to fully parse QIIME provenance YAML. "
|
|
112
|
+
"Some provenance details were omitted."
|
|
113
|
+
),
|
|
114
|
+
}
|