cli: 'aborist burn-kindergarten' — mass-burn fresh providence rows
Per fox: useful while iterating on retrieval/verifier knobs to wipe
recent test runs without finding each cache_key. Mirrors the mesh-sync
kindergarten window so what's still un-broadcast (private to this peer)
is exactly what's safe to bust without confusing peers.
Surface:
aborist burn-kindergarten [--kindergarten-seconds N]
[--reason '...'] [--by-actor X]
[--force] [--dry-run] [--verbose N]
make burn-kindergarten [SECONDS=3600] [FORCE=1] [DRY_RUN=1] [REASON='why']
Behavior:
- Walks every shard, finds providence_cache rows with
created_at >= now - SECONDS and falsification_state='live'.
- Each row goes through the existing _burn_cache_key — children gate
honored unless --force, audit event written per successful burn,
chain integrity preserved.
- Result JSON reports examined / burned / refused_has_children /
not_found counts plus a verbose tail of per-row results.
- --dry-run reports without writing or auditing.
- --kindergarten-seconds 0 = burn every live row (test reset).
Tests (4): selective burn (old kept, fresh burned), dry-run writes
nothing, 0-second window burns everything, children-gate refusal
without --force. 353 passed, 1 skipped.
Operational note: this command does NOT propagate to peers (burn is
local kindergarten cleanup by design, see docs/mesh.md). If you want
the remote effect, falsify each record individually & let mesh sync
broadcast the falsifications instead.
This commit is contained in:
parent
d6f98929d7
commit
4573dbcf4e
3 changed files with 286 additions and 1 deletions
14
Makefile
14
Makefile
|
|
@ -31,7 +31,7 @@ SEARCH_Q ?= computer
|
|||
ingest-grok ingest-grok-media \
|
||||
ingest-self ingest-git ingest-hg \
|
||||
verify search stats test docs chain-check chain-check-shards \
|
||||
falsify burn inspect bootstrap-crawler test-crawler crawl-ingest \
|
||||
falsify burn burn-kindergarten inspect bootstrap-crawler test-crawler crawl-ingest \
|
||||
recrawl-check clean clean-db clean-data help
|
||||
|
||||
all: bootstrap fetch-cur ingest-cur verify stats ## bootstrap → fetch cur → ingest cur → verify → stats
|
||||
|
|
@ -140,6 +140,18 @@ burn: bootstrap ## delete a leaf with no children. providence: KEY=<cache_key>;
|
|||
echo "unknown KIND: $$kind (expected: providence|document|core)" >&2; exit 2; \
|
||||
fi
|
||||
|
||||
# Mass-burn providence_cache rows younger than the kindergarten window.
|
||||
# Mirrors mesh sync's kindergarten so what's still un-broadcast is what's
|
||||
# safe to bust without confusing peers. Useful while iterating on
|
||||
# retrieval/verifier tunings — wipe recent test runs in one shot.
|
||||
KG_SECONDS ?= 3600
|
||||
burn-kindergarten: bootstrap ## bust providence rows < SECONDS old [SECONDS=3600 FORCE=1 DRY_RUN=1 REASON='why']
|
||||
$(ABORIST) --shards-dir $(SHARDS_DIR) burn-kindergarten \
|
||||
--kindergarten-seconds $(KG_SECONDS) \
|
||||
$(if $(REASON),--reason "$(REASON)",) \
|
||||
$(if $(FORCE),--force,) \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
# Multi-source RAG query against the shard cluster.
|
||||
# Usage: make query Q="What is anarcho-capitalism?"
|
||||
QUERY_TOP_K ?= 8
|
||||
|
|
|
|||
126
aborist/cli.py
126
aborist/cli.py
|
|
@ -1022,6 +1022,91 @@ def _burn_core_root(
|
|||
return {"status": "not_found", "document_root": document_root_value, "kind": "core"}
|
||||
|
||||
|
||||
def _cmd_burn_kindergarten(args: argparse.Namespace) -> int:
|
||||
"""Burn all providence_cache rows younger than the kindergarten window.
|
||||
|
||||
Test-ergonomic mass burn: when iterating on retrieval/verifier knobs
|
||||
you want to wipe recent test runs without finding each cache_key.
|
||||
Mirrors the kindergarten window from `mesh sync` so what's still
|
||||
"private" (un-broadcast) is also what's safe to bust without
|
||||
confusing peers.
|
||||
|
||||
Each row goes through the standard `_burn_cache_key` so the
|
||||
children gate is honored (use `--force` to override en masse).
|
||||
Each successful burn writes one ``providence_burn`` audit event;
|
||||
chain integrity is verifiable via `make chain-check-shards` after.
|
||||
"""
|
||||
import time as _time
|
||||
from aborist.store import discover_shards
|
||||
|
||||
now = int(_time.time())
|
||||
cutoff = now - max(0, args.kindergarten_seconds)
|
||||
shards_dir = Path(args.global_shards_dir) if args.global_shards_dir else None
|
||||
single_db = Path(args.db) if args.db else None
|
||||
paths: list[Path] = (
|
||||
discover_shards(shards_dir) if shards_dir else [single_db]
|
||||
)
|
||||
actor = args.by_actor or os.environ.get("USER", "unknown")
|
||||
reason = args.reason or f"burn-kindergarten window={args.kindergarten_seconds}s"
|
||||
|
||||
examined = 0
|
||||
burned = 0
|
||||
refused = 0
|
||||
not_found = 0
|
||||
items: list[dict] = []
|
||||
for sp in paths:
|
||||
c = connect(sp)
|
||||
try:
|
||||
rows = c.execute(
|
||||
"SELECT cache_key, created_at, audit_mode "
|
||||
"FROM providence_cache "
|
||||
"WHERE created_at >= ? AND falsification_state = 'live' "
|
||||
"ORDER BY created_at DESC",
|
||||
(cutoff,),
|
||||
).fetchall()
|
||||
finally:
|
||||
c.close()
|
||||
for r in rows:
|
||||
examined += 1
|
||||
if args.dry_run:
|
||||
items.append({
|
||||
"cache_key": r["cache_key"],
|
||||
"audit_mode": r["audit_mode"],
|
||||
"created_at": r["created_at"],
|
||||
"would_burn": True,
|
||||
})
|
||||
continue
|
||||
result = _burn_cache_key(
|
||||
r["cache_key"],
|
||||
reason=reason,
|
||||
by_actor=actor,
|
||||
shards_dir=shards_dir,
|
||||
db_path=single_db,
|
||||
force=bool(args.force),
|
||||
)
|
||||
status = result.get("status")
|
||||
if status == "burned":
|
||||
burned += 1
|
||||
elif status == "refused_has_children":
|
||||
refused += 1
|
||||
else:
|
||||
not_found += 1
|
||||
items.append(result)
|
||||
|
||||
print(json.dumps({
|
||||
"status": "dry_run" if args.dry_run else "burned",
|
||||
"kindergarten_seconds": args.kindergarten_seconds,
|
||||
"cutoff_at": cutoff,
|
||||
"now": now,
|
||||
"examined": examined,
|
||||
"burned": burned,
|
||||
"refused_has_children": refused,
|
||||
"not_found": not_found,
|
||||
"items": items[: args.verbose],
|
||||
}, indent=2, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_burn(args: argparse.Namespace) -> int:
|
||||
"""CLI: burn a leaf with no children.
|
||||
|
||||
|
|
@ -2849,6 +2934,47 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
)
|
||||
burn_cmd.set_defaults(func=_cmd_burn)
|
||||
|
||||
burn_kg_cmd = sub.add_parser(
|
||||
"burn-kindergarten",
|
||||
help=(
|
||||
"burn every providence_cache record younger than the "
|
||||
"kindergarten window — test-ergonomic mass cleanup that "
|
||||
"matches the mesh-sync kindergarten window so only "
|
||||
"un-broadcast records get busted"
|
||||
),
|
||||
)
|
||||
burn_kg_cmd.add_argument(
|
||||
"--kindergarten-seconds",
|
||||
dest="kindergarten_seconds",
|
||||
type=int,
|
||||
default=3600,
|
||||
help=(
|
||||
"burn rows younger than this many seconds (default: 3600 = 1 "
|
||||
"hour, mirrors mesh sync default). 0 = burn everything live."
|
||||
),
|
||||
)
|
||||
burn_kg_cmd.add_argument(
|
||||
"--reason", default=None,
|
||||
help="reason text recorded in each providence_burn audit event",
|
||||
)
|
||||
burn_kg_cmd.add_argument(
|
||||
"--by-actor", dest="by_actor", default=None,
|
||||
help="who is burning (default: $USER)",
|
||||
)
|
||||
burn_kg_cmd.add_argument(
|
||||
"--force", action="store_true",
|
||||
help="burn even if rows have falsification children",
|
||||
)
|
||||
burn_kg_cmd.add_argument(
|
||||
"--dry-run", dest="dry_run", action="store_true",
|
||||
help="report what would burn without writing",
|
||||
)
|
||||
burn_kg_cmd.add_argument(
|
||||
"--verbose", type=int, default=10,
|
||||
help="include this many items in the result JSON (default: 10)",
|
||||
)
|
||||
burn_kg_cmd.set_defaults(func=_cmd_burn_kindergarten)
|
||||
|
||||
reclassify_cmd = sub.add_parser(
|
||||
"reclassify",
|
||||
help="re-run the verifier against existing live providence records "
|
||||
|
|
|
|||
|
|
@ -274,6 +274,153 @@ def test_burn_cli_invocation(tmp_path, capsys):
|
|||
assert payload["reason"] == "via cli"
|
||||
|
||||
|
||||
def test_burn_kindergarten_burns_only_recent_rows(tmp_path, capsys):
|
||||
"""Mass-burn rows younger than the window. Older rows are kept; the
|
||||
test seeds two rows, backdates one to 2h ago, and asserts the
|
||||
fresh one burns and the old one stays."""
|
||||
import time as _time
|
||||
from aborist.cli import build_parser
|
||||
from aborist.store import transaction
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
OLD = "ab" * 32
|
||||
FRESH = "cd" * 32
|
||||
_seed_providence_record(db, cache_key=OLD, question="old", audit_mode="STRICT")
|
||||
_seed_providence_record(db, cache_key=FRESH, question="fresh", audit_mode="STRICT")
|
||||
|
||||
# Backdate OLD to 2h ago.
|
||||
long_ago = int(_time.time()) - 7200
|
||||
conn = connect(db)
|
||||
try:
|
||||
with transaction(conn):
|
||||
conn.execute(
|
||||
"UPDATE providence_cache SET created_at = ? WHERE cache_key = ?",
|
||||
(long_ago, OLD),
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
parser = build_parser()
|
||||
args = parser.parse_args([
|
||||
"--db", str(db),
|
||||
"burn-kindergarten",
|
||||
"--kindergarten-seconds", "3600",
|
||||
])
|
||||
rc = args.func(args)
|
||||
assert rc == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["status"] == "burned"
|
||||
assert payload["examined"] == 1 # only FRESH met the cutoff
|
||||
assert payload["burned"] == 1
|
||||
|
||||
conn = connect(db)
|
||||
try:
|
||||
rows = {r["cache_key"]: r for r in conn.execute(
|
||||
"SELECT cache_key FROM providence_cache"
|
||||
).fetchall()}
|
||||
finally:
|
||||
conn.close()
|
||||
assert OLD in rows
|
||||
assert FRESH not in rows
|
||||
|
||||
|
||||
def test_burn_kindergarten_dry_run_writes_nothing(tmp_path, capsys):
|
||||
"""--dry-run reports what would burn but doesn't write or audit."""
|
||||
from aborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
KEY = "ef" * 32
|
||||
_seed_providence_record(db, cache_key=KEY)
|
||||
conn = connect(db)
|
||||
try:
|
||||
events_before = conn.execute(
|
||||
"SELECT COUNT(*) FROM audit_events"
|
||||
).fetchone()[0]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
parser = build_parser()
|
||||
args = parser.parse_args([
|
||||
"--db", str(db),
|
||||
"burn-kindergarten",
|
||||
"--kindergarten-seconds", "3600",
|
||||
"--dry-run",
|
||||
])
|
||||
rc = args.func(args)
|
||||
assert rc == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["status"] == "dry_run"
|
||||
assert payload["examined"] == 1
|
||||
assert payload["burned"] == 0
|
||||
# Row still exists; audit chain unchanged.
|
||||
conn = connect(db)
|
||||
try:
|
||||
still_there = conn.execute(
|
||||
"SELECT 1 FROM providence_cache WHERE cache_key=?", (KEY,)
|
||||
).fetchone()
|
||||
events_after = conn.execute(
|
||||
"SELECT COUNT(*) FROM audit_events"
|
||||
).fetchone()[0]
|
||||
finally:
|
||||
conn.close()
|
||||
assert still_there is not None
|
||||
assert events_after == events_before
|
||||
|
||||
|
||||
def test_burn_kindergarten_zero_seconds_burns_everything(tmp_path, capsys):
|
||||
"""0-second window = burn every live providence_cache row."""
|
||||
from aborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
K1 = "11" * 32
|
||||
K2 = "22" * 32
|
||||
_seed_providence_record(db, cache_key=K1, question="q1")
|
||||
_seed_providence_record(db, cache_key=K2, question="q2")
|
||||
|
||||
parser = build_parser()
|
||||
args = parser.parse_args([
|
||||
"--db", str(db),
|
||||
"burn-kindergarten",
|
||||
"--kindergarten-seconds", "0",
|
||||
])
|
||||
rc = args.func(args)
|
||||
assert rc == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["burned"] == 2
|
||||
|
||||
conn = connect(db)
|
||||
try:
|
||||
remaining = conn.execute("SELECT COUNT(*) FROM providence_cache").fetchone()[0]
|
||||
finally:
|
||||
conn.close()
|
||||
assert remaining == 0
|
||||
|
||||
|
||||
def test_burn_kindergarten_respects_children_gate_without_force(tmp_path, capsys):
|
||||
"""A row with falsifications is REFUSED unless --force. Audit chain
|
||||
still grows on burned rows; refused rows leave no audit entry."""
|
||||
from aborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
BURNABLE = "33" * 32
|
||||
HAS_KIDS = "44" * 32
|
||||
_seed_providence_record(db, cache_key=BURNABLE)
|
||||
_seed_providence_record(db, cache_key=HAS_KIDS)
|
||||
_seed_falsification(db, cache_key=HAS_KIDS)
|
||||
|
||||
parser = build_parser()
|
||||
args = parser.parse_args([
|
||||
"--db", str(db),
|
||||
"burn-kindergarten",
|
||||
"--kindergarten-seconds", "3600",
|
||||
])
|
||||
rc = args.func(args)
|
||||
assert rc == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["burned"] == 1
|
||||
assert payload["refused_has_children"] == 1
|
||||
|
||||
|
||||
def test_burn_cli_returns_non_zero_on_refused(tmp_path, capsys):
|
||||
"""Non-zero exit when refused so scripts can detect and react."""
|
||||
from aborist.cli import build_parser
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue