-
Notifications
You must be signed in to change notification settings - Fork 1
fix(storage): dedupe judgment-request generation across census cycles #3290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -649,13 +649,41 @@ def _strategy_overrides( | |
|
|
||
|
|
||
| def _record_judgment_candidate(config: Config, item: RawAuthorityFrontierItem, *, now_ms: int) -> tuple[str, bool]: | ||
| """Persist the conflict as a non-authoritative candidate for operator judgment.""" | ||
| """Persist the conflict as a non-authoritative candidate for operator judgment. | ||
|
|
||
| polylogue-rjtv: the assertion id is derived from ``item.plan_id``, which is | ||
| itself derived from a fresh evidence digest every census cycle -- a | ||
| census cycle that re-encounters the *same* unresolved conflict (same | ||
| ``raw_id``/``logical_source_key``) before an operator has judged it would | ||
| otherwise mint a brand-new candidate each time, leaving prior cycles' | ||
| still-pending duplicates to accumulate forever (found live 2026-07-27: 24 | ||
| candidates in ``judge --list`` for what was actually 6 real conflicts). | ||
| Look up an existing still-``candidate`` request for the same conflict | ||
| identity first and refresh it in place instead of minting a new one. This | ||
| only dedupes pending-vs-pending; an already accepted/rejected/deferred | ||
| assertion is untouched, so a fresh judgment can still be requested if the | ||
| same conflict resurfaces after a prior disposition. | ||
| """ | ||
| from polylogue.core.enums import AssertionKind, AssertionStatus, AssertionVisibility | ||
| from polylogue.storage.sqlite.archive_tiers.user_write import read_assertion_envelope, upsert_assertion | ||
|
|
||
| assertion_id = f"judgment:{_digest(['raw-authority-frontier', item.plan_id])}" | ||
| root = _archive_root(config) | ||
| with closing(sqlite3.connect(root / "user.db")) as conn, conn: | ||
| pending_row = conn.execute( | ||
| """ | ||
| SELECT assertion_id FROM assertions | ||
| WHERE kind = 'judgment' AND status = 'candidate' | ||
| AND json_extract(value_json, '$.raw_id') = ? | ||
| AND json_extract(value_json, '$.logical_source_key') = ? | ||
| LIMIT 1 | ||
| """, | ||
| (item.raw_id, item.logical_source_key), | ||
| ).fetchone() | ||
| assertion_id = ( | ||
| str(pending_row[0]) | ||
| if pending_row is not None | ||
| else f"judgment:{_digest(['raw-authority-frontier', item.plan_id])}" | ||
| ) | ||
|
Comment on lines
+672
to
+686
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 6 \
'def _immediate_user_write_transaction|CREATE (UNIQUE )?INDEX|CREATE TABLE assertions' \
polylogue/storage/sqlite
rg -n -C 8 \
'pending_row = conn\.execute|def _record_judgment_candidate|def upsert_assertion' \
polylogue/storage/raw_reconciler.py \
polylogue/storage/sqlite/archive_tiers/user_write.pyRepository: Sinity/polylogue Length of output: 50373 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '== Files matching raw_reconciler.py/user_write.py/assertions DDL ==\n'
fd -a 'raw_reconciler.py|user_write.py' polylogue/storage | sed 's#^\./##'
rg -n -C 4 'CREATE TABLE assertions|CREATE UNIQUE INDEX.*assertions|CREATE INDEX.*assertions' \
polylogue/storage/sqlite/migrations polylogue/storage/sqlite/archive_tiers \
--max-count 80
printf '\n== raw_reconciler relevant sections ==\n'
wc -l polylogue/storage/raw_reconciler.py
sed -n '620,710p' polylogue/storage/raw_reconciler.py
printf '\n== upsert_assertion usages and definitions ==\n'
rg -n -C 12 'def upsert_assertion|upsert_assertion|_record_judgment_candidate|pending_row = conn\.execute' \
polylogue/storage/raw_reconciler.py polylogue/storage/sqlite/archive_tiers/user_write.py \
--max-count 100Repository: Sinity/polylogue Length of output: 50372 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '== user_write immediate transaction and upsert_assertion implementation ==\n'
sed -n '86,130p' polylogue/storage/sqlite/archive_tiers/user_write.py
sed -n '1099,1220p' polylogue/storage/sqlite/archive_tiers/user_write.py
printf '\n== assertions table DDL and all assertion indexes/migrations ==\n'
python3 - <<'PY'
from pathlib import Path
import re
root = Path('polylogue/storage/sqlite')
for p in sorted(root.glob('**/*.sql')) + sorted(root.glob('archive_tiers/*.py')):
text = p.read_text()
if 'CREATE TABLE assertions' in text or 'idx_assertions' in text:
print(f'--- {p} ---')
for line in re.finditer(r'(?ms)CREATE TABLE assertions[\s\S]+?(?=\n\n|\Z)', text):
end = line.end()
start=max(0,end-800); print(text[start:end][:1200])
for l in re.finditer(r'(?m)CREATE .* INDEX .* assertions|idx_assertions', text):
start=max(0,l.start()-150); end=min(len(text),l.end()+150)
print(text[start:end])
PY
printf '\n== call sites around _record_judgment_candidate and transaction context ==\n'
rg -n -C 6 '_record_judgment_candidate|with closing|conn:|BEGIN IMMEDIATE|_immediate_user_write_transaction' \
polylogue/storage/raw_reconciler.py polylogue/storage -g '*.py' \
--max-count 120Repository: Sinity/polylogue Length of output: 50373 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
for p in [Path('polylogue/storage/sqlite/archive_tiers/user.py'), Path('polylogue/storage/sqlite/archive_tiers/user_write.py')]:
text = p.read_text()
for name in ['CREATE TABLE assertions', 'CREATE UNIQUE INDEX', 'idx_assertions']:
if name in text:
print(f'--- assertions occurrences in {p} ---')
lines = text.splitlines()
for i,l in enumerate(lines,1):
if name in l:
lo=max(1,i-3); hi=min(len(lines),i+20 if 'idx_assertions' in l else i+8)
print(f'[{p}:{lo}-{hi}]')
for j in range(lo,hi+1):
print(f'{j:4}: {lines[j-1]}')
print()
print('--- focused assertions index/statements by regex ---')
for p in [Path('polylogue/storage/sqlite/archive_tiers/user.py'), Path('polylogue/storage/sqlite/archive_tiers/user_write.py')]:
text = p.read_text()
for m in re.finditer(r'(?ms)CREATE (UNIQUE )?INDEX\s+\Qidx_assertions\E[\s\S]{0,220}', text):
print('---', p, '---')
print(m.group(0))
PY
printf '\n===== raw_reconciler _record_judgment_candidate complete =====\n'
sed -n '640,705p' polylogue/storage/raw_reconciler.py
printf '\n===== user_write immediate transaction and upsert_assertion =====\n'
sed -n '86,120p' polylogue/storage/sqlite/archive_tiers/user_write.py
sed -n '1099,1195p' polylogue/storage/sqlite/archive_tiers/user_write.py
printf '\n===== all candidate creation paths that derive assertion IDs outside immediate_user_write_transaction =====\n'
sed -n '1215,1295p' polylogue/storage/sqlite/archive_tiers/user_write.py
sed -n '1315,1630p' polylogue/storage/sqlite/archive_tiers/user_write.pyRepository: Sinity/polylogue Length of output: 6326 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '===== raw_reconciler _record_judgment_candidate complete =====\n'
sed -n '640,705p' polylogue/storage/raw_reconciler.py
printf '\n===== user_write immediate transaction and upsert_assertion =====\n'
sed -n '86,120p' polylogue/storage/sqlite/archive_tiers/user_write.py
sed -n '1099,1195p' polylogue/storage/sqlite/archive_tiers/user_write.py
printf '\n===== assertions table DDL and indexes from user.py =====\n'
sed -n '20,62p' polylogue/storage/sqlite/archive_tiers/user.py
printf '\n===== candidate creation paths that derive assertion IDs outside immediate_user_write_transaction =====\n'
sed -n '1215,1290p' polylogue/storage/sqlite/archive_tiers/user_write.py
sed -n '1315,1630p' polylogue/storage/sqlite/archive_tiers/user_write.pyRepository: Sinity/polylogue Length of output: 29642 Make raw-authority candidate creation atomic.
🤖 Prompt for AI Agents |
||
| existing = read_assertion_envelope(conn, assertion_id) | ||
| if existing is not None and existing.status is not AssertionStatus.CANDIDATE: | ||
| return existing.assertion_id, False | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Handle
logical_source_key=Nonein the identity lookup.RawAuthorityFrontierItem.logical_source_keyis nullable, butjson_extract(...) = ?withNoneevaluates to SQLNULL, never true. Repeated conflicts without a logical-source key will still create duplicates. Use SQLite null-safe comparison (IS ?) and add a nullable-key regression case.🤖 Prompt for AI Agents