Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5415bff85c | ||
|
|
aba16583ab |
@@ -5,9 +5,9 @@ Revises: 0083
|
|||||||
Create Date: 2026-08-21
|
Create Date: 2026-08-21
|
||||||
|
|
||||||
A ledger row carries ONE snippet_id: what shape this is (instance/variant of
|
A ledger row carries ONE snippet_id: what shape this is (instance/variant of
|
||||||
a canon). But a shape can also CALL several canonical helpers — a service
|
a canon). But a shape can also CALL several canonical helpers — e.g. a
|
||||||
function that is an instance of the service-function convention and a
|
service function both conforming to the service-function convention and
|
||||||
consumer of hash_token. The 2026-08 audit had to pick one; hook evidence
|
consuming hash_token. The 2026-08 audit had to pick one; hook evidence
|
||||||
("pulled #N then wrote code referencing it") was stamped as instance when it
|
("pulled #N then wrote code referencing it") was stamped as instance when it
|
||||||
is a uses fact. This table holds the many-valued relation: shape → snippet,
|
is a uses fact. This table holds the many-valued relation: shape → snippet,
|
||||||
with the basis and the evidence. Cascades with the shape and the snippet.
|
with the basis and the evidence. Cascades with the shape and the snippet.
|
||||||
|
|||||||
@@ -191,7 +191,19 @@ def extract_definitions(text: str) -> list[Definition]:
|
|||||||
# same rule under another name?" — .closed-msg / .error-block /
|
# same rule under another name?" — .closed-msg / .error-block /
|
||||||
# .success-msg with identical bodies are one dup group, not three
|
# .success-msg with identical bodies are one dup group, not three
|
||||||
# lonely rows. Sym blocks keep their signature line in the hash.
|
# lonely rows. Sym blocks keep their signature line in the hash.
|
||||||
hashed = block[1:] if kind == "css" and len(block) > 1 else block
|
if kind == "css":
|
||||||
|
# One-line rules (`.x { color: red; }`) carry their declarations on
|
||||||
|
# the selector line itself; a block that is only the selector plus
|
||||||
|
# trailing blanks must not hash to the empty string (which grouped
|
||||||
|
# 68 unrelated one-liners as one "copy" on first deploy, #2872).
|
||||||
|
first = lines[i]
|
||||||
|
brace = first.find("{")
|
||||||
|
head = [first[brace + 1:]] if brace >= 0 and first[brace + 1:].strip() else []
|
||||||
|
hashed = head + block[1:]
|
||||||
|
if not any(x.strip() for x in hashed):
|
||||||
|
hashed = block
|
||||||
|
else:
|
||||||
|
hashed = block
|
||||||
out.append(Definition(
|
out.append(Definition(
|
||||||
kind, name, lines[i].strip()[:_SIGNATURE_CAP], _block_sha(hashed),
|
kind, name, lines[i].strip()[:_SIGNATURE_CAP], _block_sha(hashed),
|
||||||
"\n".join(block), i,
|
"\n".join(block), i,
|
||||||
|
|||||||
@@ -1225,7 +1225,6 @@ async def propose_for_repo(
|
|||||||
select(CodeShape).where(
|
select(CodeShape).where(
|
||||||
CodeShape.project_id == project_id,
|
CodeShape.project_id == project_id,
|
||||||
CodeShape.repo_key == repo_key,
|
CodeShape.repo_key == repo_key,
|
||||||
CodeShape.status.in_(_MECHANICAL_TODO),
|
|
||||||
CodeShape.vanished_at.is_(None),
|
CodeShape.vanished_at.is_(None),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1239,6 +1238,18 @@ async def propose_for_repo(
|
|||||||
examined_as = f"{body_sha}@{_PROPOSER_VERSION}"
|
examined_as = f"{body_sha}@{_PROPOSER_VERSION}"
|
||||||
if row.proposed_at is not None and row.proposed_sha == examined_as:
|
if row.proposed_at is not None and row.proposed_sha == examined_as:
|
||||||
continue
|
continue
|
||||||
|
if row.status not in _MECHANICAL_TODO:
|
||||||
|
# A judged row gets no proposal — but its uses edges (#2870)
|
||||||
|
# are a fact about the body, judged or not: the consumer map
|
||||||
|
# of a canon must include the call sites someone already
|
||||||
|
# classified. Mark it examined so the scan runs once per body.
|
||||||
|
used = reference_canons(row.kind, row.path, row.symbol, body, canons)
|
||||||
|
if used:
|
||||||
|
await record_uses(session, row, used, basis="reference",
|
||||||
|
evidence="proposer: body names the canon's symbol")
|
||||||
|
row.proposed_at = now
|
||||||
|
row.proposed_sha = examined_as
|
||||||
|
continue
|
||||||
examined += 1
|
examined += 1
|
||||||
group = row.proposal_group # derive grouping is reassigned below
|
group = row.proposal_group # derive grouping is reassigned below
|
||||||
hit = match_canon(
|
hit = match_canon(
|
||||||
|
|||||||
@@ -230,6 +230,19 @@ async def test_uses_edges_are_the_consumer_map(seeded):
|
|||||||
)
|
)
|
||||||
assert out["classified"] == 1
|
assert out["classified"] == 1
|
||||||
assert (await list_project_shapes(owner, pid, uses=hid))[1] == 2
|
assert (await list_project_shapes(owner, pid, uses=hid))[1] == 2
|
||||||
|
# The proposer writes uses edges for JUDGED rows too: Config (exempt)
|
||||||
|
# names hash_token in its body → an edge, no proposal.
|
||||||
|
from scribe.services.shape_ledger import propose_for_repo
|
||||||
|
await classify_shapes(owner, pid, [
|
||||||
|
{"path": "src/app.py", "symbol": "Config", "status": "exempt", "reason": "settings"},
|
||||||
|
])
|
||||||
|
defs = _defs(("src/app.py", "sym", "Config", "class Config:", "class Config:\n token = hash_token(raw)\n"))
|
||||||
|
with _quiet_semantic():
|
||||||
|
await propose_for_repo(owner, pid, REPO, defs)
|
||||||
|
rows, total = await list_project_shapes(owner, pid, uses=hid)
|
||||||
|
assert total == 3 and {r.symbol for r in rows} >= {"Config"}
|
||||||
|
cfg = next(r for r in rows if r.symbol == "Config")
|
||||||
|
assert cfg.status == "exempt" and cfg.proposal is None
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
await classify_shapes(owner, pid, [
|
await classify_shapes(owner, pid, [
|
||||||
{"path": "src/app.py", "symbol": "Config", "status": "exempt", "reason": "x", "uses": [999999]},
|
{"path": "src/app.py", "symbol": "Config", "status": "exempt", "reason": "x", "uses": [999999]},
|
||||||
|
|||||||
@@ -481,6 +481,13 @@ def test_extract_definitions_fingerprints_each_block():
|
|||||||
css = ".closed-msg {\n text-align: center;\n padding: 0.5rem 0;\n}\n.error-block {\n text-align: center;\n padding: 0.5rem 0;\n}\n.other {\n text-align: left;\n}\n"
|
css = ".closed-msg {\n text-align: center;\n padding: 0.5rem 0;\n}\n.error-block {\n text-align: center;\n padding: 0.5rem 0;\n}\n.other {\n text-align: left;\n}\n"
|
||||||
d = {x.name: x for x in extract_definitions(css)}
|
d = {x.name: x for x in extract_definitions(css)}
|
||||||
assert d["closed-msg"].body_sha == d["error-block"].body_sha != d["other"].body_sha
|
assert d["closed-msg"].body_sha == d["error-block"].body_sha != d["other"].body_sha
|
||||||
|
# One-line rules hash their own declarations — never the empty string
|
||||||
|
# (first deploy grouped 68 unrelated one-liners as one copy).
|
||||||
|
one = ".a { color: red; }\n\n.b { color: red; }\n\n.c { color: blue; }\n\n.d {\n color: red;\n}\n"
|
||||||
|
e = {x.name: x for x in extract_definitions(one)}
|
||||||
|
import hashlib
|
||||||
|
assert e["a"].body_sha == e["b"].body_sha != e["c"].body_sha
|
||||||
|
assert e["a"].body_sha != hashlib.sha1(b"").hexdigest()[:16]
|
||||||
|
|
||||||
|
|
||||||
def test_coverage_line_names_the_proposers_standing():
|
def test_coverage_line_names_the_proposers_standing():
|
||||||
|
|||||||
Reference in New Issue
Block a user