From 29fd64f67e3de295a45b6c911ca9ee02c98692e9 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 21 Oct 2026 20:26:18 -0400 Subject: [PATCH] docs(6205): forge comments state the reason, not the instance that taught it The module header drops the milestone/step map and record citations; the per-user optionality, the keyring model and the empty-selector baseline are stated as present-tense facts. Co-Authored-By: Claude Opus 5.5 --- src/scribe/services/forge.py | 47 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/scribe/services/forge.py b/src/scribe/services/forge.py index c22b96b2..098a3e61 100644 --- a/src/scribe/services/forge.py +++ b/src/scribe/services/forge.py @@ -1,25 +1,24 @@ """Forge adapter — optional server-side READ access to the operator's git forge. -Step 4 of milestone 288 (#2689, decision #2686). The recorded location of a -snippet is the source of truth for its code and the stored body is a cache; -this module is the seam that lets the SERVER read that source of truth, so the -cache can be refreshed at pull time (step 5), drift can be flagged from push -webhooks (step 6), and coverage can be measured (step 7). +The recorded location of a snippet is the source of truth for its code and +the stored body is a cache; this module is the seam that lets the SERVER read +that source of truth, so the cache can be refreshed at pull time, drift can be +flagged from push webhooks, and coverage can be measured. Design constraints, in force everywhere below: - - OPTIONAL per user (rule #115, sharpened by #2778). Connections are + - OPTIONAL per user. Connections are per-user keyring rows resolved by repo host on the PROJECT OWNER's keyring; `get_forges()` returns an empty selector when the owner has - nothing configured, and every consumer must treat that as "keep today's - behavior". A user who never configures a forge is not degraded — that is + nothing configured, and every consumer must treat that as "behave as if + there were no forge". A user who never configures a forge is not degraded — that is the baseline. - READ-ONLY by construction. The adapter exposes reads; there is no write method to misuse. The token an operator mints for it only ever needs read scope, and the docs say so. - - The contract stays as small as its consumers (steps 5-7): read_file / + - The contract stays as small as its consumers: read_file / latest_commit / archive / default_branch / resolve_repo / check. Two - implementations (Gitea, GitHub — step 8) keep it honest; resist widening + implementations (Gitea, GitHub) keep it honest; resist widening it speculatively. - Repo identity is the repo-binding key — `normalize_repo_key`'s host/owner/repo — so the join between a snippet's recorded repo and the @@ -53,12 +52,12 @@ logger = logging.getLogger(__name__) FORGE_KINDS = ("gitea", "github") # Total budget per forge call. Consumers either have a cache to fall back to -# (step 5) or a user watching a button (the test probe) — neither tolerates a +# or a user watching a button (the test probe) — neither tolerates a # hung socket, and there is no retry: the fallback IS the retry policy. _TIMEOUT = httpx.Timeout(5.0) # Archive downloads move a whole-repo tarball and only ever run off the -# request path (coverage recompute, step 7), so they get a bigger budget than +# request path (coverage recompute), so they get a bigger budget than # the per-file reads — but still a bound, because a hung background task # holds a connection slot as surely as a foreground one. _ARCHIVE_TIMEOUT = httpx.Timeout(60.0) @@ -79,13 +78,13 @@ class ForgeFile: """One file read from the forge at a specific point in history.""" content: str - # The commit the content was served at — what provenance stores (#2688). + # The commit the content was served at — what provenance stores. commit_sha: str path: str def host_of(url: str) -> str: - """The lowercase hostname of a URL — the keyring's lookup key (#2778).""" + """The lowercase hostname of a URL — the keyring's lookup key.""" return (urlsplit(url).hostname or "").lower() @@ -199,7 +198,7 @@ class ForgeAdapter: async def latest_commit(self, repo: str, path: str, ref: str = "") -> str: """The newest commit touching ``path`` — "" when it can't be told. - The cached-SHA short-circuit (#2693): when a snippet's provenance + The cached-SHA short-circuit: when a snippet's provenance already names a commit, this one small call can prove the file hasn't moved since — no content transfer, which is what keeps pull-time freshness inside GitHub's rate limits. @@ -213,7 +212,7 @@ class ForgeAdapter: async def archive(self, repo: str, ref: str) -> bytes: """The repo's content at ``ref`` as a gzipped tarball, in one request. - Coverage measurement (step 7) needs every source file's text; per-file + Coverage measurement needs every source file's text; per-file reads would mean one API call per file, so the archive endpoint is the only shape that scales past toy repos. Callers must never run this in a request path — it moves the whole repo. @@ -287,8 +286,8 @@ class GiteaForge(ForgeAdapter): class GitHubForge(ForgeAdapter): - """The GitHub implementation — the second one, which is the point (#2693): - it proves the seam is a contract rather than a Gitea-shaped hole. Works + """The GitHub implementation — the second one, which is the point: it + proves the seam is a contract rather than a Gitea-shaped hole. Works against github.com and GitHub Enterprise; the token is a fine-grained PAT with Contents: Read-only (or a classic token with `repo` read).""" @@ -379,8 +378,8 @@ def build_adapter( ) -> ForgeAdapter | None: """One validated adapter from raw connection values, or None. - None means "this connection cannot serve reads" — the same contract the - old instance-wide lookup had, applied per keyring row. Misconfigurations + None means "this connection cannot serve reads", decided per keyring + row. Misconfigurations are logged, never raised: a bad row must not break the reads the good rows can still serve. """ @@ -403,13 +402,13 @@ def build_adapter( @dataclass(frozen=True) class ForgeSelector: - """The forge reads available to one project owner (#2778). + """The forge reads available to one project owner. Consumers ask it to serve a REPO, not to hand over "the forge": resolve() walks the owner's adapters and returns the (adapter, api_repo) pair for the first one whose host serves the repo — or None, which every consumer - treats exactly as the old "no forge configured" state. An empty selector - IS rule #115's baseline. + treats exactly as "no forge configured". An empty selector is the + baseline every install starts from, not a degraded mode. """ adapters: tuple[ForgeAdapter, ...] = () @@ -431,7 +430,7 @@ async def get_forges( ) -> ForgeSelector: """The forge selector for reads on behalf of ``owner_id``'s records. - The keyring model (#2778): every server-side forge read for a record runs + The keyring model: every server-side forge read for a record runs on the PROJECT OWNER's connections, resolved by repo host — a forge token is a user's credential, and one user's reads must never ride another user's token. Pass the record's ``project_id`` so the per-project pin