Skip to content
rlsbl.ownership
On this page

The questions answered here alone: whether a path is rlsbl's own tool-owned bookkeeping, which workspace member owns it (most specific path wins), and what a releasable's changelog scope claims beyond its members' files.

#rlsbl.ownership

#rlsbl.ownership

File ownership: the tool-owned exempt set, single-owner attribution, and the scope claims a releasable makes on its own state directory.

The questions answered here, and nowhere else:

  1. Is this path tool-owned? :func:is_tool_owned_path decides from static

path rules alone -- no config reads, no git calls, no workspace lookups. A tool-owned path is rlsbl's own bookkeeping (changelog state, release state, the workspace directory, the generated CI router), so it never needs a changelog owner and never participates in attribution.

  1. Which workspace member owns this path? :func:owner_of answers with

exactly one member: the most specific declared member path wins, and the root member (path = ".") owns everything no other member claims.

  1. Is this path inside the scope I am asking about?

:class:OwnershipScope answers, and it is the only question a releasable participates in: a releasable is not a member, so it owns no file, but its scope claims its own state directory (.rlsbl-monorepo/releasables/<name>/) on top of its members' files.

Kept free of imports from git, workspace, targets and checks so every layer can depend on it -- :mod:rlsbl.workspace_types is the one exception, for the directory names, and it imports nothing but :mod:rlsbl.errors. Commit-level attribution (which needs git) is in :mod:rlsbl.git_util, which imports this module.

#OwnershipError

File attribution could not be performed.

#releasable_state_dir

python
def releasable_state_dir(releasable_name) -> str

Repo-relative path of a releasable's own state directory.

.rlsbl-monorepo/releasables/<name> -- the changelog, the release archives, the version, the transition record and the config of one releasable. No member's declared path claims it (it is tool-owned, and a releasable is not a member), so it is the releasable itself that claims it, at the scope level: see :meth:OwnershipScope.for_releasable.

#state_dir_releasable

python
def state_dir_releasable(path) -> str | None

The releasable whose state directory path sits in, or None.

The inverse of :func:releasable_state_dir. A path under .rlsbl-monorepo/releasables/<name>/ belongs to no member -- it is tool-owned, and a releasable is not a member -- so this is the only way to answer "whose is this?" for it. Used by messages that have to name the owner of a file the asking scope does not claim.

#normalize_path

python
def normalize_path(path) -> str

Return path with \ separators folded and trailing slashes gone.

#is_root_path

python
def is_root_path(path) -> bool

True when path is one of the spellings that mean the repository root.

The root member declares path = "."; "" and "./" are the other accepted spellings of the same territory. Anything that prefixes a member path has to ask, because prefixing the root produces a path with a leading ./ that is not the repository root's real spelling -- and for a git tag it is not even a legal ref name.

#tool_owned_rule

python
def tool_owned_rule(path) -> str | None

Return the static rule making path tool-owned, or None.

The returned string is the rule itself (".rlsbl/changes/**", "CHANGELOG.md"), suitable for putting in a message that has to explain why a path needs no owner. Root-relative rules answer only for a path at the repository root; the rest answer at any depth.

#is_tool_owned_path

python
def is_tool_owned_path(path) -> bool

Is path rlsbl's own bookkeeping, and therefore exempt from ownership?

#member_path

python
def member_path(member) -> str

Return a member's declared path, normalized ("" for the root member).

#member_name

python
def member_name(member) -> str

Return a member's name.

#is_root_member

python
def is_root_member(member) -> bool

Does member declare the repository root as its territory?

#member_prefix

python
def member_prefix(member) -> str

Return the path prefix a member's files carry.

"" for the root member -- it claims every path -- and "pkg/" for a member at pkg. The root member's prefix used to be computed as "./", which matches no path git ever prints, so a root member owned nothing at all.

#find_root_member

python
def find_root_member(members)

Return the root member of members, or None when there is none.

#owner_of

python
def owner_of(filepath, members)

Return the single member owning filepath, or None.

None means one of two things, both of which the caller must be able to live with: the path is tool-owned (:func:is_tool_owned_path), or the member list has no root member and no declared path claims the file. A loaded workspace always has a root member, so within a real workspace only the first case occurs.

Most specific wins: with members at ., pkg and pkg/inner, the file pkg/inner/a.py belongs to pkg/inner alone.

#member_for_directory

python
def member_for_directory(dirpath, members, *, include_root)

Return the member whose territory a directory falls in.

The same most-specific-path rule :func:owner_of uses, minus the tool-owned exclusion: a directory is a place to run a command from, not a file needing a changelog owner.

include_root decides whether the root member may answer. It is mandatory because the two questions it separates are genuinely different: file attribution always gives the root member the residual, while "which project am I standing in?" is a question several commands answer with "none of them, you are at the workspace root" and act on. Callers state which they are asking.

#_most_specific_claim

python
def _most_specific_claim(normalized, members)

The most specific member path claiming normalized; root is the residual.

#owner_name_of

python
def owner_name_of(filepath, members) -> str | None

Name of the member owning filepath, or None (see :func:owner_of).

#owner_names_of_files

python
def owner_names_of_files(files, members) -> set

Return the set of member names owning any path in files.

#OwnershipScope

A question asked of attribution: which members' files am I after?

Attribution needs the whole member list to answer at all -- a file under pkg/inner belongs to pkg/inner even when the caller only cares about pkg, and a root file belongs to the root member even when the caller only cares about pkg. Handing a function the members it cares about and nothing else is what let a file be claimed by two members at once, so the two halves travel together: members is every member in the workspace, owned names the subset in scope.

state_dirs is the third half, and the one thing here that is not a member question. A releasable's own state directory -- its changelog, its release archives, its version -- belongs to no member: no declared path claims it, and inventing a phantom member for it would break the invariant that every file has exactly one member owner. A RELEASABLE's scope claims it directly instead, so archiving that releasable's release file and finalizing its changelog are commits inside that releasable's scope rather than outside every scope in the workspace.

#for_members

python
def for_members(cls, all_members, scope_members) -> 'OwnershipScope'

Scope covering scope_members, claiming no state directory.

#for_releasable

python
def for_releasable(cls, all_members, scope_members, releasable_name) -> 'OwnershipScope'

Scope covering a releasable: its members AND its state directory.

#for_member

python
def for_member(cls, all_members, one_member) -> 'OwnershipScope'

Scope covering exactly one member.

#owner_name_of

python
def owner_name_of(self, filepath) -> str | None

Name of the member owning filepath (any member, in scope or not).

#claims_state_dir

python
def claims_state_dir(self, filepath) -> bool

Does filepath sit inside a state directory this scope claims?

#claims

python
def claims(self, filepath) -> bool

Is filepath in scope -- owned by a member, or in a state directory?

The two answers cannot collide: a state directory is tool-owned, so it has no member owner to disagree with. Being claimed here says which releasable a path belongs to; it says nothing about whether a changelog entry is REQUIRED for it, which the tool-owned exempt set still answers with "no".

#claims_any

python
def claims_any(self, files) -> bool

Is any path in files owned by a member in scope?

#owned_members

python
def owned_members(self) -> list

The in-scope members themselves, in workspace declaration order.

#describe

python
def describe(self) -> str

A short human name for the scope, for error messages.

A releasable's state-directory claim is named alongside its members: the directory belongs to no member, so a description built from the member names alone reads as if it were outside the scope -- and the message that carries it would then contradict what :meth:claims answers.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search