Skip to content
rlsbl.preview_apply
On this page

The command-neutral observe, preview, apply skeleton every rlsbl reconciler is built on: keyed verdict items, one ordered preview, and a single renderer.

#rlsbl.preview_apply

#rlsbl.preview_apply

The command-neutral observe -> preview -> apply skeleton.

Several rlsbl commands are reconcilers: they look at some subject (a mirror remote, a converted repository, a published version), decide which of a fixed set of states it is in, report that decision as a plan, and -- only when the caller asked for it -- perform the writes the plan named. The shape was first written out inside monorepo mirror; this module is that shape with the mirror taken out of it.

The three pieces ----------------

  • :class:VerdictItem -- one subject's verdict: a key naming the subject,

a state naming which classification it fell into, the observed facts behind that classification, and the actions an apply would take. A reconciler that judges one whole repository emits a one-item preview; one that judges every package in a workspace emits an item per package.

  • :class:Preview -- the ordered list of those items, and nothing else. The

order is the reconciler's own, preserved verbatim.

  • :func:render_preview -- the ONE renderer. Every reconciler's plan output

is produced here, so the plan of a command written next year reads like the plan of the one written today.

The no-writes line ------------------

:func:reconcile is the entry skeleton, and its body is deliberately short enough to read in one glance: observation runs first, inside :func:no_writes, and every write happens after the dry_run branch. Observation is not merely documented as read-only -- for the duration of the observe call this module swaps rlsbl's mutation entry points (:mod:rlsbl.effects) for versions that raise :class:ObserveWriteError, and screens every effects.run argv against :data:rlsbl.observe_allowlist.OBSERVE_ALLOWLIST. A reconciler whose "observation" quietly pushes a branch fails loudly at the attempt instead of silently making --dry-run a lie.

One authority, not two. The screen is an allowlist because the question "may this program run while we are only looking?" already has an answer, and it is the observe allowlist -- the same list strictcli consults to decide which argv really executes under --dry-run. This guard used to carry a private denylist of mutating git subcommands instead, and an opposite-polarity second authority answers the same question differently the moment either side moves: git subtree push, git clean -fdx, git config --global, git remote add, git fetch --prune, git init and any non-git argv at all were all absent from that denylist and therefore ran.

What the guard does NOT cover, stated plainly so nobody assumes otherwise:

  • Scratch directories. effects.mkdtemp stays live and, inside

:func:effects.observe_scratch_dirs (which :func:no_writes enters), creates a REAL directory even under a preview -- observing a remote means cloning it somewhere, and an allowlisted clone that really runs needs a parent that really exists. effects.rmtree is restricted to those tracked paths: deleting anything else during observation raises.

  • **effects.gh.** gh calls go through effects.run and are screened by

the same prefixes, so a gh verb absent from the allowlist is refused rather than allowed. What is still missing is a real read/write classification of gh's verbs; until the publication reconciler needs one, the allowlist's handful of gh reads is the whole vocabulary an observation may use.

  • Grandchildren. Only the argv rlsbl itself launches is screened. A

command like git subtree split spawns its own git processes, which the screen never sees (and does not need to: it writes no refs).

All writes -- guarded phase or not -- go through :mod:rlsbl.effects, which is what makes them previewable and recordable under strictcli's effects regime.

#ObserveWriteError

A mutation was attempted during observation, above the no-writes line.

#VerdictItem

One subject's verdict in a preview.

Args:

  • key: names the subject judged (a project name, a package name, a

version). Unique within a preview; the renderer can show it.

  • state: the classification, in snake_case. The set of states is

the reconciler's own closed vocabulary.

  • summary: the one-line headline printed after the state label.
  • label: overrides the printed state label. Defaults to state with

underscores turned into hyphens, which is what a reader expects to see on a command line.

  • facts: observed facts behind the verdict, one per rendered line,

indented under the headline.

  • actions: what an apply WOULD do, one per rendered line, indented under

the facts. Empty for a state that needs no action.

  • detail: a free-form block rendered verbatim (its own indentation is

preserved). For guidance too long to be a fact line.

  • data: the reconciler's own observation record, carried untouched from

observe to apply so the apply step never re-derives it.

#state_label

python
def state_label(self) -> str

The label the renderer prints for this item's state.

#Preview

An ordered list of :class:VerdictItem, and nothing else.

#keys

python
def keys(self) -> tuple[str, ...]

#states

python
def states(self) -> tuple[str, ...]

#by_key

python
def by_key(self, key: str) -> VerdictItem | None

#only

python
def only(self) -> VerdictItem

The single item of a one-subject preview (hard error otherwise).

#single

python
def single(item: VerdictItem) -> Preview

The one-item preview -- a whole-repository verdict is this case.

#render_preview

python
def render_preview(preview: Preview, *, show_keys: bool, out=None) -> None

Print preview as a human-readable plan.

Args:

  • preview: the plan to render.
  • show_keys: prefix each headline with the item's key. A reconciler

that judges one subject passes False (the key would be noise); one that judges many passes True. No default: which one a command wants is a decision it must state.

  • out: stream to print to. Resolved at call time so a caller (or

pytest's capture) can rebind sys.stdout.

#git_subcommand

python
def git_subcommand(argv) -> str | None

The git subcommand in argv, or None when argv is not a git call.

#observe_allowed

python
def observe_allowed(argv) -> bool

True when argv matches an observe-allowlist prefix.

Element-wise string equality against each entry's prefix -- exactly how strictcli matches its proc_observe_allowlist, so "runs during observation" and "really executes under --dry-run" are the same set. A shell string (shell=True) matches nothing: the allowlist is about argv, and a shell line is not one.

#no_writes

python
def no_writes()

Refuse rlsbl's mutation entry points for the length of the block.

Three things, restored on the way out (including when the block raises):

  • the names in :data:FORBIDDEN_DURING_OBSERVE become raisers;
  • effects.run is screened by :func:observe_allowed, so an argv that

is not on the observe allowlist raises instead of running;

  • effects.rmtree is restricted to scratch this block's own

effects.mkdtemp created, which is also what makes those directories real under a preview (see :func:effects.observe_scratch_dirs).

#Reconciler

A reconciler: how to observe, how to apply, and how to render.

Args:

  • observe: called with no arguments, returns a :class:Preview. Runs

under :func:no_writes.

  • apply_item: called once per item, in preview order, only outside a

dry run. Every write the reconciler performs happens here.

  • show_keys: passed straight to :func:render_preview.

#reconcile

python
def reconcile(reconciler: Reconciler, *, dry_run: bool, out=None) -> Preview

Observe, then either render the plan or apply it.

Returns the preview either way, so a caller can inspect what was judged.

Search