fix(shapes): the capped meaning pass reads new shapes first, and its miss withdraws an early flag (#4208)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Failing after 51s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m33s
CI & Build / Build & push image (push) Successful in 21s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Failing after 51s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m33s
CI & Build / Build & push image (push) Successful in 21s
Measured on the first live refresh after 91cde6c deployed: the version bump
queued 1,533 rows for the semantic arm, the cap read 150 of them in row
order, and all six shapes new since the previous refresh - the only rows
flag_divergence acts on, and the highest ids - were flagged before the arm
reached them. The gate silenced nothing because it never got to look, and
since a flag persists until judged, a later conclusive miss could not take
it back.
- propose_for_repo sorts the semantic todo by _semantic_priority:
unclassified before scoped, newest first.
- flag_divergence withdraws a standing flag when the row now carries a
conclusive miss; that evidence alone withdraws one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
@@ -1545,6 +1545,25 @@ _SEMANTIC_LIMIT = 3
|
||||
# ledger's standing discipline — the one `FORM_UNKNOWN` enforces everywhere
|
||||
# else — is that not knowing must make a check quieter, never more confident.
|
||||
BASIS_NO_SEMANTIC_MATCH = "no-semantic-match"
|
||||
|
||||
|
||||
def _semantic_priority(row) -> tuple:
|
||||
"""Order for the capped semantic pass: the rows its verdict can still act on
|
||||
come first (#4208, measured on the first live refresh after it shipped).
|
||||
|
||||
The pass is capped (`_SEMANTIC_CAP`), and `flag_divergence` acts only on
|
||||
shapes NEW since the previous refresh. In plain row order those are the
|
||||
highest ids — the back of the queue — so whenever the backlog exceeds the
|
||||
cap (every `_PROPOSER_VERSION` bump queues the whole todo at once), the
|
||||
cap is spent on old rows and the new ones are flagged as "cannot tell"
|
||||
before the arm ever reads them. That is what happened: 1,533 rows queued,
|
||||
150 checked, and all six new shapes flagged unexamined.
|
||||
|
||||
So: the human todo (`unclassified`) before `scoped`, newest first within
|
||||
each. A row with no creation time sorts last in its group.
|
||||
"""
|
||||
created = row.created_at.timestamp() if row.created_at is not None else float("-inf")
|
||||
return (row.status != "unclassified", -created)
|
||||
# Bump when a basis's rule changes: rows remember the (body, ruleset) they
|
||||
# were examined under, so a tightened rule re-examines everything once.
|
||||
# v3: language-family gate on the sym bases, reference stoplist, semantic
|
||||
@@ -1911,6 +1930,7 @@ async def propose_for_repo(
|
||||
proposed += 1
|
||||
elif row.kind == "sym":
|
||||
semantic_todo.append((row, d))
|
||||
semantic_todo.sort(key=lambda item: _semantic_priority(item[0]))
|
||||
for i, (row, d) in enumerate(semantic_todo):
|
||||
if i >= semantic_cap:
|
||||
# Not reached this refresh: leave it unexamined so the next
|
||||
@@ -2466,7 +2486,9 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
|
||||
"""Flag shapes created after ``since`` (the previous refresh) that sit
|
||||
where a canon dominates and were not proposed as that canon. With no
|
||||
previous refresh (first seed) nothing is new, nothing is flagged.
|
||||
Standing flags persist until judged. Returns how many are flagged."""
|
||||
Standing flags persist until judged — or until the proposer's semantic
|
||||
arm reports a conclusive miss for the row (#4208). Returns how many are
|
||||
flagged."""
|
||||
if since is None:
|
||||
return 0
|
||||
async with async_session() as session:
|
||||
@@ -2490,6 +2512,17 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
|
||||
for r in siblings:
|
||||
if r.status not in _MECHANICAL_TODO:
|
||||
continue
|
||||
# A conclusive miss outranks a standing flag, and is checked
|
||||
# before it. A flag raised on a row the semantic arm had not
|
||||
# yet reached was raised on "cannot tell" — the arm is capped,
|
||||
# so that is routine — and once the arm has read the body and
|
||||
# found it is none of the canons, keeping the prompt would be
|
||||
# asserting over a measurement we now hold. Only this evidence
|
||||
# withdraws a flag; nothing else changes "persist until judged".
|
||||
if r.proposal_basis == BASIS_NO_SEMANTIC_MATCH:
|
||||
if r.diverges_from is not None:
|
||||
r.diverges_from = None
|
||||
continue
|
||||
if r.diverges_from is not None:
|
||||
flagged += 1
|
||||
continue
|
||||
@@ -2514,9 +2547,8 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
|
||||
#
|
||||
# Only the conclusive miss is stored, so an unexamined row and
|
||||
# a body too thin to embed still ask the question rather than
|
||||
# being quietly excused.
|
||||
if r.proposal_basis == BASIS_NO_SEMANTIC_MATCH:
|
||||
continue
|
||||
# being quietly excused. (Tested at the top of this loop, where
|
||||
# it also withdraws a flag raised before the arm got there.)
|
||||
# The same structural test the write-time check applies
|
||||
# (#4204). The sweep and the hook must agree about what counts
|
||||
# as divergence, or an audit contradicts the line the writer
|
||||
|
||||
Reference in New Issue
Block a user