feat: route profiled ops runs through Glas
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02b6f-7db1-7222-918b-e813a6bda38d
This commit is contained in:
parent
68ab94581a
commit
0f01c3e421
17 changed files with 895 additions and 11 deletions
|
|
@ -14,9 +14,9 @@
|
|||
## Developer Workflow
|
||||
|
||||
```bash
|
||||
# Install (editable, pulls in the llm-connect sibling checkout)
|
||||
# Install (editable, including the governed profile-execution siblings)
|
||||
python3 -m venv .venv && source .venv/bin/activate
|
||||
pip install -e . -e ../llm-connect pytest
|
||||
pip install -e . -e ../llm-connect -e ../sand-boxer -e ../glas-harness pytest
|
||||
|
||||
# Test
|
||||
PYTHONPATH=".:$HOME/llm-connect" python3 -m pytest tests/ -q
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -28,6 +28,6 @@ image-export: image ## Save image tar for k3s import
|
|||
|
||||
deploy-rsync: ## Rsync tree to railiance01:~/rein-aharness
|
||||
rsync -az --delete \
|
||||
--exclude '.git' --exclude '__pycache__' --exclude '.pytest_cache' \
|
||||
--exclude '.git' --exclude '.venv' --exclude '__pycache__' --exclude '.pytest_cache' \
|
||||
--exclude 'llm_connect_vendor' --exclude '*.egg-info' \
|
||||
./ railiance01:rein-aharness/
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ rein-aharness claim-loop
|
|||
# Install user service: ./deploy/scripts/install-claim-loop-user.sh
|
||||
# Docs: docs/ops-run-claim-loop.md
|
||||
|
||||
# A queued harness_profile_ref is authoritative: the claim worker builds a
|
||||
# Glas ExecutionRequest and invokes its resolver/gateway. Install the sibling
|
||||
# runtime packages in the claim-loop venv:
|
||||
pip install -e ../sand-boxer -e ../glas-harness
|
||||
|
||||
# Legacy: issue-core tickets (external / Forgejo projection only — not FI/Binky ops)
|
||||
export ISSUE_CORE_URL=http://127.0.0.1:8765 ISSUE_CORE_API_KEY=…
|
||||
rein-aharness poll --source=issue-core
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
| workplan | REIN-A-0001 | finished | — | workplans/REIN-A-0001-statehub-bootstrap.md |
|
||||
| workplan | REIN-A-0002 | finished | — | workplans/REIN-A-0002-ops-run-claim-loop.md |
|
||||
| workplan | REIN-A-0003 | finished | — | workplans/REIN-A-0003-binky-weekly-review-approach.md |
|
||||
| workplan | REIN-A-0004 | active | — | workplans/REIN-A-0004-glas-profiled-ops-runs.md |
|
||||
| task | HARNESS-WP-0001-T01 | done | — | workplans/HARNESS-WP-0001-harness-foundation.md |
|
||||
| task | HARNESS-WP-0001-T02 | done | — | workplans/HARNESS-WP-0001-harness-foundation.md |
|
||||
| task | HARNESS-WP-0001-T03 | done | — | workplans/HARNESS-WP-0001-harness-foundation.md |
|
||||
|
|
@ -36,3 +37,7 @@
|
|||
| task | REIN-A-0003-T01 | done | — | workplans/REIN-A-0003-binky-weekly-review-approach.md |
|
||||
| task | REIN-A-0003-T02 | done | — | workplans/REIN-A-0003-binky-weekly-review-approach.md |
|
||||
| task | REIN-A-0003-T03 | done | — | workplans/REIN-A-0003-binky-weekly-review-approach.md |
|
||||
| task | REIN-A-0004-T01 | done | — | workplans/REIN-A-0004-glas-profiled-ops-runs.md |
|
||||
| task | REIN-A-0004-T02 | done | — | workplans/REIN-A-0004-glas-profiled-ops-runs.md |
|
||||
| task | REIN-A-0004-T03 | done | — | workplans/REIN-A-0004-glas-profiled-ops-runs.md |
|
||||
| task | REIN-A-0004-T04 | progress | — | workplans/REIN-A-0004-glas-profiled-ops-runs.md |
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ fi
|
|||
|
||||
# Ensure package entrypoint exists
|
||||
if [[ ! -x "${ROOT}/.venv/bin/rein-aharness" ]]; then
|
||||
echo "warning: ${ROOT}/.venv/bin/rein-aharness missing; install with: cd ${ROOT} && uv sync"
|
||||
echo "warning: ${ROOT}/.venv/bin/rein-aharness missing; install the worker environment first"
|
||||
elif ! "${ROOT}/.venv/bin/python" -c 'import glas_harness, sandboxer' >/dev/null 2>&1; then
|
||||
echo "warning: profiled ops runs require sibling runtimes; install with:"
|
||||
echo " ${ROOT}/.venv/bin/pip install -e ${ROOT}/../sand-boxer -e ${ROOT}/../glas-harness"
|
||||
fi
|
||||
|
||||
systemctl --user daemon-reload
|
||||
|
|
|
|||
|
|
@ -9,16 +9,24 @@ Does **not** require issue-core or Forgejo for FI / Binky-style ops.
|
|||
|
||||
```text
|
||||
activity-core emit_tasks
|
||||
→ INSERT ops_run (open) + dual-write activity_task_spawn
|
||||
→ INSERT ops_run (open, optional harness_profile_ref) + activity_task_spawn
|
||||
│
|
||||
▼
|
||||
rein-aharness claim-loop
|
||||
POST /ops-runs/claim
|
||||
select_approach(labels, definition, hint)
|
||||
execute (fi-research-brief | brief-daily | mail-* | agent-session)
|
||||
harness_profile_ref present? ─ yes → Glas ExecutionRequest → gateway
|
||||
│ no
|
||||
└─→ select_approach(labels, definition, hint) → legacy executor
|
||||
POST complete | fail(+reopen)
|
||||
```
|
||||
|
||||
`harness_profile_ref` is authoritative under ACT-ADR-006. A profiled row never
|
||||
consults `approach_hint`, `select_approach`, or a legacy executor, including
|
||||
after profile resolution refusal. The claim worker sends the complete
|
||||
`GatewayResult` to Activity Core; Activity Core persists its allowlisted
|
||||
`ExecutionEvidence` and drops direct tool output/error. Profile-absent rows keep
|
||||
the legacy coexistence path until the migration flag is enabled upstream.
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
|
|
@ -55,7 +63,8 @@ export ACTIVITY_CORE_WORKER_TOKEN=… # from actcore-runtime-secre
|
|||
| labels `agent-session` | agentic `run` (TaskSpec) |
|
||||
| none | fail ops_run (no silent drop) |
|
||||
|
||||
Optional `approach_hint` on the ops_run overrides matching.
|
||||
For a profile-absent legacy row, optional `approach_hint` overrides matching.
|
||||
It has no effect when `harness_profile_ref` is present.
|
||||
|
||||
Add a row: edit `rein_aharness/approaches.py` (`APPROACH_RULES` + `execute_approach`)
|
||||
and tests in `tests/test_approaches.py`. Prefer cheapest correct path
|
||||
|
|
@ -91,6 +100,8 @@ rein-aharness run --from-issue-core
|
|||
|
||||
```bash
|
||||
# From rein-aharness checkout on railiance01
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -e . -e ../llm-connect -e ../sand-boxer -e ../glas-harness
|
||||
./deploy/scripts/install-claim-loop-user.sh
|
||||
|
||||
# Or manually:
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ rein-aharness = "rein_aharness.cli:main"
|
|||
|
||||
[tool.uv.sources]
|
||||
llm-connect = { path = "../llm-connect", editable = true }
|
||||
glas-harness = { path = "../glas-harness", editable = true }
|
||||
sandboxer = { path = "../sand-boxer", editable = true }
|
||||
|
||||
[project.optional-dependencies]
|
||||
llm = ["llm-connect"]
|
||||
glas = ["glas-harness", "sandboxer"]
|
||||
dev = ["pytest>=8"]
|
||||
|
||||
[build-system]
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ from rein_aharness.approaches import (
|
|||
execute_approach,
|
||||
select_approach,
|
||||
)
|
||||
from rein_aharness.glas_execution import (
|
||||
GLAS_APPROACH,
|
||||
GlasExecutionError,
|
||||
execute_profiled_run,
|
||||
)
|
||||
from rein_aharness.ops_run_client import (
|
||||
ActivityCoreOpsClient,
|
||||
OpsRun,
|
||||
|
|
@ -105,7 +110,7 @@ def process_one(
|
|||
return ProcessResult(claimed=False, empty=True, reason="queue empty")
|
||||
|
||||
run = claimed[0]
|
||||
approach = select_approach(run)
|
||||
approach = GLAS_APPROACH if run.harness_profile_ref else select_approach(run)
|
||||
logger.info(
|
||||
"claimed run_id=%s approach=%s title=%r labels=%s",
|
||||
run.id,
|
||||
|
|
@ -140,6 +145,13 @@ def process_one(
|
|||
detail={"dry_run": True},
|
||||
)
|
||||
|
||||
if run.harness_profile_ref:
|
||||
return _process_profiled_run(
|
||||
client,
|
||||
run,
|
||||
report_to_hub=report_to_hub,
|
||||
)
|
||||
|
||||
hb = _Heartbeat(client, run.id, cfg.lease_seconds)
|
||||
hb.start()
|
||||
try:
|
||||
|
|
@ -207,6 +219,84 @@ def process_one(
|
|||
)
|
||||
|
||||
|
||||
def _process_profiled_run(
|
||||
client: ActivityCoreOpsClient,
|
||||
run: OpsRun,
|
||||
*,
|
||||
report_to_hub: bool,
|
||||
) -> ProcessResult:
|
||||
"""Execute a profiled row without consulting or falling back to legacy routing."""
|
||||
hb = _Heartbeat(client, run.id, client.config.lease_seconds)
|
||||
hb.start()
|
||||
try:
|
||||
try:
|
||||
gateway_result = execute_profiled_run(
|
||||
run,
|
||||
client.config,
|
||||
report_to_hub=report_to_hub,
|
||||
)
|
||||
except GlasExecutionError as exc:
|
||||
reason = str(exc)
|
||||
try:
|
||||
out = client.fail(
|
||||
run.id,
|
||||
error=reason,
|
||||
reopen=False,
|
||||
result={"ok": False, "approach": GLAS_APPROACH, "reason": reason},
|
||||
)
|
||||
except OpsRunError as close_exc:
|
||||
return ProcessResult(
|
||||
claimed=True,
|
||||
run_id=run.id,
|
||||
approach=GLAS_APPROACH,
|
||||
ok=False,
|
||||
reason=f"close ops_run failed: {close_exc}; {reason}",
|
||||
)
|
||||
return ProcessResult(
|
||||
claimed=True,
|
||||
run_id=run.id,
|
||||
approach=GLAS_APPROACH,
|
||||
ok=False,
|
||||
reason=reason,
|
||||
ops_state=out.state,
|
||||
)
|
||||
finally:
|
||||
hb.stop()
|
||||
|
||||
evidence = gateway_result["evidence"]
|
||||
ok = gateway_result["ok"]
|
||||
reason = str(evidence.get("error") or evidence.get("outcome") or "Glas execution failed")
|
||||
try:
|
||||
if ok:
|
||||
out = client.complete(run.id, result=gateway_result)
|
||||
else:
|
||||
out = client.fail(
|
||||
run.id,
|
||||
error=reason,
|
||||
reopen=False,
|
||||
result=gateway_result,
|
||||
)
|
||||
except OpsRunError as exc:
|
||||
return ProcessResult(
|
||||
claimed=True,
|
||||
run_id=run.id,
|
||||
approach=GLAS_APPROACH,
|
||||
ok=False,
|
||||
reason=f"close ops_run failed: {exc}; gateway_ok={ok} {reason}",
|
||||
detail={"execution_evidence": evidence},
|
||||
)
|
||||
|
||||
return ProcessResult(
|
||||
claimed=True,
|
||||
run_id=run.id,
|
||||
approach=GLAS_APPROACH,
|
||||
ok=ok,
|
||||
reason="" if ok else reason,
|
||||
ops_state=out.state,
|
||||
detail={"execution_evidence": evidence},
|
||||
)
|
||||
|
||||
|
||||
def poll_peek(client: ActivityCoreOpsClient | None = None) -> list[dict[str, Any]]:
|
||||
"""List open ops_runs with selected approach (no claim)."""
|
||||
client = client or ActivityCoreOpsClient()
|
||||
|
|
@ -220,7 +310,10 @@ def poll_peek(client: ActivityCoreOpsClient | None = None) -> list[dict[str, Any
|
|||
"state": run.state,
|
||||
"labels": run.labels,
|
||||
"target_repo": run.target_repo,
|
||||
"approach": select_approach(run),
|
||||
"approach": (
|
||||
GLAS_APPROACH if run.harness_profile_ref else select_approach(run)
|
||||
),
|
||||
"harness_profile_ref": run.harness_profile_ref,
|
||||
"created_at": run.raw.get("created_at"),
|
||||
}
|
||||
)
|
||||
|
|
|
|||
91
rein_aharness/glas_execution.py
Normal file
91
rein_aharness/glas_execution.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"""Profile-authoritative execution of activity-core ops runs through Glas.
|
||||
|
||||
Glas and sand-boxer are sibling runtime packages rather than hard dependencies
|
||||
of rein-aharness's legacy paths. Imports therefore stay at the execution edge:
|
||||
profile-absent coexistence continues to work, while a profiled row fails closed
|
||||
with an actionable error if the governed runtime is not installed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from rein_aharness.ops_run_client import OpsRun, OpsRunConfig, resolve_ops_target
|
||||
|
||||
GLAS_APPROACH = "glas-profile"
|
||||
|
||||
_SCALAR_REFS = (
|
||||
"correlation_id",
|
||||
"assignment_ref",
|
||||
"role_ref",
|
||||
"duty_ref",
|
||||
)
|
||||
_LIST_REFS = ("goal_refs", "resource_envelope_refs")
|
||||
|
||||
|
||||
class GlasExecutionError(RuntimeError):
|
||||
"""The authoritative Glas invocation could not produce a GatewayResult."""
|
||||
|
||||
|
||||
def _request_kwargs(run: OpsRun, config: OpsRunConfig, report_to_hub: bool) -> dict[str, Any]:
|
||||
if not run.harness_profile_ref:
|
||||
raise GlasExecutionError(f"ops_run {run.id} has no harness_profile_ref")
|
||||
|
||||
refs = run.execution_refs
|
||||
kwargs: dict[str, Any] = {
|
||||
"harness_profile_ref": run.harness_profile_ref,
|
||||
"repo": str(resolve_ops_target(run, config)),
|
||||
"title": run.title or "(untitled)",
|
||||
"description": run.description or "",
|
||||
"actor": config.worker_id,
|
||||
"project": "rein-aharness",
|
||||
"request_id": run.id,
|
||||
"report_to_hub": report_to_hub,
|
||||
}
|
||||
for key in _SCALAR_REFS:
|
||||
value = refs.get(key)
|
||||
if isinstance(value, str) and value:
|
||||
kwargs[key] = value
|
||||
for key in _LIST_REFS:
|
||||
value = refs.get(key)
|
||||
if isinstance(value, list):
|
||||
kwargs[key] = [item for item in value if isinstance(item, str)]
|
||||
return kwargs
|
||||
|
||||
|
||||
def execute_profiled_run(
|
||||
run: OpsRun,
|
||||
config: OpsRunConfig,
|
||||
*,
|
||||
report_to_hub: bool = True,
|
||||
request_factory: Callable[..., Any] | None = None,
|
||||
gateway: Callable[[Any], Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Invoke Glas and return its complete JSON-compatible GatewayResult."""
|
||||
if request_factory is None or gateway is None:
|
||||
try:
|
||||
from glas_harness.contract import ExecutionRequest
|
||||
from glas_harness.gateway import run_execution
|
||||
except ImportError as exc:
|
||||
raise GlasExecutionError(
|
||||
"profiled ops_run requires glas-harness with sand-boxer support; "
|
||||
"install the sibling checkouts into the claim-worker environment"
|
||||
) from exc
|
||||
request_factory = request_factory or ExecutionRequest
|
||||
gateway = gateway or run_execution
|
||||
|
||||
try:
|
||||
request = request_factory(**_request_kwargs(run, config, report_to_hub))
|
||||
result = gateway(request)
|
||||
raw = result.model_dump(mode="json") if hasattr(result, "model_dump") else result
|
||||
except GlasExecutionError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise GlasExecutionError(f"Glas gateway invocation failed: {exc}") from exc
|
||||
|
||||
if not isinstance(raw, dict) or not isinstance(raw.get("ok"), bool):
|
||||
raise GlasExecutionError("Glas gateway returned an invalid GatewayResult")
|
||||
if not isinstance(raw.get("evidence"), dict):
|
||||
raise GlasExecutionError("Glas GatewayResult is missing execution evidence")
|
||||
return raw
|
||||
|
|
@ -28,6 +28,8 @@ _SAFE_RESPONSE_METADATA_KEYS = frozenset(
|
|||
"created_at",
|
||||
}
|
||||
)
|
||||
_SAFE_ERROR_KEYS = ("error", "provider_status", "provider", "model")
|
||||
_MAX_ERROR_MESSAGE_CHARS = 400
|
||||
|
||||
|
||||
class LLMConnectError(RuntimeError):
|
||||
|
|
@ -60,9 +62,10 @@ class LLMConnectClient:
|
|||
json=payload,
|
||||
timeout=self.timeout_seconds,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except httpx.HTTPError as exc:
|
||||
raise LLMConnectError(f"llm-connect request failed: {exc}") from exc
|
||||
if resp.status_code >= 400:
|
||||
raise LLMConnectError(_llm_connect_error_text(resp))
|
||||
try:
|
||||
data = resp.json()
|
||||
except ValueError as exc:
|
||||
|
|
@ -74,6 +77,27 @@ class LLMConnectClient:
|
|||
return content
|
||||
|
||||
|
||||
def _llm_connect_error_text(resp: httpx.Response) -> str:
|
||||
"""Keep llm-connect's safe cause without copying an upstream response blob."""
|
||||
base = f"llm-connect returned HTTP {resp.status_code}"
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
return base
|
||||
if not isinstance(body, dict):
|
||||
return base
|
||||
|
||||
parts = [
|
||||
f"{key}={body[key]}"
|
||||
for key in _SAFE_ERROR_KEYS
|
||||
if body.get(key) not in (None, "")
|
||||
]
|
||||
message = body.get("message")
|
||||
if isinstance(message, str) and message.strip():
|
||||
parts.append(f"message={message.strip()[:_MAX_ERROR_MESSAGE_CHARS]}")
|
||||
return f"{base}: " + "; ".join(parts) if parts else base
|
||||
|
||||
|
||||
def get_llm_connect_client() -> LLMConnectClient:
|
||||
base_url = os.environ.get("LLM_CONNECT_URL", "").strip()
|
||||
if not base_url:
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ class OpsRun:
|
|||
source_id: str = ""
|
||||
triggering_event_id: str = ""
|
||||
approach_hint: str | None = None
|
||||
harness_profile_ref: str | None = None
|
||||
execution_refs: dict[str, Any] = field(default_factory=dict)
|
||||
result: dict[str, Any] = field(default_factory=dict)
|
||||
raw: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
|
@ -78,6 +80,8 @@ class OpsRun:
|
|||
source_id=str(data.get("source_id") or ""),
|
||||
triggering_event_id=str(data.get("triggering_event_id") or ""),
|
||||
approach_hint=data.get("approach_hint"),
|
||||
harness_profile_ref=data.get("harness_profile_ref"),
|
||||
execution_refs=dict(data.get("execution_refs") or {}),
|
||||
result=dict(data.get("result") or {}),
|
||||
raw=data,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
from rein_aharness.approaches import ApproachResult, APPROACH_FI_RESEARCH_BRIEF
|
||||
from rein_aharness.claim_loop import process_one, poll_peek
|
||||
from rein_aharness.glas_execution import GLAS_APPROACH, GlasExecutionError
|
||||
from rein_aharness.ops_run_client import OpsRun, OpsRunConfig, ActivityCoreOpsClient
|
||||
|
||||
|
||||
|
|
@ -95,3 +96,90 @@ def test_poll_peek() -> None:
|
|||
rows = poll_peek(client)
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["approach"] == APPROACH_FI_RESEARCH_BRIEF
|
||||
|
||||
|
||||
def test_profiled_run_uses_glas_and_completes_with_full_result() -> None:
|
||||
client = MagicMock(spec=ActivityCoreOpsClient)
|
||||
client.config = OpsRunConfig(worker_id="w", lease_seconds=90)
|
||||
run = _claimed_run()
|
||||
run.harness_profile_ref = "harness.agent-dev-local@1.0.0"
|
||||
run.approach_hint = "fi-research-brief"
|
||||
client.claim.return_value = [run]
|
||||
client.complete.return_value = OpsRun(
|
||||
id=run.id,
|
||||
activity_definition_id="def",
|
||||
idempotency_key="k",
|
||||
target_repo=run.target_repo,
|
||||
title=run.title,
|
||||
description="",
|
||||
state="succeeded",
|
||||
)
|
||||
gateway_result = {
|
||||
"ok": True,
|
||||
"evidence": {
|
||||
"outcome": "succeeded",
|
||||
"profile_ref": run.harness_profile_ref,
|
||||
"sandbox_id": "sbx-1",
|
||||
},
|
||||
"tool_output": "not copied into ProcessResult.detail",
|
||||
"tool_error": None,
|
||||
}
|
||||
with (
|
||||
patch("rein_aharness.claim_loop.execute_profiled_run", return_value=gateway_result),
|
||||
patch("rein_aharness.claim_loop.select_approach") as select,
|
||||
patch("rein_aharness.claim_loop.execute_approach") as execute,
|
||||
):
|
||||
result = process_one(client)
|
||||
|
||||
assert result.ok is True
|
||||
assert result.approach == GLAS_APPROACH
|
||||
assert result.detail == {"execution_evidence": gateway_result["evidence"]}
|
||||
client.complete.assert_called_once_with(run.id, result=gateway_result)
|
||||
client.fail.assert_not_called()
|
||||
select.assert_not_called()
|
||||
execute.assert_not_called()
|
||||
|
||||
|
||||
def test_profile_refusal_fails_terminally_without_legacy_fallback() -> None:
|
||||
client = MagicMock(spec=ActivityCoreOpsClient)
|
||||
client.config = OpsRunConfig(worker_id="w", lease_seconds=90)
|
||||
run = _claimed_run()
|
||||
run.harness_profile_ref = "harness.unknown@9.9.9"
|
||||
run.approach_hint = "fi-research-brief"
|
||||
client.claim.return_value = [run]
|
||||
client.fail.return_value = OpsRun(
|
||||
id=run.id,
|
||||
activity_definition_id="def",
|
||||
idempotency_key="k",
|
||||
target_repo=run.target_repo,
|
||||
title=run.title,
|
||||
description="",
|
||||
state="failed",
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"rein_aharness.claim_loop.execute_profiled_run",
|
||||
side_effect=GlasExecutionError("unknown harness profile"),
|
||||
),
|
||||
patch("rein_aharness.claim_loop.select_approach") as select,
|
||||
patch("rein_aharness.claim_loop.execute_approach") as execute,
|
||||
):
|
||||
result = process_one(client)
|
||||
|
||||
assert result.ok is False
|
||||
assert result.ops_state == "failed"
|
||||
assert client.fail.call_args.kwargs["reopen"] is False
|
||||
select.assert_not_called()
|
||||
execute.assert_not_called()
|
||||
|
||||
|
||||
def test_poll_peek_reports_authoritative_profile_route() -> None:
|
||||
client = MagicMock(spec=ActivityCoreOpsClient)
|
||||
run = _claimed_run()
|
||||
run.harness_profile_ref = "harness.agent-dev-local@1.0.0"
|
||||
client.list_open.return_value = [run]
|
||||
with patch("rein_aharness.claim_loop.select_approach") as select:
|
||||
rows = poll_peek(client)
|
||||
assert rows[0]["approach"] == GLAS_APPROACH
|
||||
assert rows[0]["harness_profile_ref"] == run.harness_profile_ref
|
||||
select.assert_not_called()
|
||||
|
|
|
|||
93
tests/test_glas_execution.py
Normal file
93
tests/test_glas_execution.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"""Tests for the authoritative activity-core -> Glas consumer boundary."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from rein_aharness.glas_execution import GlasExecutionError, execute_profiled_run
|
||||
from rein_aharness.ops_run_client import OpsRun, OpsRunConfig
|
||||
|
||||
|
||||
def _repo(tmp_path: Path) -> Path:
|
||||
repo = tmp_path / "target"
|
||||
repo.mkdir()
|
||||
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
|
||||
return repo
|
||||
|
||||
|
||||
def _run(repo: Path) -> OpsRun:
|
||||
return OpsRun(
|
||||
id="run-1",
|
||||
activity_definition_id="def-1",
|
||||
idempotency_key="key-1",
|
||||
target_repo="target",
|
||||
title="Governed task",
|
||||
description="Do the bounded work",
|
||||
state="claimed",
|
||||
harness_profile_ref="harness.agent-dev-local@1.0.0",
|
||||
approach_hint="agent-session",
|
||||
execution_refs={
|
||||
"correlation_id": "corr-1",
|
||||
"assignment_ref": "assignment:1",
|
||||
"role_ref": "role:1",
|
||||
"duty_ref": "duty:1",
|
||||
"goal_refs": ["goal:1"],
|
||||
"resource_envelope_refs": ["resource:1"],
|
||||
"unknown": "must-not-cross",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_profiled_run_builds_complete_execution_request(tmp_path: Path) -> None:
|
||||
repo = _repo(tmp_path)
|
||||
captured = {}
|
||||
|
||||
def request_factory(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return kwargs
|
||||
|
||||
expected = {
|
||||
"ok": True,
|
||||
"evidence": {"outcome": "succeeded", "profile_ref": "profile"},
|
||||
"tool_output": "direct caller output",
|
||||
"tool_error": None,
|
||||
}
|
||||
result = execute_profiled_run(
|
||||
_run(repo),
|
||||
OpsRunConfig(worker_id="rein-aharness@test", repo_roots=(str(tmp_path),)),
|
||||
report_to_hub=False,
|
||||
request_factory=request_factory,
|
||||
gateway=lambda request: expected,
|
||||
)
|
||||
|
||||
assert result is expected
|
||||
assert captured == {
|
||||
"harness_profile_ref": "harness.agent-dev-local@1.0.0",
|
||||
"repo": str(repo.resolve()),
|
||||
"title": "Governed task",
|
||||
"description": "Do the bounded work",
|
||||
"actor": "rein-aharness@test",
|
||||
"project": "rein-aharness",
|
||||
"request_id": "run-1",
|
||||
"correlation_id": "corr-1",
|
||||
"assignment_ref": "assignment:1",
|
||||
"role_ref": "role:1",
|
||||
"duty_ref": "duty:1",
|
||||
"goal_refs": ["goal:1"],
|
||||
"resource_envelope_refs": ["resource:1"],
|
||||
"report_to_hub": False,
|
||||
}
|
||||
|
||||
|
||||
def test_profiled_run_rejects_invalid_gateway_result(tmp_path: Path) -> None:
|
||||
repo = _repo(tmp_path)
|
||||
with pytest.raises(GlasExecutionError, match="invalid GatewayResult"):
|
||||
execute_profiled_run(
|
||||
_run(repo),
|
||||
OpsRunConfig(repo_roots=(str(tmp_path),)),
|
||||
request_factory=lambda **kwargs: kwargs,
|
||||
gateway=lambda request: {"evidence": {}},
|
||||
)
|
||||
|
|
@ -169,6 +169,8 @@ def test_llm_connect_client_complete(monkeypatch) -> None:
|
|||
import rein_aharness.llm_connect_client as mod
|
||||
|
||||
class FakeResp:
|
||||
status_code = 200
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
|
|
@ -187,6 +189,68 @@ def test_llm_connect_client_complete(monkeypatch) -> None:
|
|||
assert client.last_response_metadata.get("model") == "test-model"
|
||||
|
||||
|
||||
class _ErrorResp:
|
||||
def __init__(self, status_code: int, body) -> None:
|
||||
self.status_code = status_code
|
||||
self._body = body
|
||||
|
||||
def json(self):
|
||||
if isinstance(self._body, Exception):
|
||||
raise self._body
|
||||
return self._body
|
||||
|
||||
|
||||
def test_llm_connect_error_preserves_safe_provider_cause(monkeypatch) -> None:
|
||||
import rein_aharness.llm_connect_client as mod
|
||||
|
||||
response = _ErrorResp(
|
||||
502,
|
||||
{
|
||||
"error": "provider_api_error",
|
||||
"provider_status": 401,
|
||||
"provider": "openrouter",
|
||||
"model": "model-1",
|
||||
"message": "No auth credentials found",
|
||||
"api_key": "sk-must-not-appear",
|
||||
"raw_response": {"authorization": "Bearer secret"},
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(mod.httpx, "post", lambda *args, **kwargs: response)
|
||||
|
||||
with pytest.raises(LLMConnectError) as excinfo:
|
||||
LLMConnectClient("http://llm.test").complete("hi")
|
||||
|
||||
text = str(excinfo.value)
|
||||
assert "HTTP 502" in text
|
||||
assert "error=provider_api_error" in text
|
||||
assert "provider_status=401" in text
|
||||
assert "provider=openrouter" in text
|
||||
assert "model=model-1" in text
|
||||
assert "message=No auth credentials found" in text
|
||||
assert "sk-must-not-appear" not in text
|
||||
assert "authorization" not in text
|
||||
|
||||
|
||||
def test_llm_connect_error_message_is_bounded(monkeypatch) -> None:
|
||||
import rein_aharness.llm_connect_client as mod
|
||||
|
||||
response = _ErrorResp(502, {"error": "provider_api_error", "message": "x" * 5000})
|
||||
monkeypatch.setattr(mod.httpx, "post", lambda *args, **kwargs: response)
|
||||
with pytest.raises(LLMConnectError) as excinfo:
|
||||
LLMConnectClient("http://llm.test").complete("hi")
|
||||
assert len(str(excinfo.value)) < 600
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", [ValueError("not json"), ["unexpected"]])
|
||||
def test_llm_connect_error_without_usable_body_reports_status(monkeypatch, body) -> None:
|
||||
import rein_aharness.llm_connect_client as mod
|
||||
|
||||
response = _ErrorResp(504, body)
|
||||
monkeypatch.setattr(mod.httpx, "post", lambda *args, **kwargs: response)
|
||||
with pytest.raises(LLMConnectError, match="llm-connect returned HTTP 504"):
|
||||
LLMConnectClient("http://llm.test").complete("hi")
|
||||
|
||||
|
||||
def test_get_client_requires_env(monkeypatch) -> None:
|
||||
monkeypatch.delenv("LLM_CONNECT_URL", raising=False)
|
||||
with pytest.raises(LLMConnectError, match="LLM_CONNECT_URL"):
|
||||
|
|
|
|||
|
|
@ -31,10 +31,20 @@ def test_ops_run_from_api() -> None:
|
|||
"labels": ["automated", "research-brief"],
|
||||
"state": "open",
|
||||
"attempt": 0,
|
||||
"harness_profile_ref": "harness.agent-dev-local@1.0.0",
|
||||
"execution_refs": {
|
||||
"correlation_id": "corr-1",
|
||||
"goal_refs": ["goal:42@1"],
|
||||
},
|
||||
}
|
||||
)
|
||||
assert row.target_repo == "freedom-intelligence"
|
||||
assert "research-brief" in row.labels
|
||||
assert row.harness_profile_ref == "harness.agent-dev-local@1.0.0"
|
||||
assert row.execution_refs == {
|
||||
"correlation_id": "corr-1",
|
||||
"goal_refs": ["goal:42@1"],
|
||||
}
|
||||
|
||||
|
||||
def test_config_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
|
|
|||
287
uv.lock
generated
287
uv.lock
generated
|
|
@ -1,6 +1,24 @@
|
|||
version = 1
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.14.2"
|
||||
|
|
@ -32,6 +50,41 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.141.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glas-harness"
|
||||
version = "0.1.0"
|
||||
source = { editable = "../glas-harness" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "httpx", specifier = ">=0.27" },
|
||||
{ name = "pydantic", specifier = ">=2.0" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8" },
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
{ name = "sandboxer", marker = "extra == 'sandbox'", editable = "../sand-boxer" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
|
|
@ -110,6 +163,27 @@ dev = [
|
|||
{ name = "ruff", specifier = ">=0.4" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.3"
|
||||
|
|
@ -128,6 +202,123 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314 },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420 },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464 },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823 },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782 },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179 },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733 },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238 },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309 },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575 },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552 },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527 },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492 },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.20.0"
|
||||
|
|
@ -221,16 +412,85 @@ dependencies = [
|
|||
dev = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
glas = [
|
||||
{ name = "glas-harness" },
|
||||
{ name = "sandboxer" },
|
||||
]
|
||||
llm = [
|
||||
{ name = "llm-connect" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "glas-harness", marker = "extra == 'glas'", editable = "../glas-harness" },
|
||||
{ name = "httpx", specifier = ">=0.27" },
|
||||
{ name = "llm-connect", marker = "extra == 'llm'", editable = "../llm-connect" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8" },
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
{ name = "sandboxer", marker = "extra == 'glas'", editable = "../sand-boxer" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "15.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sandboxer"
|
||||
version = "0.0.0"
|
||||
source = { editable = "../sand-boxer" }
|
||||
dependencies = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fastapi", specifier = ">=0.115" },
|
||||
{ name = "httpx", specifier = ">=0.27" },
|
||||
{ name = "pydantic", specifier = ">=2.0" },
|
||||
{ name = "pyyaml", specifier = ">=6.0" },
|
||||
{ name = "typer", specifier = ">=0.12" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "pytest", specifier = ">=8.0" },
|
||||
{ name = "ruff", specifier = ">=0.6" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.32" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -242,6 +502,21 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.27.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
{ name = "rich" },
|
||||
{ name = "shellingham" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/40/4a3db7990d1f62a53182aa96eaef57aeb2886a27f90a195bc66713565d31/typer-0.27.1.tar.gz", hash = "sha256:a79bef8469a79c45498e7b814ecf8d603cc7644e9acbd9e19cac0334240b18df", size = 203994 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/89/9518bc0c3929bee36b3a4a8e3daddd6e03f92f9961c66d4983b837160543/typer-0.27.1-py3-none-any.whl", hash = "sha256:53150287edd11baeb4e4722c8e394fcdf8181c0ae89485cba8d25c778d5edd56", size = 122874 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.16.0"
|
||||
|
|
@ -250,3 +525,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3
|
|||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750 },
|
||||
]
|
||||
|
|
|
|||
103
workplans/REIN-A-0004-glas-profiled-ops-runs.md
Normal file
103
workplans/REIN-A-0004-glas-profiled-ops-runs.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
id: REIN-A-0004
|
||||
type: workplan
|
||||
title: "Profile-driven ops-run execution through Glas"
|
||||
domain: infotech
|
||||
repo: rein-aharness
|
||||
status: active
|
||||
owner: codex
|
||||
topic_slug: rein-aharness
|
||||
priority: high
|
||||
created: "2026-08-22"
|
||||
updated: "2026-08-22"
|
||||
related:
|
||||
- ACTIVITY-WP-0032
|
||||
- ACT-ADR-006
|
||||
---
|
||||
|
||||
# REIN-A-0004 — Profile-driven ops-run execution through Glas
|
||||
|
||||
## Origin
|
||||
|
||||
Activity Core handoff `4ed3422a-b42c-4408-843c-f63c3e86cf3e` reports that
|
||||
the claim consumer currently drops `harness_profile_ref` and `execution_refs`.
|
||||
A profiled row would consequently enter the legacy `select_approach` path and
|
||||
could execute a different constellation than the producer authorized.
|
||||
|
||||
The related handoff `69710949-9726-4fd0-aef3-1c7075870410` reports that
|
||||
llm-connect provider errors lose their safe structured status at this boundary,
|
||||
making a provider 401 look like an undifferentiated 502 transport failure.
|
||||
|
||||
## Carry the profile selector and attribution references
|
||||
|
||||
```task
|
||||
id: REIN-A-0004-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Extend `OpsRun.from_api` with `harness_profile_ref` and the allowlisted
|
||||
`execution_refs`. Preserve the queued selector verbatim for the authoritative
|
||||
Glas resolver; do not translate it into an approach hint.
|
||||
|
||||
**Done 2026-08-22:** both fields are normalized onto `OpsRun` and covered by
|
||||
the API projection test.
|
||||
|
||||
## Route profiled rows through the Glas gateway
|
||||
|
||||
```task
|
||||
id: REIN-A-0004-T02
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
For a row with a profile, resolve its target checkout, build the Glas
|
||||
`ExecutionRequest`, and invoke `run_execution` before any sandbox is created by
|
||||
this consumer. Complete or fail the ops run with the full `GatewayResult`.
|
||||
Profile refusal and gateway failure are terminal and never fall back to
|
||||
`select_approach`. Profile-absent rows retain the legacy coexistence path.
|
||||
|
||||
**Done 2026-08-22:** `glas_execution.py` resolves the queued checkout, maps the
|
||||
complete contract 1.0 request (including attribution refs), and invokes Glas
|
||||
through lazy runtime imports. The claim loop branches on profile presence
|
||||
before legacy selection and sends the full `GatewayResult` to Activity Core.
|
||||
Its own returned detail retains only `ExecutionEvidence`. Tests prove success,
|
||||
terminal refusal, no legacy fallback, and profile-aware queue inspection. A
|
||||
real sibling-package probe of `harness.unknown@9.9.9` returned
|
||||
`refused/resolution` with no sandbox id.
|
||||
|
||||
## Preserve safe llm-connect provider failure context
|
||||
|
||||
```task
|
||||
id: REIN-A-0004-T03
|
||||
status: done
|
||||
priority: medium
|
||||
```
|
||||
|
||||
On an HTTP error, retain only llm-connect's allowlisted `error`,
|
||||
`provider_status`, `provider`, and `model` fields plus a bounded message. Never
|
||||
copy the raw provider response or credential material into an ops-run result.
|
||||
|
||||
**Done 2026-08-22:** HTTP errors retain exactly the four safe diagnostic fields
|
||||
plus a 400-character message. Tests cover the upstream 401 hidden behind 502,
|
||||
redaction-by-allowlist, bounding, and unusable bodies.
|
||||
|
||||
## Deploy and coordinate one production proof
|
||||
|
||||
```task
|
||||
id: REIN-A-0004-T04
|
||||
status: progress
|
||||
priority: high
|
||||
```
|
||||
|
||||
Install the Glas and sand-boxer sibling packages into the railiance01 claim
|
||||
worker environment, deploy the source-tested consumer, and coordinate one
|
||||
low-risk profiled definition with Activity Core. Evidence must show the queued
|
||||
profile, resolved rein/model/sandbox/outcome, and teardown/commit facts.
|
||||
|
||||
**Progress 2026-08-22:** source verification is green (96 tests, compilation,
|
||||
and diff checks). Packaging exposes the local Glas/sand-boxer extra and the
|
||||
deployment rsync now excludes `.venv`, preventing workstation-bound shebangs
|
||||
from overwriting the live worker environment. railiance01 is healthy on the
|
||||
legacy worker but still needs the Glas checkout/package install, deployment,
|
||||
restart, and coordinated pilot.
|
||||
Loading…
Add table
Add a link
Reference in a new issue