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.mkdtempstays 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 througheffects.runand 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, insnake_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
def state_label(self) -> strThe label the renderer prints for this item's state.
#Preview
An ordered list of :class:VerdictItem, and nothing else.
#keys
def keys(self) -> tuple[str, ...]#states
def states(self) -> tuple[str, ...]#by_key
def by_key(self, key: str) -> VerdictItem | None#only
def only(self) -> VerdictItemThe single item of a one-subject preview (hard error otherwise).
#single
def single(item: VerdictItem) -> PreviewThe one-item preview -- a whole-repository verdict is this case.
#render_preview
def render_preview(preview: Preview, *, show_keys: bool, out=None) -> NonePrint 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
def git_subcommand(argv) -> str | NoneThe git subcommand in argv, or None when argv is not a git call.
#observe_allowed
def observe_allowed(argv) -> boolTrue 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
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_OBSERVEbecome raisers; effects.runis screened by :func:observe_allowed, so an argv that
is not on the observe allowlist raises instead of running;
effects.rmtreeis 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
def reconcile(reconciler: Reconciler, *, dry_run: bool, out=None) -> PreviewObserve, then either render the plan or apply it.
Returns the preview either way, so a caller can inspect what was judged.