app.sources.filesystem

Filesystem source adapter: ADR files in a directory tree on disk.

Each immediate subdirectory of the configured root is a project (optionally filtered by scope.projects). ADR files are discovered recursively under the registration path inside each project, using the legacy filename pattern (app.domain.titles.is_adr_filename()). The change token is <mtime_ns>:<size> from os.stat(), so an unchanged file is never re-read. Directory walks and reads run in a worker thread via asyncio.to_thread(), since pathlib and os are blocking.

There is no file:// URL: FilesystemAdapter.fetch() returns the absolute on-disk path as a human-readable source_url instead. It is shown only to operators with filesystem access, so a plain path is the most useful “link” available; author is always None (the filesystem has no commit/push metadata).

An unreadable or missing root raises SourceNotFoundError or SourceAccessError from FilesystemAdapter.list_projects(), which fails the whole poll (the port contract then deletes nothing). A project’s ADR directory that is simply absent is not an error (no files); one that exists but cannot be listed (or whose walk hits an unreadable subdirectory) is reported as an incomplete document listing for that project only, so polling preserves its stored records.

Public API: validate_settings(), FilesystemAdapter.

class app.sources.filesystem.FilesystemAdapter(*, root, path, published_ref, projects=frozenset({}))

Bases: object

Reads ADR files from a directory tree on disk.

Parameters:
  • root (Path) – Root directory (one immediate subdirectory per project); may be relative to the process working directory only if the caller already resolved it.

  • path (str) – ADR path inside each project (POSIX, relative; possibly empty for the project root).

  • published_ref (str) – Ref label reported for every project (the registration’s configured published_ref; the filesystem has no real refs).

  • projects (frozenset[str]) – Project names to include; empty means every immediate subdirectory of root.

root: Path
path: str
published_ref: str
projects: frozenset[str]
async list_projects()

List the in-scope immediate subdirectories of root.

Return type:

ProjectListing

Returns:

The listing (always complete; a problem here instead raises).

Raises:
async list_documents(project, previous_tokens)

List the ADR files of one project.

Parameters:
Return type:

DocumentListing

Returns:

The listing; complete=False with a problem if the project’s ADR directory exists but could not be fully read.

async fetch(document)

Read one document’s exact bytes.

Parameters:

document (SourceDocument) – A document from list_documents().

Return type:

FetchedDocument

Returns:

Its content; source_url is the absolute path on disk (there is no file:// URL); author is always None.

Raises:
app.sources.filesystem.validate_settings(settings, env)

Validate root and scope.projects; bind the adapter factory.

No filesystem I/O happens here; root is only resolved against settings.base_dir when relative.

Parameters:
  • settings (SourceSettings) – Common settings plus options (root) and scope (projects, optional).

  • env (Mapping[str, str]) – Unused; the filesystem adapter needs no secrets.

Return type:

SourceBinding

Returns:

The binding.

Raises:

app.sources.port.SourceConfigError – If root or scope are invalid.