hyperloop 0.9.0__tar.gz → 0.9.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.
- {hyperloop-0.9.0 → hyperloop-0.9.1}/CHANGELOG.md +8 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/PKG-INFO +1 -1
- {hyperloop-0.9.0 → hyperloop-0.9.1}/pyproject.toml +1 -1
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/matrix_setup.py +41 -40
- {hyperloop-0.9.0 → hyperloop-0.9.1}/uv.lock +1 -1
- {hyperloop-0.9.0 → hyperloop-0.9.1}/.github/workflows/ci.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/.github/workflows/release.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/.gitignore +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/.pre-commit-config.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/.python-version +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/CLAUDE.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/LICENSE +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/README.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/base/implementer.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/base/kustomization.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/base/pm.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/base/process-improver.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/base/process.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/base/rebase-resolver.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/base/verifier.yaml +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/observability.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/prompts/checklist.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/prompts/checks/check_result_file.sh +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/prompts/rules.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/spec.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/tasks/task-001.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/tasks/task-002.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/tasks/task-003.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/tasks/task-004.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/tasks/task-005.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/specs/tasks/task-006.md +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/__init__.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/__main__.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/__init__.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/git_state.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/local.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/matrix_probe.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/probe.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/serial.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/adapters/structlog_probe.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/cli.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/compose.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/config.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/domain/__init__.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/domain/decide.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/domain/deps.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/domain/model.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/domain/pipeline.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/logging.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/loop.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/ports/__init__.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/ports/pr.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/ports/probe.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/ports/runtime.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/ports/serial.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/ports/state.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/src/hyperloop/pr.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/__init__.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/fakes/__init__.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/fakes/pr.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/fakes/probe.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/fakes/runtime.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/fakes/serial.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/fakes/state.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_cli.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_compose.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_config.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_decide.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_deps.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_e2e.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_fakes.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_git_state.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_local_runtime.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_loop.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_matrix_probe.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_model.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_pipeline.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_pr.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_probe.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_serial_agents.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_smoke.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_state_contract.py +0 -0
- {hyperloop-0.9.0 → hyperloop-0.9.1}/tests/test_structlog_probe.py +0 -0
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v0.9.1 (2026-04-15)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **matrix**: Proper UIA two-step registration flow
|
|
10
|
+
([`97080f9`](https://github.com/jsell-rh/hyperloop/commit/97080f9d6980e914451a59d4b2453c7b5294ed30))
|
|
11
|
+
|
|
12
|
+
|
|
5
13
|
## v0.9.0 (2026-04-15)
|
|
6
14
|
|
|
7
15
|
|
|
@@ -11,18 +11,18 @@ auto-setup and cache.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
|
-
import logging
|
|
15
14
|
import secrets
|
|
16
15
|
from typing import TYPE_CHECKING, cast
|
|
17
16
|
|
|
18
17
|
import httpx
|
|
18
|
+
import structlog
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
|
|
23
23
|
from hyperloop.config import MatrixConfig
|
|
24
24
|
|
|
25
|
-
_log =
|
|
25
|
+
_log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
|
26
26
|
|
|
27
27
|
_CACHE_FILE = ".hyperloop/matrix-state.json"
|
|
28
28
|
|
|
@@ -53,6 +53,8 @@ def _ensure_gitignored(repo_path: Path) -> None:
|
|
|
53
53
|
"""Ensure .hyperloop/ is in the target repo's .gitignore."""
|
|
54
54
|
gitignore = repo_path / ".gitignore"
|
|
55
55
|
entry = ".hyperloop/"
|
|
56
|
+
if not repo_path.is_dir():
|
|
57
|
+
return
|
|
56
58
|
if gitignore.is_file():
|
|
57
59
|
content = gitignore.read_text()
|
|
58
60
|
if entry in content.splitlines():
|
|
@@ -104,55 +106,52 @@ def _register_bot(
|
|
|
104
106
|
username: str,
|
|
105
107
|
password: str,
|
|
106
108
|
) -> tuple[str, str]:
|
|
107
|
-
"""Register a bot user. Returns (user_id, access_token).
|
|
109
|
+
"""Register a bot user via UIA flow. Returns (user_id, access_token).
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
Matrix UIA (User-Interactive Authentication) registration:
|
|
112
|
+
1. POST /register without auth → server returns 401 with session + flows.
|
|
113
|
+
2. POST /register with auth (type + token + session) → 200 with credentials.
|
|
114
|
+
|
|
115
|
+
If the user already exists (HTTP 400), falls back to login.
|
|
111
116
|
"""
|
|
112
117
|
url = f"{homeserver}/_matrix/client/v3/register"
|
|
113
|
-
|
|
118
|
+
base_body: dict[str, object] = {
|
|
114
119
|
"username": username,
|
|
115
120
|
"password": password,
|
|
116
|
-
"auth": {
|
|
117
|
-
"type": "m.login.registration_token",
|
|
118
|
-
"token": registration_token,
|
|
119
|
-
},
|
|
120
121
|
"initial_device_display_name": "hyperloop",
|
|
121
122
|
"inhibit_login": False,
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
# Step 1: request without auth to get session
|
|
126
|
+
resp = client.post(url, json=base_body)
|
|
125
127
|
|
|
126
128
|
if resp.status_code == 200:
|
|
129
|
+
# Some servers accept registration without UIA
|
|
127
130
|
data = resp.json()
|
|
128
131
|
return str(data["user_id"]), str(data["access_token"])
|
|
129
132
|
|
|
130
|
-
if resp.status_code == 401:
|
|
131
|
-
# Server requires a different auth flow — try the flows it offers
|
|
132
|
-
data = resp.json()
|
|
133
|
-
flows = data.get("flows", [])
|
|
134
|
-
session = data.get("session", "")
|
|
135
|
-
|
|
136
|
-
# Check if m.login.registration_token is among the available flows
|
|
137
|
-
for flow in flows:
|
|
138
|
-
stages = flow.get("stages", [])
|
|
139
|
-
if "m.login.registration_token" in stages:
|
|
140
|
-
body["auth"] = {
|
|
141
|
-
"type": "m.login.registration_token",
|
|
142
|
-
"token": registration_token,
|
|
143
|
-
"session": session,
|
|
144
|
-
}
|
|
145
|
-
resp = client.post(url, json=body)
|
|
146
|
-
if resp.status_code == 200:
|
|
147
|
-
data = resp.json()
|
|
148
|
-
return str(data["user_id"]), str(data["access_token"])
|
|
149
|
-
|
|
150
133
|
if resp.status_code == 400:
|
|
151
|
-
# User likely already exists — try login
|
|
152
134
|
return _login(client, homeserver, username, password)
|
|
153
135
|
|
|
136
|
+
# Step 2: server should return 401 with session + flows
|
|
137
|
+
if resp.status_code in (401, 403):
|
|
138
|
+
data = resp.json()
|
|
139
|
+
session = data.get("session", "")
|
|
140
|
+
if session:
|
|
141
|
+
base_body["auth"] = {
|
|
142
|
+
"type": "m.login.registration_token",
|
|
143
|
+
"token": registration_token,
|
|
144
|
+
"session": session,
|
|
145
|
+
}
|
|
146
|
+
resp = client.post(url, json=base_body)
|
|
147
|
+
if resp.status_code == 200:
|
|
148
|
+
data = resp.json()
|
|
149
|
+
return str(data["user_id"]), str(data["access_token"])
|
|
150
|
+
if resp.status_code == 400:
|
|
151
|
+
return _login(client, homeserver, username, password)
|
|
152
|
+
|
|
154
153
|
resp.raise_for_status()
|
|
155
|
-
msg = f"
|
|
154
|
+
msg = f"Registration failed: {resp.status_code} {resp.text}"
|
|
156
155
|
raise RuntimeError(msg)
|
|
157
156
|
|
|
158
157
|
|
|
@@ -249,9 +248,9 @@ def ensure_matrix_ready(
|
|
|
249
248
|
config, repo_path, homeserver, registration_token, cache
|
|
250
249
|
)
|
|
251
250
|
except Exception:
|
|
252
|
-
_log.exception("
|
|
251
|
+
_log.exception("matrix_bot_registration_failed")
|
|
253
252
|
else:
|
|
254
|
-
_log.warning("
|
|
253
|
+
_log.warning("matrix_skipped", reason="no access token and no registration token")
|
|
255
254
|
return "", ""
|
|
256
255
|
|
|
257
256
|
if not access_token:
|
|
@@ -262,18 +261,20 @@ def ensure_matrix_ready(
|
|
|
262
261
|
room_id = config.room_id or cached_room
|
|
263
262
|
|
|
264
263
|
if not room_id:
|
|
264
|
+
_log.info("matrix_room_creating")
|
|
265
265
|
try:
|
|
266
266
|
room_id = _auto_create_room(config, repo_path, homeserver, access_token)
|
|
267
267
|
except Exception:
|
|
268
|
-
_log.exception("
|
|
268
|
+
_log.exception("matrix_room_creation_failed")
|
|
269
269
|
return access_token, ""
|
|
270
270
|
|
|
271
271
|
# Ensure the bot has joined the room (it's a new user each run)
|
|
272
|
-
if access_token != explicit_token:
|
|
272
|
+
if access_token != explicit_token and room_id:
|
|
273
|
+
_log.info("matrix_room_joining", room_id=room_id)
|
|
273
274
|
try:
|
|
274
275
|
_join_room(httpx.Client(timeout=10.0), homeserver, access_token, room_id)
|
|
275
276
|
except Exception:
|
|
276
|
-
_log.exception("
|
|
277
|
+
_log.exception("matrix_room_join_failed", room_id=room_id)
|
|
277
278
|
|
|
278
279
|
return access_token, room_id
|
|
279
280
|
|
|
@@ -318,7 +319,7 @@ def _register_disposable_bot(
|
|
|
318
319
|
password=password,
|
|
319
320
|
)
|
|
320
321
|
|
|
321
|
-
_log.info("
|
|
322
|
+
_log.info("matrix_bot_registered", user_id=user_id)
|
|
322
323
|
return access_token
|
|
323
324
|
finally:
|
|
324
325
|
client.close()
|
|
@@ -373,7 +374,7 @@ def _auto_create_room(
|
|
|
373
374
|
password=cache.get("password", ""),
|
|
374
375
|
)
|
|
375
376
|
|
|
376
|
-
_log.info("
|
|
377
|
+
_log.info("matrix_room_created", room_id=room_id)
|
|
377
378
|
return room_id
|
|
378
379
|
finally:
|
|
379
380
|
client.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|