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
37 changes: 30 additions & 7 deletions devtools/agent_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,35 @@
from collections.abc import Callable, Mapping
from pathlib import Path, PurePosixPath

AGENT_PRINCIPAL_ENV = "SINNIXD_PRINCIPAL"
#: The queue runner exports ``AGENTCTL_<NAME>``; the earlier daemon exported
#: ``SINNIXD_<NAME>``. Every read accepts both, newest first.
RUNTIME_ENV_PREFIXES = ("AGENTCTL_", "SINNIXD_")
AGENT_PRINCIPAL_ENV = "AGENTCTL_PRINCIPAL"
AGENT_PRINCIPAL = "agent-control"
AGENT_MAX_PYTEST_WORKERS = 2
HARNESS_RUN_ENV = "POLYLOGUE_PYTEST_RUN_ID"
QUEUE_WORKER_ENV = "SINNIXD_QUEUE_WORKER"
QUEUE_POOL_ENV = "SINNIXD_QUEUE_POOL"
QUEUE_WORKER_ENV = "AGENTCTL_QUEUE_WORKER"
QUEUE_POOL_ENV = "AGENTCTL_QUEUE_POOL"


def runtime_env(env: Mapping[str, str], name: str) -> str | None:
"""Read a queue-runner variable under any of its prefixes.

``name`` is the bare suffix (``"JOB_ID"``) or a full name under either
prefix; the newest prefix wins when both are set.
"""
suffix = name
for prefix in RUNTIME_ENV_PREFIXES:
if name.startswith(prefix):
suffix = name[len(prefix) :]
break
for prefix in RUNTIME_ENV_PREFIXES:
value = env.get(prefix + suffix)
if value is not None:
return value
Comment on lines +37 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Clear the higher-priority prefix in legacy environment tests

When the corpus runs through the declared verify_affected or verify_all AgentCTL operations, pytest inherits real AGENTCTL_OPERATION, AGENTCTL_JOB_ID, and AGENTCTL_CORRELATION_ID values. Tests such as test_declared_operation_requires_the_fixed_route and the AgentCTL receipt tests only set or clear their SINNIXD_* counterparts, so this precedence rule ignores the test values: the former returns None instead of verify_quick, while the receipt assertions observe the runner's IDs instead of job-join/job-17. Update those tests or an autouse fixture to isolate both namespaces so the declared verification operations can pass.

Useful? React with 馃憤聽/ 馃憥.

return None


QUEUE_WORKER_VALUE = "1"
PYTEST_POOL = "pytest"
#: Every operation declaring ``pool = "pytest"`` in ``.agentctl/project.toml``,
Expand Down Expand Up @@ -53,7 +76,7 @@ def _inside_pytest_cgroup(cgroup_reader: Callable[[], str] | None) -> bool:


def inside_agent_job(env: Mapping[str, str], *, cgroup_reader: Callable[[], str] | None = None) -> bool:
return env.get(AGENT_PRINCIPAL_ENV) == AGENT_PRINCIPAL or _inside_agent_cgroup(cgroup_reader)
return runtime_env(env, AGENT_PRINCIPAL_ENV) == AGENT_PRINCIPAL or _inside_agent_cgroup(cgroup_reader)


def inside_declared_pytest_worker(env: Mapping[str, str], *, cgroup_reader: Callable[[], str] | None = None) -> bool:
Expand All @@ -64,13 +87,13 @@ def inside_declared_pytest_worker(env: Mapping[str, str], *, cgroup_reader: Call
come from ``sinnixd-queue-run``; the pytest slice binds that identity to the
pool declared by the operation.
"""
if env.get(QUEUE_WORKER_ENV) != QUEUE_WORKER_VALUE or not env.get("SINNIXD_JOB_ID"):
if runtime_env(env, QUEUE_WORKER_ENV) != QUEUE_WORKER_VALUE or not runtime_env(env, "JOB_ID"):
return False
declared_pool = env.get(QUEUE_POOL_ENV)
declared_pool = runtime_env(env, QUEUE_POOL_ENV)
if declared_pool is not None:
if declared_pool != PYTEST_POOL:
return False
elif env.get("SINNIXD_OPERATION") not in PYTEST_WORKER_OPERATIONS:
elif runtime_env(env, "OPERATION") not in PYTEST_WORKER_OPERATIONS:
return False
return _inside_pytest_cgroup(cgroup_reader)

Expand Down
12 changes: 9 additions & 3 deletions devtools/pytest_slot.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@
]

#: Exported into every task started by ``sinnixd-queue-run``.
QUEUE_TASK_ENV: Final = "SINNIXD_JOB_ID"
QUEUE_TASK_ENV: Final = "AGENTCTL_JOB_ID"
#: The installed queue runner moves the workload out of pueued.service and
#: into the slice selected by the launch document's pool.
QUEUE_RUNNER: Final = "sinnixd-queue-run"
#: The queue runner, newest name first; the daemon-era name still resolves on
#: a host that has not switched.
QUEUE_RUNNERS: Final = ("agentctl-run", "sinnixd-queue-run")
QUEUE_RUNNER: Final = QUEUE_RUNNERS[0]
Comment on lines +61 to +62

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the missing-runner test for both executable names

With the hermetic PATH in test_missing_scoped_queue_runner_refuses_before_queueing, the monkeypatch hides only QUEUE_RUNNER (agentctl-run) and no legacy runner exists, so this branch raises a diagnostic naming agentctl-run; the unchanged assertion still matches sinnixd-queue-run, making tests/unit/devtools/test_pytest_slot.py fail on every run. Update that test to account for QUEUE_RUNNERS and the new diagnostic.

Useful? React with 馃憤聽/ 馃憥.

#: Explicit escape, for the hermetic test of this mechanism.
SLOT_ESCAPE_ENV: Final = "POLYLOGUE_PYTEST_SLOT"
SLOT_HELD: Final = "held"
Expand Down Expand Up @@ -341,7 +344,10 @@ def _queue(
launch_path = root / LAUNCH_DIR / f"pytest-slot-{identity}.json"
log_path = root / LAUNCH_DIR / f"pytest-slot-{identity}.log"
adder = adder_environment(env)
queue_runner = shutil.which(QUEUE_RUNNER, path=adder.get("PATH") or os.defpath)
queue_runner = next(
(found for name in QUEUE_RUNNERS if (found := shutil.which(name, path=adder.get("PATH") or os.defpath))),
None,
)
if queue_runner is None:
raise PytestSlotUnavailableError(REFUSAL.format(reason=f"`{QUEUE_RUNNER}` is not on PATH"))
_write_launch(
Expand Down
12 changes: 7 additions & 5 deletions devtools/sinnixd_service_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from typing import Any
from uuid import UUID

from devtools.agent_env import runtime_env

_CGROUP_PATH = Path("/proc/self/cgroup")
_PROJECT_ID = "polylogue"

Expand Down Expand Up @@ -81,14 +83,14 @@ def require_declared_operation_context(
environment. This does not replace Sinnixd's exact-head or lease validation.
"""
env = os.environ if environment is None else environment
job_id = env.get("SINNIXD_JOB_ID", "")
job_id = runtime_env(env, "JOB_ID") or ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate the prefix migration into the Node proof guards

When dev_loop_proof or live_provider_proof is started by the new runner with only AGENTCTL_JOB_ID, AGENTCTL_PROJECT_ID, and AGENTCTL_OPERATION, this function now accepts the context, but both services copy that same environment into their Node subprocesses and browser-extension/scripts/{dev_loop_shared_chrome_proof,live_provider_proof}.mjs still require the SINNIXD_* names. Both declared operations therefore exit before exercising their proofs under the environment this commit is intended to support; update the Node guards or provide compatible aliases to the child.

Useful? React with 馃憤聽/ 馃憥.

unit = _unit_name(job_id)
expected = {
"SINNIXD_JOB_ID": job_id,
"SINNIXD_PROJECT_ID": _PROJECT_ID,
"SINNIXD_OPERATION": operation,
"JOB_ID": job_id,
"PROJECT_ID": _PROJECT_ID,
"OPERATION": operation,
}
if any(env.get(key) != value for key, value in expected.items()):
if any(runtime_env(env, key) != value for key, value in expected.items()):
raise ValueError("Sinnixd service context does not match the declared operation")
read_cgroup = cgroup_reader or (lambda: _CGROUP_PATH.read_text(encoding="utf-8"))
cgroup = _current_cgroup(read_cgroup)
Expand Down
4 changes: 2 additions & 2 deletions devtools/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from pathlib import Path
from typing import Any

from devtools.agent_env import refuse_verify_tier
from devtools.agent_env import refuse_verify_tier, runtime_env
from devtools.checkout_guard import CheckoutImportMismatchError, assert_polylogue_matches_checkout
from devtools.cloud_sentinels import cloud_sentinel_declined
from devtools.gate import quick_gates
Expand Down Expand Up @@ -130,7 +130,7 @@ def _raise_verification_interruption(signum: int, _frame: Any) -> None:


def _declared_agentctl_operation(raw_argv: Sequence[str]) -> str | None:
operation = os.environ.get("SINNIXD_OPERATION")
operation = runtime_env(os.environ, "OPERATION")
return operation if _AGENTCTL_OPERATION_ARGV.get(operation or "") == tuple(raw_argv) else None


Expand Down
7 changes: 4 additions & 3 deletions devtools/verify_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from pathlib import Path
from typing import Any

from devtools.agent_env import runtime_env
from devtools.pytest_evidence import evaluate_pytest_evidence
from devtools.testmon_provision import TESTMON_DATA_RELPATH

Expand Down Expand Up @@ -263,10 +264,10 @@ def __init__(
# semantic status is still decided by this verifier.
if agentctl_operation is not None:
for field, variable in (
("agentctl_job_id", "SINNIXD_JOB_ID"),
("agentctl_correlation_id", "SINNIXD_CORRELATION_ID"),
("agentctl_job_id", "JOB_ID"),
("agentctl_correlation_id", "CORRELATION_ID"),
):
value = os.environ.get(variable)
value = runtime_env(os.environ, variable)
if value:
self._payload[field] = value
# Kept on every receipt so later classification does not depend on a
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/devtools/test_agent_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,12 @@ def test_every_declared_pytest_pool_operation_classifies_its_own_worker() -> Non
}

assert agent_env.inside_declared_pytest_worker(environment, cgroup_reader=deployed_pytest_cgroup), operation


def test_runtime_env_prefers_the_agentctl_prefix_and_falls_back() -> None:
"""Anti-vacuity: dropping the SINNIXD_ fallback makes the second assertion red."""
from devtools.agent_env import runtime_env

assert runtime_env({"AGENTCTL_JOB_ID": "new", "SINNIXD_JOB_ID": "old"}, "JOB_ID") == "new"
assert runtime_env({"SINNIXD_JOB_ID": "old"}, "AGENTCTL_JOB_ID") == "old"
assert runtime_env({}, "JOB_ID") is None
Loading