feat(ledger): coverage self-seeds — enter_project background refresh + refresh_pattern_coverage tool + shape-accounting skill (#2802, milestone 294)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 33s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 33s
CI & Build / Build & push image (push) Skipped
The UI Refresh button must not be the only seed path (operator directive, hit live: the P7 backfill stalled waiting for a click). Three parts: - enter_project fire-and-forgets refresh_if_stale on the project OWNER — absent or day-old readouts recompute in the background (same spawn the webhook path uses), the enter stays fast, forge-less owners exit quietly (rule #115 baseline), and an in-flight guard keeps concurrent enters from fetching the same tarball N times. - refresh_pattern_coverage(project_id): the synchronous agent-facing form — write-gated, owner-keyring resolution, and ValueError messages that name the fix (add a connection / bind_repo) instead of measuring nothing silently. - plugin 0.1.33 ships the shape-accounting skill: the five statuses, the seed/todo/judge loop, and the derive-first rule, triggered by the coverage line or any proved code-to-canon relationship. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -390,6 +390,52 @@ async def test_enter_project_surfaces_the_line_only_once_computed(seeded):
|
||||
_user_id_ctx.reset(token)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_explicit_refresh_names_its_failures(seeded):
|
||||
"""refresh_for_caller (#2802) raises fixable errors instead of silence:
|
||||
an agent mid-task must learn WHY nothing measured — 'None' is exactly the
|
||||
stranding the button-only path caused."""
|
||||
from scribe.models import async_session
|
||||
from scribe.models.user import User
|
||||
from scribe.services.coverage import refresh_for_caller
|
||||
from sqlalchemy import select
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
# The owner has no forge connection rows → the error names the fix.
|
||||
with pytest.raises(ValueError) as err:
|
||||
await refresh_for_caller(uid, pid)
|
||||
assert "Git Forges" in str(err.value)
|
||||
|
||||
# A stranger gets not-found/no-write, never a measurement.
|
||||
async with async_session() as s:
|
||||
other = (await s.execute(
|
||||
select(User).where(User.username == "coverage_outsider")
|
||||
)).scalar_one_or_none()
|
||||
if other is None:
|
||||
other = User(username="coverage_outsider")
|
||||
s.add(other)
|
||||
await s.flush()
|
||||
other_id = other.id
|
||||
await s.commit()
|
||||
with pytest.raises(ValueError) as err:
|
||||
await refresh_for_caller(other_id, pid)
|
||||
assert "no write access" in str(err.value)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_background_seed_is_quiet_without_a_forge(seeded):
|
||||
"""refresh_if_stale (#2802) must exit silently for a forge-less owner —
|
||||
rule #115's baseline — and treat a fresh cache as nothing-to-do."""
|
||||
from scribe.services.coverage import refresh_coverage, refresh_if_stale
|
||||
|
||||
uid, pid = seeded["uid"], seeded["pid"]
|
||||
# Absent cache + no forge rows: returns without raising, writes nothing.
|
||||
await refresh_if_stale(uid, pid)
|
||||
# Fresh cache: returns before ever consulting the keyring.
|
||||
stored = await refresh_coverage(uid, pid, selector=_selector(_tarball(TREE)))
|
||||
await refresh_if_stale(uid, pid, cached=stored)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_unservable_binding_measures_nothing(seeded):
|
||||
"""A project bound only to a host the forge doesn't serve returns None —
|
||||
|
||||
Reference in New Issue
Block a user