Committed profiles and retained evidence

The original localci command runs one command in your current checkout. It remains available. Use localci validate when checks must run from committed source and produce retained evidence. Use localci report to publish that record later. Validation needs neither GitHub credentials nor network access when the project's declared commands and Docker image are already available.

Declare a small ordered profile in pyproject.toml:

[tool.localci.execution.profile]
backend = "host"

[[tool.localci.execution.profile.phases]]
name = "tests"
command = ["python", "-m", "unittest"]
timeout_seconds = 120

[[tool.localci.execution.profile.phases]]
name = "build"
command = ["make", "bundle"]
timeout_seconds = 120

[[tool.localci.execution.profile.artifacts]]
name = "bundle"
path = "source/dist/bundle.tar"
required = true
phase = "build"

[tool.localci.execution.reporting.contexts]
tests = "Local checks / tests"
build = "Local checks / bundle"

Host commands inherit the operator's environment and explicitly configured profile.settings.environment values. They run in the disposable snapshot, not the working checkout. Arrange the required tools and dependencies first. LocalCI does not install dependencies, copy ignored files, or create worktrees.

For Docker, set backend = "docker" and declare an immutable image:

[tool.localci.execution.runner]
image = "sha256:<64 lowercase hexadecimal characters>"

[tool.localci.execution.profile.settings.environment]
EXAMPLE_SETTING = "value"

Replace the placeholder with a local image ID. Docker is optional, and LocalCI never installs it or pulls/builds an image. It checks the exact image identity, uses --pull=never, --init, dropped capabilities and no-new-privileges, mounts the writable snapshot at /workspace, and defaults to offline networking. Each phase can explicitly choose network = "bridge" instead of "none". Containers receive only the declared environment values. Host variables are not implicitly forwarded. Keep credentials out of configuration and argv, since both belong to the retained execution record.

Large profiles can use a separate committed file:

[tool.localci]
execution_config = "ci/execution.toml"

That file contains [runner], [profile], [[profile.phases]], [[profile.artifacts]], and [reporting.contexts], without the tool.localci.execution prefix. localci validate --config ci/execution.toml selects it directly. The selected commit supplies the configuration, even if the working checkout has different edits.

localci validate HEAD
localci validate <commit> --evidence /path/to/new-empty-directory --json
localci validate HEAD --phase build

--phase chooses an ordered subset. Repeat it for several phases. --timeout changes deadlines for this invocation. With exactly one selected phase, --command accepts a repeated argv override. The resulting record contains the actual selection, commands, deadlines, settings and overrides. Python callers can additionally pass explicit environment overrides to execution.validate. A successful isolated build is distinguishable from complete validation.

Each required artifact is checked after its declared producing phase. Paths are relative to the evidence directory; snapshot outputs begin with source/. Artifact paths and symlinks must stay within the evidence directory. Execution stops after a failed, timed-out, or interrupted phase. Later phases are recorded as unexecuted, with no exit code or log. The phase deadline covers output streaming, including a silent process or a child that keeps stdout open.

The evidence directory retains source.tar, source/, individual phase logs, and result.json, with owner-only record and log permissions. Records use format = "localci.execution" and version = 1. They contain source commit, tree and archive checksum; runner version and image; the effective profile; phase outcomes, elapsed times and log checksums; and artifact checksums. success means the requested profile completed, including required artifacts. It does not imply deployment or authorize another project's full validation gate.

Checksums detect changed retained bytes. They are not signatures against an operator rewriting the entire record. Project consumers decide which execution profile is sufficient for deployment or accepted-merge reuse.

To report retained results, supply the intended published commit:

localci report /path/to/evidence --sha <full-commit> --repository acme/widget
localci report /path/to/evidence --sha <full-commit> --comment --issue 123

Before posting, LocalCI checks retained bytes against their checksums, reproduces the source archive from Git, verifies the selected committed configuration and actual overrides, and compares the requested repository/commit with the record. The caller resolves the intended published branch or PR head and supplies that SHA. LocalCI does not infer deployment or merge policy. Source/image/profile identities cannot be changed silently, and phases omitted from a partial run are reported as unexecuted failures. A reporting failure does not rewrite the execution record, so reporting can be retried without rerunning checks.

GitHub setup and credential behavior are the same as the original command. Validation exits 0 for requested-profile success, 1 for failed checks/artifacts, 2 for configuration/setup failures, and 130 for interruption. Reporting exits 3 when publication fails. JSON validation prints one result locator on stdout and sends execution output to stderr. Simple single-command users retain their existing command, dirty-checkout behavior, and exit-code guarantees.

One command and one bundle

A project does not need a phase for every check. Declare one phase that invokes its existing validation-and-build script, and one required artifact produced by that script. This uses one container, one log, and one status context. The script can print individual command timings and must exit nonzero if any required check fails or the build cannot complete. LocalCI does not interpret test skips; projects that require particular tests to execute must enforce that in the script.

Consuming evidence from Python

The public integration functions are execution.validate, evidence.read, evidence.verify_source, and execution_reporting.report. They use Path arguments. validate returns the evidence directory even when checks fail; inspect the verified record before accepting its artifact. Configuration and handled Git errors raise LocalCIError (from localci.errors). Filesystem and source archive operations can also raise their underlying Python exceptions. Reporting does not rerun validation.

For example, a project adapter can supply the selected commit to its build:

from pathlib import Path
from localci import evidence, execution

root = Path.cwd()
sha = selected_full_commit_sha  # Selected by the project's adapter.
directory = execution.validate(
    root,
    sha,
    overrides={"environment": {"BUILD_SHA": sha}},
)
record = evidence.verify_source(directory, root)
if not record["success"]:
    raise RuntimeError(f"Validation failed; inspect {directory}")

The environment override is forwarded to host or Docker execution and retained in record["profile"]["overrides"]. It is public evidence, so use it only for non-secret values. The CLI does not expose environment overrides; use the Python API when a project needs them.

Before deploying, a consumer must additionally require its intended full commit, image, effective profile, reporting contexts, and allowed overrides. A successful subset or overridden command is not necessarily the consumer's required gate. Verify any identity embedded in the artifact too. LocalCI verifies what ran; the consumer decides which runs it accepts. Do not relabel a candidate's record or bundle with a merge commit's SHA. Keep deployment receipts separate from the execution record, and reuse verified artifact bytes for deployment retries.

evidence.read(directory) verifies the v1 record and retained checksums. evidence.verify_source(directory, root) additionally binds those bytes and the effective configuration to the local Git history. The source commit must remain available locally. Checksums do not authenticate the operator who created them.

Choosing the execution mode

The original localci command is appropriate when checks need the checkout's Git metadata, existing virtual environment, or other locally prepared files. It remains the small adoption path for projects that already have one check command. Committed validation is an additional choice, not a migration requirement.

validate extracts git archive: it has no .git directory, ignored virtual environments, or populated submodules. Archive attributes can exclude files, and Git LFS content is not hydrated by LocalCI. Make the declared command work with that source snapshot and prepared image, or keep using checkout execution. LocalCI does not install Git, gh, test tools, or project dependencies in an image. Tests that need Git should create their own disposable fixtures, with Git already available in the declared execution environment.