feat(ledger): a repo binding names the branch its ledger follows — bind_repo(ref=) (#2873, milestone 294)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Failing after 25s
CI & Build / TypeScript typecheck (push) Canceled after 30s
CI & Build / Python tests (push) Canceled after 30s
CI & Build / Build & push image (push) Canceled after 0s

Project 2 is bound to main, so every consolidation of the 2026-08 audit was
invisible to the ledger until the dev→main merge; the operator works on dev
(rule 1). repo_bindings.ref (migration 0082, nullable) is the branch the
coverage refresh reads; NULL keeps the forge default branch. set_binding takes
ref (name sets, "" clears, None leaves standing); bindings_for_project feeds
the refresh; bind_repo exposes ref ("-" clears). to_dict carries it.

Operator decision on #2873 (2026-08-21): per-binding ref, chosen at bind time,
default the repo default branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 15:10:54 -04:00
co-authored by Claude Fable 5
parent 57d68c9355
commit 1209e1c2d9
6 changed files with 108 additions and 9 deletions
+6 -3
View File
@@ -36,7 +36,7 @@ from typing import NamedTuple
from datetime import datetime, timedelta, timezone
from scribe.services.forge import ForgeSelector, get_forges
from scribe.services.repo_bindings import keys_for_project
from scribe.services.repo_bindings import bindings_for_project
from scribe.services.settings import get_setting, set_setting
logger = logging.getLogger(__name__)
@@ -407,12 +407,15 @@ async def compute_coverage(
# the project's repos (#2792).
canons = None
proposer_stats = {"examined": 0, "proposed": 0, "semantic_checked": 0}
for key in await keys_for_project(user_id, project_id):
for binding in await bindings_for_project(user_id, project_id):
key = binding.repo_key
hit = selector.resolve(key)
if hit is None:
continue # bound to a host no connection serves
forge, api_repo = hit
ref = await forge.default_branch(api_repo)
# The binding's own ref when it names one (#2873: a dev-first project
# has its ledger follow dev), else the forge's default branch.
ref = binding.ref or await forge.default_branch(api_repo)
definitions = definitions_from_archive(await forge.archive(api_repo, ref))
# The head commit is provenance sugar on the ledger rows; failing to
# learn it must not fail the sync — the ref names the point well