orchestrata_ultimate.promotion.identity
Reference › orchestrata_ultimate › promotion › identity
The deployment identity verifier. It proves, or disproves, that the deploy root, its VERSION record and every production-named systemd unit are the same deployed thing. The verify stage calls it as its last check, and it is also a command-line tool through main and build_parser (--root, a required --expect-sha, a test-only --pattern, and --json). The helper modules are identity_scope (data types, parsing, unit discovery), identity_core (process and liveness checks), identity_checks (unit-file path checks), identity_skips and identity_urls.
check_identity(root, expect_sha, pattern, run, proc_root, tools_root) returns an IdentityReport. The shell command runner and the /proc location are injected so the function is pure apart from those seams. It returns early with an error when the expected sha is empty, the root is not a directory, VERSION is unreadable or not in the space-separated key value format, or git rev-parse HEAD fails. It records a mismatch when HEAD differs from VERSION's commit or from the expected sha. It then discovers units in user and system scope whose names match the service globs, adds each service's own sibling timer, and judges every unit: the skip rules, the path directives, the unit-file path check, the liveness check and the process check. A template instance whose program lives under the tools tree is judged against that tree's own VERSION and virtual environment (ADR-0067).
_check_process_identity handles long-running units: a unit with no main process is an error, the process must pass the checks in identity_core, and its start time must not precede the deployed stamp in VERSION. Oneshots and timers are exempt from the process check. Exit codes follow the report: 0 proven, 1 a mismatch was found, 2 the verifier could not inspect something, and an inspection failure outranks a mismatch.
At a glance
| Source | src/orchestrata_ultimate/promotion/identity.py |
| Lines of code | 325 |
| Classes | 0 |
| Functions | 5 |
| Imports (internal) | orchestrata_ultimate.promotion.identity_checks, orchestrata_ultimate.promotion.identity_core, orchestrata_ultimate.promotion.identity_scope, orchestrata_ultimate.promotion.identity_skips |
| Imported by | orchestrata_ultimate.promotion.stages, orchestrata_ultimate.promotion.stages_verify, scripts.verify_deployment_identity |
| Tests that import it | tests/deploy/test_install_orchestrata_version.py, tests/promotion/_identity_fixtures.py, tests/promotion/test_identity_cli.py, tests/promotion/test_identity_integration_basic.py, tests/promotion/test_identity_integration_process.py, tests/promotion/test_identity_integration_scope.py, tests/promotion/test_identity_skips.py, tests/promotion/test_identity_tool_tree_instances.py |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
default_run |
default_run(argv: Sequence[str]) -> subprocess.CompletedProcess[str] |
The real seam: shell out to systemctl / git. |
check_identity |
check_identity(root: Path | str, expect_sha: str, *, pattern: str | None = None, run: RunFn = default_run, proc_root: Path = Path('/proc'), tools_root: Path = TOOLS_ROOT) -> IdentityReport |
Prove (or disprove) that root, its VERSION and every prod-named systemd unit are the same deployed thing. |
_check_process_identity |
_check_process_identity(name: str, properties: dict[str, str], root_resolved: Path, venv_python: Path, version: VersionRecord, proc_root: Path, report: IdentityReport) -> None |
Checks c/d/e -- proc identity + start-time floor. |
build_parser |
build_parser() -> argparse.ArgumentParser |
|
main |
main(argv: list[str] | None = None) -> int |
Source
"""Deployment identity verifier (U-T6).
Proves that the tree at a deploy ROOT, its ``VERSION`` record, and every
prod-named systemd unit are provably the SAME deployed thing. Health
(``active``/``inactive``) is not identity: a unit can sit perfectly healthy
while serving from the wrong checkout.
Amendment 2026-09-23 (reviewer verdict on 32acfb5d7, "safe to merge: NO"):
scope is decided by unit NAME -- ``orchestrata-*.service``/``orchgraph-*.service``
plus, separately, ONLY each discovered service's own sibling ``.timer`` --
never by ``ExecStart``'s argv0 and never by the bare presence of ``*.timer``
(that would count every unrelated host timer as "proven" too). A prod-named
unit is judged no matter where its ``ExecStart`` actually points, and every
path-like token of ``ExecStart*``, ``WorkingDirectory``, ``EnvironmentFiles``
and ``Environment`` must resolve under ROOT or a known system prefix (check
c0); ``ExecStart``'s own argv0 is the exception -- for a ``Type=oneshot``
unit it must resolve strictly under ROOT with no system-prefix exemption,
since D4 skips every /proc check for a oneshot and c0 is the only identity
proof it has. ``VERSION`` is the repo's real ``key value``-per-line format
(``deploy/install-orchestrata.sh``), not ``key=value``.
Amendment 2026-09-23 attempt 4 (reviewer findings against 7998cc949):
``PATH_DIRECTIVES``/``DEFAULT_GLOBS``/``SERVICE_GLOBS`` now live in
:mod:`identity_core` and are imported at module level here -- the earlier
"lazy import inside check_identity" only moved the ``ModuleNotFoundError``
from import time to call time (``scripts`` still was not on ``sys.path`` at
the call site either), so ``check_identity`` now needs nothing from
``scripts`` at all. ``--expect-sha`` is a required, positional argument (the
contract's CLI shape always required it; the optional keyword form was a
bug). Every discovered unit whose Type is not ``oneshot`` is judged as
long-running (proc identity + start-time floor), not just an allowlisted
subset of Types -- and an unparsable/absent ``ExecMainStartTimestamp`` on a
unit with a live ``MainPID`` is cannot-inspect, never a silent pass.
All the actual parsing/checking logic lives in
:mod:`orchestrata_ultimate.promotion.identity_core`; this module sequences
it and is the CLI surface. Every I/O boundary is an injectable seam so tests
run against real transient systemd units, real git repos and real /proc,
never a double of this module's own code (RUL-002):
* ``run`` -- a ``Sequence[str] -> subprocess.CompletedProcess[str]`` callable
for every ``systemctl``/``git`` invocation.
* ``proc_root`` -- the directory that stands in for ``/proc``.
"""
from __future__ import annotations
import argparse
import os
import re
import subprocess
from collections.abc import Sequence
from pathlib import Path
from orchestrata_ultimate.promotion.identity_checks import tool_tree_root
from orchestrata_ultimate.promotion.identity_core import (
PATH_DIRECTIVES as _PATH_DIRECTIVES,
)
from orchestrata_ultimate.promotion.identity_core import (
SERVICE_GLOBS,
UNIT_PROPERTIES,
IdentityReport,
RunFn,
UnitFacts,
VersionRecord,
check_c0,
check_liveness,
check_path_directives,
check_process,
discover,
parse_timestamp,
parse_version,
)
from orchestrata_ultimate.promotion.identity_scope import (
TOOLS_ROOT,
instance_of,
read_tools_version,
systemd_unescape,
)
from orchestrata_ultimate.promotion.identity_skips import resolve_skip
__all__ = ["default_run", "check_identity", "build_parser", "main"]
def default_run(argv: Sequence[str]) -> subprocess.CompletedProcess[str]:
"""The real seam: shell out to systemctl / git.
`TZ=UTC`: `systemctl show` formats timestamps in the caller's local
timezone (confirmed "... EDT" unset vs "... UTC" set, systemd 257).
`LC_ALL=C`: output must not vary by locale. GIT_DIR/GIT_WORK_TREE/
GIT_INDEX_FILE are scrubbed so an inherited git-hook environment can
never redirect `git -C ROOT` at a different repo.
"""
env = dict(os.environ)
env["TZ"] = "UTC"
env["LC_ALL"] = "C"
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"):
env.pop(key, None)
return subprocess.run( # noqa: S603
list(argv), capture_output=True, text=True, timeout=30, check=False, env=env
)
def check_identity(
root: Path | str,
expect_sha: str,
*,
pattern: str | None = None,
run: RunFn = default_run,
proc_root: Path = Path("/proc"),
tools_root: Path = TOOLS_ROOT,
) -> IdentityReport:
"""Prove (or disprove) that ``root``, its ``VERSION`` and every prod-named
systemd unit are the same deployed thing. Pure aside from the injected
``run``/``proc_root`` seams. ``pattern`` narrows discovery FURTHER than
the globs (test isolation only -- production passes None).
A template instance whose argv0 lives under ``tools_root`` is judged
against that tree's own VERSION/venv/cwd, and its ``%I`` parameter is
data, not code (ADR-0067 R1/P3); ``tools_root`` is a test seam only.
``expect_sha`` is required (the contract's CLI shape always required it;
:func:`build_parser` refuses to parse without it too) -- a verifier that
can silently skip its OWN sha comparison is not a verifier."""
root_path = Path(root)
report = IdentityReport(root=root_path)
if expect_sha == "":
report.errors.append("--expect-sha must not be empty")
return report
if not root_path.is_dir():
report.errors.append(f"ROOT {root_path} is not a directory")
return report
root_resolved = root_path.resolve()
version_path = root_path / "VERSION"
try:
version_text = version_path.read_text(encoding="utf-8")
except OSError as exc:
report.errors.append(f"cannot read {version_path}: {exc}")
return report
version = parse_version(version_text)
if version is None:
report.errors.append(
f"{version_path} is missing or not in the `key value` format"
)
return report
git_result = run(["git", "-C", str(root_path), "rev-parse", "HEAD"])
if git_result.returncode != 0:
problem = (git_result.stderr or git_result.stdout or "").strip()
report.errors.append(f"git -C {root_path} rev-parse HEAD failed: {problem}")
return report
sha = git_result.stdout.strip()
report.sha = sha
if sha != version.commit:
report.mismatches.append(
f"ROOT: VERSION.commit: expected {sha}, got {version.commit}"
)
if expect_sha and sha != expect_sha:
report.mismatches.append(f"ROOT: git HEAD: expected {expect_sha}, got {sha}")
compiled_pattern = re.compile(pattern) if pattern else None
venv_python = (root_path / ".venv" / "bin" / "python").resolve()
# Amendment finding 1: scope is orchestrata-*/orchgraph-* SERVICES, plus
# -- separately, per discovered service -- ONLY that service's own
# sibling `.timer` (same base name). A bare host timer (logrotate.timer,
# man-db.timer, ...) is never in scope just because a unit named that
# exists; the previous "*.timer" glob discovered EVERY timer on the
# host and counted every one of them as "proven".
discovered: list[tuple[str, str]] = []
for scope in ("user", "system"):
names, error = discover(run, scope, SERVICE_GLOBS, compiled_pattern)
if error is not None:
report.errors.append(error)
continue
for name in names:
discovered.append((name, scope))
timer_name = re.sub(r"\.service$", ".timer", name)
timer_names, timer_error = discover(run, scope, (timer_name,), None)
if timer_error is not None:
report.errors.append(timer_error)
continue
discovered.extend((timer, scope) for timer in timer_names)
tools_versions: dict[Path, VersionRecord | None] = {} # read lazily, once
for name, scope in discovered:
properties, skip_reason, error = resolve_skip(name, scope, run, UNIT_PROPERTIES)
if skip_reason is not None:
report.skipped.append(f"skipped {name} ({skip_reason})")
continue
if error is not None:
report.errors.append(error)
continue
unit_root, unit_python, unit_version = root_resolved, venv_python, version
data: tuple[str, ...] = ()
tools = tool_tree_root(name, properties, tools_root)
if tools is not None:
if tools not in tools_versions:
tools_versions[tools] = read_tools_version(tools, report.errors)
tools_version = tools_versions[tools]
if tools_version is None:
continue # cause already in report.errors, once per tree
unit_root = tools.resolve()
unit_python = (unit_root / ".venv" / "bin" / "python").resolve()
unit_version = tools_version
data = ("/" + systemd_unescape(instance_of(name)),)
unit = UnitFacts(name=name, scope=scope, properties=properties, root=tools)
report.units.append(unit)
check_path_directives(unit, _PATH_DIRECTIVES, report) # check g
check_c0(unit, unit_root, report, data_prefixes=data) # check c0
check_liveness(unit, run, scope, name, report) # check f
_check_process_identity(
name,
properties,
unit_root,
unit_python,
unit_version,
proc_root,
report,
)
if not report.units and not report.errors and not report.skipped:
report.errors.append("no unit discovered in scope")
return report
def _check_process_identity(
name: str,
properties: dict[str, str],
root_resolved: Path,
venv_python: Path,
version: VersionRecord,
proc_root: Path,
report: IdentityReport,
) -> None:
"""Checks c/d/e -- proc identity + start-time floor.
Amendment finding 5: EVERY Type that is not ``oneshot`` is long-running
and judged here -- there is no allowlist of "checked" Types (idle, dbus,
notify-reload and an unset Type used to skip this entirely and count as
trivially proven). A ``.timer`` unit is the one structural exception: it
has no ``MainPID``/``Type`` of its own (verified on this host, see
``identity_core.check_liveness``'s docstring) so it is exempted by NAME,
not by Type."""
if name.endswith(".timer"):
return
if properties.get("Type", "") == "oneshot":
return # D4: an in-flight oneshot legitimately runs something else
try:
pid = int(properties.get("MainPID", "0") or "0")
except ValueError:
pid = 0
if pid <= 0:
# D2: never silently dropped -- a long-running unit with no process
# cannot have its identity proven at all.
report.errors.append(
f"{name}: no process (MainPID=0) -- cannot verify cwd/cmdline/exe"
)
return
check_process(pid, proc_root, root_resolved, venv_python, name, report)
raw_start = properties.get("ExecMainStartTimestamp", "")
start_time = parse_timestamp(raw_start)
if start_time is None:
# Amendment finding 9: systemd always sets this the moment a real
# process starts, so an unparsable/absent value on a unit that DOES
# have a live MainPID is a real inconsistency -- cannot-inspect,
# never a silent pass.
report.errors.append(
f"{name}: ExecMainStartTimestamp: cannot parse {raw_start!r} "
f"for a running process (MainPID={pid})"
)
elif start_time < version.deployed:
report.mismatches.append(
f"{name}: start-time: expected >= {version.deployed.isoformat()}, "
f"got {start_time.isoformat()}"
)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="verify_deployment_identity.py",
description=(
"Prove that ROOT, its VERSION record, and every prod-named "
"systemd unit are the same deployed thing."
),
)
parser.add_argument(
"--root",
default="/opt/orchestrata",
help="deploy root (default: /opt/orchestrata)",
)
parser.add_argument(
"--expect-sha",
required=True,
help="git sha the deploy must match (required)",
)
parser.add_argument(
"--pattern", default=None, help="extra regex narrowing discovery (tests only)"
)
parser.add_argument(
"--json", action="store_true", help="emit the full report as JSON"
)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
report = check_identity(
args.root, args.expect_sha, pattern=args.pattern, run=default_run
)
if args.json:
import json
print(json.dumps(report.as_dict(), indent=2, default=str))
else:
print(report.render())
return report.exit_code
if __name__ == "__main__":
raise SystemExit(main())