feat(ledger): uses edges — consumption is its own relation, conformance keeps one snippet_id (#2870, milestone 294)
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Failing after 29s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / Python tests (push) Failing after 39s
CI & Build / Build & push image (push) Skipped

A shape can follow one convention canon AND call several helper canons; the
row's single snippet_id made the 2026-08 audit pick (hash_token won, the
service-function convention lost), and hook evidence — pulled a snippet, then
wrote code naming it — was stamped as instance when it is a uses fact.

- code_shape_uses (migration 0084): shape → snippet, basis, evidence; unique
  per pair; cascades with both ends. USE_BASES: reference | hook | agent |
  audit | import. A judgment-grade basis overwrites a mechanical one, never
  the reverse.
- classify_shapes items and classify_shapes_by_rule take uses=[snippet ids]
  (targets validated like snippet_id; all-or-nothing).
- The write-path hook writes a uses edge for every pulled canon the payload
  names (the instance stamp is unchanged); the proposer writes a uses edge
  for every canon a body names (reference_canons: kind + language family +
  stoplist, same rules as the reference basis) — the mechanical form of
  "auto-confirm own-import references" deferred from #2871.
- list_shapes(uses=N) lists the consumers of a canon; get_snippet's consumer
  map gains `uses` beside instances/variants.

Operator decision on #2870 (2026-08-21): keep one snippet_id, add uses edges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 15:18:25 -04:00
co-authored by Claude Fable 5
parent bfe5a461b4
commit d4c7b0e48d
8 changed files with 314 additions and 14 deletions
+35
View File
@@ -201,6 +201,41 @@ async def test_sync_stamps_scoped_rows_and_unstamps_when_they_become_reachable(s
assert by_symbol["card"].status == "instance" and by_symbol["card"].snippet_id == sid
@pytest.mark.integration
async def test_uses_edges_are_the_consumer_map(seeded):
"""#2870: a shape keeps ONE snippet_id (what it is) and any number of
uses edges (what it calls); the snippet's consumer map lists them,
list_shapes(uses=N) finds them, and a sweep can write them."""
from scribe.services import snippets as snippets_svc
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
helper = await snippets_svc.create_snippet(
owner, name="cls_hash_helper", code="def hash_token(raw):\n return raw\n",
language="python", repo="Widget", path="src/hash.py", symbol="hash_token",
project_id=pid,
)
hid = int(helper.id)
out = await classify_shapes(owner, pid, [
{"path": "src/app.py", "symbol": "make_app", "status": "instance",
"snippet_id": sid, "uses": [hid]},
], via="audit")
assert out["classified"] == 1
rows, total = await list_project_shapes(owner, pid, uses=hid)
assert total == 1 and rows[0].symbol == "make_app" and rows[0].snippet_id == sid
consumers = await snippet_consumers(owner, hid)
assert consumers["instances"] == [] and len(consumers["uses"]) == 1
assert consumers["uses"][0]["symbol"] == "make_app" and consumers["uses"][0]["basis"] == "audit"
# A sweep writes uses too; an unknown snippet in uses applies nothing.
out = await classify_shapes_where(
owner, pid, path="src/util.py", status="exempt", reason="local", uses=[hid],
)
assert out["classified"] == 1
assert (await list_project_shapes(owner, pid, uses=hid))[1] == 2
with pytest.raises(ValueError):
await classify_shapes(owner, pid, [
{"path": "src/app.py", "symbol": "Config", "status": "exempt", "reason": "x", "uses": [999999]},
])
@pytest.mark.integration
async def test_list_filters_compose(seeded):
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]