Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions .github/workflows/custodian-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,33 @@ jobs:
fi

- name: Materialize boundary artifact file
# Decode the boundary disclosure artifact from the base64 CONTENT secret
# REPOGRAPH_BOUNDARY_ARTIFACT_B64. The older *_FILE secret held a filesystem
# path that cannot resolve on a CI runner (the artifact lives in the private
# repo on the dev machine), so this step used to fail and the audit never ran.
# Graceful: if the secret is absent, skip — custodian's B2 reports the
# missing-artifact requirement rather than this step hard-failing.
env:
REPOGRAPH_BOUNDARY_ARTIFACT_SOURCE: ${{ secrets.REPOGRAPH_BOUNDARY_ARTIFACT_FILE }}
REPOGRAPH_BOUNDARY_ARTIFACT_B64: ${{ secrets.REPOGRAPH_BOUNDARY_ARTIFACT_B64 }}
run: |
if [ -z "${REPOGRAPH_BOUNDARY_ARTIFACT_SOURCE:-}" ]; then
echo "Missing REPOGRAPH_BOUNDARY_ARTIFACT_FILE secret" >&2
exit 1
if [ -z "${REPOGRAPH_BOUNDARY_ARTIFACT_B64:-}" ]; then
echo "REPOGRAPH_BOUNDARY_ARTIFACT_B64 not set — skipping (B2 flags if required)."
exit 0
fi
tmp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/repograph-boundary-XXXXXX.json")"
python - "$REPOGRAPH_BOUNDARY_ARTIFACT_SOURCE" "$tmp_file" <<'PY'
dest="$(mktemp "${RUNNER_TEMP:-/tmp}/repograph-boundary-XXXXXX.json")"
printf '%s' "$REPOGRAPH_BOUNDARY_ARTIFACT_B64" | base64 -d > "$dest"
python - "$dest" <<'PY'
import json
import shutil
import sys
import urllib.request
from pathlib import Path

source = sys.argv[1]
dest = Path(sys.argv[2])
src_path = Path(source)
if src_path.is_file():
shutil.copyfile(src_path, dest)
elif source.startswith(("http://", "https://")):
with urllib.request.urlopen(source) as response, dest.open("wb") as fh:
shutil.copyfileobj(response, fh)
else:
raise SystemExit(f"Unsupported boundary artifact source: {source}")
data = json.loads(dest.read_text(encoding="utf-8"))
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
print(f"boundary_provenance={data.get('source_graph_id')}@{data.get('source_ref_or_commit')}")
PY
echo "REPOGRAPH_BOUNDARY_ARTIFACT_FILE=$tmp_file" >> "$GITHUB_ENV"
echo "REPOGRAPH_BOUNDARY_ARTIFACT_FILE=$dest" >> "$GITHUB_ENV"
- name: Run Custodian audit
run: |
# .custodian/config.yaml flags an unset core.hooksPath (W2); wire it
# like a developer checkout would so the audit reflects real findings.
git config core.hooksPath .hooks
custodian-multi --repos . --fail-on-findings --no-color
1 change: 1 addition & 0 deletions src/core_runner/contracts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
``pending | running | succeeded | failed | timed_out | cancelled |
rejected``.
"""

from core_runner.contracts.invocation import RuntimeInvocation
from core_runner.contracts.result import ArtifactDescriptor, RuntimeResult

Expand Down
1 change: 1 addition & 0 deletions src/core_runner/contracts/invocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import RuntimeInvocation`` without depending on the RxP package
directly.
"""

from rxp.contracts import RuntimeInvocation

__all__ = ["RuntimeInvocation"]
1 change: 1 addition & 0 deletions src/core_runner/contracts/result.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""CoreRunner returns the canonical RxP RuntimeResult contract."""

from rxp.contracts import ArtifactDescriptor, RuntimeResult

__all__ = ["RuntimeResult", "ArtifactDescriptor"]
1 change: 1 addition & 0 deletions src/core_runner/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Transient SIGTERM handler — child group is killed if the Python supervisor
is itself killed (OOM killer, supervisor stop)
"""

from __future__ import annotations

import os
Expand Down
45 changes: 24 additions & 21 deletions src/core_runner/runners/async_http_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@

Each call uses a short-lived ``httpx.Client``; no global state.
"""

from __future__ import annotations

import json
Expand Down Expand Up @@ -97,8 +98,7 @@ def __init__(
) -> None:
if httpx is None and client is None:
raise ImportError(
"AsyncHttpRunner requires httpx. Install with "
"`pip install core-runner[http]`"
"AsyncHttpRunner requires httpx. Install with `pip install core-runner[http]`"
)
self._follow_redirects = follow_redirects
self._verify = verify
Expand Down Expand Up @@ -137,12 +137,11 @@ def run(self, invocation: RuntimeInvocation) -> RuntimeResult:

pending_raw = meta.get("http.poll_pending_codes") or ""
try:
pending_codes = tuple(
int(s.strip()) for s in pending_raw.split(",") if s.strip()
)
pending_codes = tuple(int(s.strip()) for s in pending_raw.split(",") if s.strip())
except ValueError:
return _rejected(
invocation, started,
invocation,
started,
"http.poll_pending_codes must be comma-separated integers",
)

Expand Down Expand Up @@ -187,18 +186,22 @@ def run(self, invocation: RuntimeInvocation) -> RuntimeResult:
# - server acknowledged async dispatch (status field absent
# or non-terminal, e.g. Archon's {accepted, status:"started"})
if _is_synchronous_terminal(
kickoff_resp, poll_status_path, terminal_states,
kickoff_resp,
poll_status_path,
terminal_states,
):
return _terminal_from_kickoff(
invocation, started, kickoff_resp,
success_states, poll_status_path,
invocation,
started,
kickoff_resp,
success_states,
poll_status_path,
)
# Fall through to poll loop — kickoff was an ack, not a result.
elif kickoff_resp.status_code != 202:
preview = kickoff_resp.text[:200] if kickoff_resp.text else ""
msg = (
f"kickoff expected 202 (or 200), got HTTP "
f"{kickoff_resp.status_code}: {preview}"
f"kickoff expected 202 (or 200), got HTTP {kickoff_resp.status_code}: {preview}"
).strip()
return _failed(invocation, started, msg)

Expand All @@ -208,7 +211,8 @@ def run(self, invocation: RuntimeInvocation) -> RuntimeResult:
run_id_path = meta.get("http.poll_run_id_path")
if not run_id_path:
return _rejected(
invocation, started,
invocation,
started,
"poll_url_template contains {run_id} but no poll_run_id_path provided",
)
try:
Expand All @@ -228,7 +232,9 @@ def run(self, invocation: RuntimeInvocation) -> RuntimeResult:
while True:
if _deadline_exceeded(deadline_monotonic):
return _timed_out(
invocation, started, timeout,
invocation,
started,
timeout,
TimeoutError("poll loop deadline exceeded"),
"poll",
)
Expand All @@ -249,7 +255,8 @@ def run(self, invocation: RuntimeInvocation) -> RuntimeResult:
continue
preview = poll_resp.text[:200] if poll_resp.text else ""
return _failed(
invocation, started,
invocation,
started,
f"poll expected HTTP 200, got {poll_resp.status_code}: {preview}".strip(),
)
try:
Expand All @@ -260,8 +267,7 @@ def run(self, invocation: RuntimeInvocation) -> RuntimeResult:
status = _extract_path(poll_payload, poll_status_path)
if status is None:
msg = (
f"poll response has no field at path "
f"{poll_status_path!r}: {poll_payload!r}"
f"poll response has no field at path {poll_status_path!r}: {poll_payload!r}"
)
return _failed(invocation, started, msg)
status_str = str(status)
Expand All @@ -280,8 +286,7 @@ def run(self, invocation: RuntimeInvocation) -> RuntimeResult:
stderr_path=None,
artifacts=[],
error_summary=(
None if success
else f"backend reported terminal status: {status_str}"
None if success else f"backend reported terminal status: {status_str}"
),
)

Expand Down Expand Up @@ -454,9 +459,7 @@ def _timed_out(
phase: str,
) -> RuntimeResult:
note = (
f"{phase} exceeded timeout of {timeout}s: {exc}"
if timeout
else f"{phase} timed out: {exc}"
f"{phase} exceeded timeout of {timeout}s: {exc}" if timeout else f"{phase} timed out: {exc}"
)
return RuntimeResult(
invocation_id=invocation.invocation_id,
Expand Down
4 changes: 2 additions & 2 deletions src/core_runner/runners/http_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
This runner installs no global state. Each ``run`` opens a short-lived
``httpx.Client`` so timeout/cancellation semantics are local to the call.
"""

from __future__ import annotations

import json
Expand Down Expand Up @@ -50,8 +51,7 @@ def __init__(
) -> None:
if httpx is None and client is None:
raise ImportError(
"HttpRunner requires httpx. Install with "
"`pip install core-runner[http]`"
"HttpRunner requires httpx. Install with `pip install core-runner[http]`"
)
self._follow_redirects = follow_redirects
self._verify = verify
Expand Down
1 change: 1 addition & 0 deletions src/core_runner/runners/manual_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
``HttpRunner`` / ``ContainerRunner`` will cover ``"http"`` /
``"container"`` with concrete implementations.
"""

from __future__ import annotations

from collections.abc import Callable
Expand Down
2 changes: 1 addition & 1 deletion src/core_runner/runners/subprocess_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
Provides stdout/stderr capture to files and ArtifactDescriptor production
on top of the process-group-safe safe_run() primitive.
"""

from __future__ import annotations

import os
from datetime import UTC, datetime
from pathlib import Path

Expand Down
1 change: 1 addition & 0 deletions src/core_runner/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
than raising — same posture as the missing-working-directory check
in ``SubprocessRunner``.
"""

from __future__ import annotations

from datetime import UTC, datetime
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Refuses to run unless invoked from inside this project's `.venv` —
prevents accidental test runs against the global interpreter.
"""

from __future__ import annotations

import os
Expand Down
Loading
Loading