rlsbl v0.113.0 /rlsbl.commands.release.release_state
On this page

Release state file: JSON persistence for idempotent release flow, tracking which steps completed so failed releases can resume from the last successful ...

#rlsbl.commands.release.release_state

#rlsbl.commands.release.release_state

Release state file: JSON persistence for idempotent release flow, tracking which steps completed so failed releases can resume from the last successful step.

Tracks which release steps have completed so that a failed release can be resumed without re-executing already-done work.

Canonical step list ------------------- RELEASE_STEPS is the single ordered source of truth for every step a release records. The run guard message, save_step validation, resume skip logic, and auto-clear completeness checks all derive from it — never hardcode step counts or step-name lists elsewhere.

Two marker kinds are recorded in the state file:

  • Success markers (completed_steps list): the step finished (or was

not applicable and is provably done). These gate resume-skip.

  • Failure markers (failed_steps dict of step -> message): the step

ran and failed. They do NOT gate resume-skip (a resume re-attempts the step) — they feed the completion summary. Steps in FATAL_STEPS abort the release when they fail (state preserved, resumable); non-fatal steps record the failure and let the release complete.

A state file is provably complete when every canonical step has a success or failure marker and no fatal step failed (is_state_complete). Only then may the success path clear it.

State file location -------------------

  • Standalone projects and implicit-monorepo packages:

<project_dir>/.rlsbl/releases/in-progress.json

  • Releasable releases (explicit monorepo mode): the state belongs to the

releasable, not the representative member package, and lives at <workspace_root>/.rlsbl-monorepo/releasables/<name>/releases/in-progress.json.

ALL derivations of the state path must go through :func:get_state_path (with the releasable dir from :func:resolve_releasable_dir when in a monorepo) so the run guard, resume CLI, executor, unexpected-files whitelist, and scrub agree on a single location.

The file is written at the start of the mutating phase, deleted on success, and left in place on failure.

#get_state_dir

python
def get_state_dir(project_dir: str, *, releasable_dir: str | None=None) -> str

Return the directory holding release state files.

releasable_dir is the releasable's state directory (.rlsbl-monorepo/releasables/<name>/); when given, state lives in its releases/ subdirectory instead of the project's .rlsbl/releases/. Delegates to the single releases-dir derivation in :mod:rlsbl.release_file (shared with the release-file family: unreleased.toml, v{x}.toml, unreleased.md, v{x}.md).

#get_state_path

python
def get_state_path(project_dir: str, *, releasable_dir: str | None=None) -> str

Return the path to the release state file (in-progress.json).

This is the ONLY function that may derive the state file location. Pass releasable_dir (from :func:resolve_releasable_dir or an already-resolved releasable config dir) for releasable releases.

#get_scrub_result_path

python
def get_scrub_result_path(project_dir: str, *, releasable_dir: str | None=None) -> str

Return the path to the scrub-result.json file (same home as the release state file).

#resolve_releasable_dir

python
def resolve_releasable_dir(project_dir, workspace_root) -> str | None

Resolve the releasable state dir for a project, or None.

Returns <workspace_root>/.rlsbl-monorepo/releasables/<name>/ when project_dir is a member of a releasable in an explicit-mode workspace; None for standalone projects, implicit-mode packages, non-member projects, or when workspace_root is None.

#StateResolutionError

Raised when the resume source cannot be resolved unambiguously.

#resolve_resume_source

python
def resolve_resume_source(workspace_root, cwd='.') -> tuple[str, str]

Resolve (project_dir, state_path) for rlsbl release resume.

  • Standalone (workspace_root is None): state under

<cwd>/.rlsbl/releases/.

  • Inside a member package dir: the releasable-aware state path. If

in-flight state exists only at the legacy per-project location, a :class:StateResolutionError with a migration hint is raised (never silently ignore pre-existing in-flight state).

  • At the workspace root: finds the single releasable whose state file

exists and resolves the representative member (from the saved monorepo_name, falling back to the first member). Errors on zero or multiple in-flight releasables.

#find_releasable_state_files

python
def find_releasable_state_files(workspace_root) -> list[tuple[str, str]]

Scan all releasables in a workspace for in-progress release state.

Returns a sorted list of (releasable_name, state_path) tuples for every releasable whose releases/in-progress.json exists. Used by the resume CLI when invoked from the workspace root.

#save_release_state

python
def save_release_state(state_path: str, state_dict: dict) -> None

Atomically write the release state dict to disk (tmp + os.replace).

#load_release_state

python
def load_release_state(state_path: str) -> dict | None

Read and parse the release state file. Returns None if missing.

#save_step

python
def save_step(state_path: str, step_name: str) -> None

Record a successful step: load, append to completed_steps, save.

Also clears any failure marker for the step (a resume that re-attempts a previously-failed step replaces the failure with success). Raises ValueError for step names not in :data:RELEASE_STEPS.

#save_step_failure

python
def save_step_failure(state_path: str, step_name: str, message: str) -> None

Record a step failure marker with a human-readable message.

Failure markers do NOT gate resume-skip; they feed the completion summary. A failure replaces any prior success marker for the step. Raises ValueError for step names not in :data:RELEASE_STEPS.

#clear_release_state

python
def clear_release_state(state_path: str) -> None

Delete the state file and its parent dir if empty (no-op if already absent).

This is an unconditional removal — used by the success epilogue (after :func:is_state_complete verification), rollback paths (state is useless after a local rollback), and PR-mode handoff (only the local mutating phase is tracked; publishing happens in CI).

#get_failed_steps

python
def get_failed_steps(state: dict) -> dict[str, str]

Return the failure markers dict (step -> message) from a state dict.

#get_missing_steps

python
def get_missing_steps(state: dict) -> list[str]

Return canonical steps that have neither a success nor a failure marker, in canonical order.

#has_fatal_failure

python
def has_fatal_failure(state: dict) -> bool

Return True if any fatal step has a failure marker.

#is_state_complete

python
def is_state_complete(state: dict) -> bool

Return True if the state is provably complete: every canonical step has a success-or-failure marker AND no fatal step failed.

Search