On this page
Layered evidence gate that checks multiple sources to determine whether a release was published, producing CLEARED or BLOCKED verdicts.
#rlsbl.evidence_gate
#rlsbl.evidence_gate
Layered evidence gate that checks multiple sources to determine whether a release was published, producing CLEARED or BLOCKED verdicts.
The gate checks multiple evidence sources in order and produces a verdict: CLEARED (safe to undo) or BLOCKED (may have been published).
Evidence sources are extensible -- new sources can be added by implementing the EvidenceSource protocol and registering them in the sources list.
Current sources:
- RegistryProbeSource: uses publication_probe() from target implementations
(npm, pypi, go)
- CachedRegistrySource: asks the registry itself, for targets whose primary
probe answers from somewhere else (Go's asks the git remote; see the class docstring)
Future sources (not yet implemented):
- CIPublishRunSource: checks GitHub Actions workflow conclusions
- LocalStateSource: checks in-progress.json completed_steps
- GitHubReleaseAssetSource: checks for uploaded assets
#Verdict
Result of the evidence gate.
#EvidenceKind
What kind of evidence a source provides.
#Evidence
A single piece of evidence from one source about one target.
Attributes:
source: Name of the evidence source (e.g. "registry_probe").target: Name of the target (e.g. "npm", "pypi").kind: Whether this evidence says PUBLISHED, UNPUBLISHED, or INCONCLUSIVE.message: Human-readable detail.
#to_dict
def to_dict(self)#EvidenceSource
Protocol for evidence sources.
#name
def name(self) -> strUnique name for this evidence source.
#gather
def gather(self, targets, project_dir, version, ctx=None) -> list[Evidence]Gather evidence for the given targets and version.
Args:
targets: list of target objects from TARGETS registry.project_dir: path to the project directory.version: the version string (without 'v' prefix).ctx: optional project context.
Returns:
- list of Evidence objects, one per target checked.
#RegistryProbeSource
Evidence source using target.publication_probe().
#name
def name(self)#gather
def gather(self, targets, project_dir, version, ctx=None)#CachedRegistrySource
Evidence source asking the registry itself, for targets that have a second probe.
A target's primary publication_probe does not have to ask the registry. Go's asks the GIT REMOTE whether the version's tag exists -- the right question for "did we tag this?", and the wrong one for "is this out in the world?": proxy.golang.org caches a module version PERMANENTLY the first time anyone resolves it, so a tag deleted after someone fetched it is gone from the remote and still served by the proxy forever. Reading the tag's absence as "never published" is how a destructive operation gets cleared for a version consumers can still download today.
Which targets have such a second probe is the target's own answer (supports_cached_registry_probe), not a name this module knows.
The source contributes only two kinds, never three:
- PUBLISHED -- the registry serves it. Under the gate's rule that alone
blocks, which is the whole point.
- INCONCLUSIVE -- it does not, or could not be asked.
Absence is deliberately never reported as UNPUBLISHED: a lazily-indexed registry is absent-by-default for a version nobody has fetched, so registry lag alone must never be the evidence that clears a deletion. Something that really did observe the version's absence has to say so.
#name
def name(self)#gather
def gather(self, targets, project_dir, version, ctx=None)#GateResult
Result of running the evidence gate.
Attributes:
verdict: CLEARED or BLOCKED.evidence: all evidence gathered from all sources.reason: human-readable explanation of the verdict.
#to_dict
def to_dict(self)#run_evidence_gate
def run_evidence_gate(targets, project_dir, version, ctx=None, sources=None)Run the layered evidence gate to determine if a release is safe to undo.
Decision rule:
- CLEARED: at least one authoritative source says UNPUBLISHED and none says PUBLISHED.
- BLOCKED: any source says PUBLISHED.
- BLOCKED (hard error): no authoritative evidence at all (all INCONCLUSIVE).
Args:
targets: list of target objects from TARGETS registry.project_dir: path to the project directory.version: the version string (without 'v' prefix).ctx: optional project context.sources: list of EvidenceSource objects; defaults to DEFAULT_SOURCES.
Returns:
- GateResult with verdict and evidence.
#write_undo_audit
def write_undo_audit(audit_dir, version, tag, gate_result, operator_context=None)Write an audit record for a non-latest undo operation.
Creates undo-audit.json in the given directory with per-target evidence, verdict, and operator context.
Args:
audit_dir: directory to write the audit file to (e.g. .rlsbl/ or
the releasable dir).
version: the version that was undone.tag: the git tag that was deleted.gate_result: the GateResult from run_evidence_gate().operator_context: optional dict with additional context.
Returns:
- path to the written audit file.
Raises:
RlsblError: an existingundo-audit.jsonthat cannot be parsed. The
append is a whole-file rewrite, so continuing would replace the records already in it.