Code Farm: per-plot auto-replant pause and crop-lock check; community sync and node work
DevPlace CI / test (push) Failing after 44m45s

Continuous Delivery could never be turned off once a crop was planted. Add a
one-shot per-plot "stop after harvest" flag (POST /game/plot/stop, Devii tool
game_plot_stop, game_plots.stop_after_harvest), cleared by the next plant,
autoreplant claim or Refactor. Autoreplant now also respects crop locks through
the shared _crop_lock predicate. Plan and measurements in happyplanting.md.

Also includes the pending community sync, node network and related work in the
tree; the full suite passed (4999) with all of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 02:23:43 +02:00
co-authored by Claude Sonnet 5.5
parent ce899fc5e1
commit 02ab747cd0
203 changed files with 11624 additions and 301 deletions
+134
View File
@@ -0,0 +1,134 @@
# retoor <retoor@molodetz.nl>
from collections import Counter
from itertools import islice
import pytest
from devplacepy.database import get_table, init_db
from devplacepy.database.sync import claim_identity
from devplacepy.services.sync.batch import BatchRunner
from devplacepy.services.sync.handles import HandleBuilder
from devplacepy.services.sync.records import SyncBatchForm
from devplacepy.services.sync.spec import HANDLES
from devplacepy.utils import generate_uid
ORIGIN = "handles.example.org"
OWN_PERSON = {
"abner_page_by_abnercoimbre": "abnercoimbre",
"jakearchibald_com_by_jaffathecak": "jaffathecak",
"spritely_institute_via_untrusem": "untrusem",
"libreboot_org_via_runxiyu": "runxiyu",
"lumi_codeberg_page_via_stilic": "stilic",
"decomp_dev_via_FedericoSchonborn": "federicoschonborn",
"via_rberger_2026-10-15_12_36_38_": "rberger",
"via_veqq_2026-10-14_23_47_30_cac": "veqq",
"costa_jacobpalm_dk_via_FedericoS": "federicos",
"entropicthoughts_com_via_lcapald": "lcapald",
"github_com_via_gmem": "gmem",
"try_haxy_net_via_krig": "krig",
"youtu_be_via_jeffective": "jeffective",
}
DOMAIN_ONLY = {
"costa_jacobpalm_dk": "jacobpalm",
"techcommunity_microsoft_com": "microsoft",
"gtoal_com": "gtoal",
"javidjamae": "javidjamae",
"john_smith": "john_smith",
}
@pytest.mark.parametrize("raw,expected", sorted((OWN_PERSON | DOMAIN_ONLY).items()))
def test_the_person_is_found_behind_the_site_and_the_stamp(raw, expected):
assert HandleBuilder.person(raw) == expected
@pytest.mark.parametrize("raw", sorted(OWN_PERSON))
def test_no_candidate_keeps_the_site_the_stamp_or_the_relation_word(raw):
for candidate in islice(HandleBuilder(ORIGIN, raw).candidates(raw), HANDLES.rerolls):
lowered = candidate.lower()
assert "_via_" not in lowered and "_by_" not in lowered
assert "2026" not in lowered and not lowered.startswith("via_")
def test_every_candidate_is_a_valid_handle_across_the_input_domain():
raws = ["", "a", "ab", "x" * 80, "UPPER_case", "ünï-cödé", "__", "-a-", "via", "by_via", "9999", "john.smith", "a_b_c_d_e_f_g_h"]
raws += [f"user{index}_by_someone{index}" for index in range(300)]
for index, raw in enumerate(raws):
seen = list(islice(HandleBuilder(ORIGIN, f"u{index}").candidates(raw), 60))
assert seen
for candidate in seen:
assert HANDLES.min_length <= len(candidate) <= HANDLES.max_length, (raw, candidate)
assert HandleBuilder.valid(candidate), (raw, candidate)
def test_the_same_record_always_gets_the_same_candidates():
first = list(islice(HandleBuilder(ORIGIN, "u1").candidates("someone_via_other"), 40))
second = list(islice(HandleBuilder(ORIGIN, "u1").candidates("someone_via_other"), 40))
assert first == second
def test_the_mix_follows_the_spec_weights():
counts = Counter()
total = 4000
for index in range(total):
first = next(iter(HandleBuilder(ORIGIN, f"id{index}").candidates("kernelhacker")))
counts["plain" if first == "kernelhacker" else "digits" if any(char.isdigit() for char in first) else "word"] += 1
plain = counts["plain"] / total * 100
assert abs(plain - HANDLES.plain_weight) < 5
assert counts["digits"] / total * 100 > 25
def test_candidates_differ_between_records_with_the_same_name():
firsts = {next(iter(HandleBuilder(ORIGIN, f"id{index}").candidates("same"))) for index in range(200)}
assert len(firsts) > 40
@pytest.fixture
def clean(local_db):
init_db()
yield
for table in ("sync_links", "sync_identities"):
get_table(table).delete(origin=ORIGIN)
def push(records, humanize):
return BatchRunner("users", humanize=humanize).run(SyncBatchForm(origin=ORIGIN, records=records))
def stored_name(origin_uid):
link = get_table("sync_links").find_one(origin=ORIGIN, kind="users", origin_uid=origin_uid)
return get_table("users").find_one(uid=link["local_uid"])["username"]
def test_a_new_user_gets_a_realistic_handle_and_an_update_keeps_it(clean):
raw = f"abner_page_by_abnercoimbre{generate_uid()[-4:]}"
record = {"origin_uid": "h1", "username": raw, "created_at": "2025-01-01T00:00:00Z"}
push([record], humanize=True)
name = stored_name("h1")
assert name != raw and "_by_" not in name and name.startswith("abnercoimbre")
push([{**record, "bio": "changed bio", "updated_at": "2025-02-01T00:00:00Z"}], humanize=True)
assert stored_name("h1") == name
def test_without_the_flag_the_original_behaviour_is_untouched(clean):
raw = f"plain_sender_{generate_uid()[-6:]}"
push([{"origin_uid": "h2", "username": raw, "created_at": "2025-01-01T00:00:00Z"}], humanize=False)
assert stored_name("h2") == raw
def test_an_alias_clone_name_is_never_decorated(clean):
alias = f"mirakou{generate_uid()[-5:]}"
claim_identity(ORIGIN, "alice", "alice", alias, "{}", False)
push([{"origin_uid": "alice", "username": alias, "created_at": "2025-01-01T00:00:00Z"}], humanize=True)
assert stored_name("alice") == alias
def test_two_senders_with_one_person_get_two_distinct_free_handles(clean):
base = f"samename{generate_uid()[-5:]}"
push(
[{"origin_uid": f"s{index}", "username": f"site_{index}_via_{base}", "created_at": "2025-01-01T00:00:00Z"} for index in range(12)],
humanize=True,
)
names = [stored_name(f"s{index}") for index in range(12)]
assert len({name.lower() for name in names}) == 12
+99
View File
@@ -0,0 +1,99 @@
# retoor <retoor@molodetz.nl>
import pytest
from devplacepy.database import get_table, init_db
from devplacepy.database.sync import claim_identity, claim_link
from devplacepy.services.sync.healing import AliasHealer
from devplacepy.services.sync.processors import UserProcessor
from devplacepy.utils import generate_uid
ORIGIN = "heal.example.org"
@pytest.fixture(autouse=True)
def clean(local_db):
init_db()
for table in ("sync_identities", "sync_links"):
get_table(table).delete(origin=ORIGIN)
yield
for table in ("sync_identities", "sync_links"):
get_table(table).delete(origin=ORIGIN)
def clone(source="alice", alias="Mira"):
suffix = generate_uid()[-6:]
alias = f"{alias}{suffix}"
processor = UserProcessor(ORIGIN)
result = processor.process(0, {"origin_uid": source, "username": alias, "created_at": "2026-01-01T00:00:00Z"})
claim_identity(ORIGIN, source.capitalize(), source, alias, "style", False)
return result.local_uid, alias
def synced_text(origin_uid, table, text, kind):
uid = generate_uid()
get_table(table).insert(
{"uid": uid, "user_uid": "u", "title": None, "content": text, "created_at": "2026-01-01T00:00:00+00:00", "target_uid": "x", "target_type": "post", "deleted_at": None, "deleted_by": None}
)
claim_link(ORIGIN, kind, origin_uid, uid, "fp", "2026-01-01T00:00:00+00:00")
return uid
def test_nothing_changes_when_the_clone_still_has_the_alias():
clone()
report = AliasHealer(ORIGIN).heal()
assert not report.renamed and not report.conflicts and report.rewritten == 0
def test_an_administrator_rename_moves_the_identity_and_the_stored_mentions():
uid, alias = clone()
post = synced_text("p1", "posts", f"thanks @{alias} and {alias} again, @{alias}x stays", "posts")
comment = synced_text("c1", "comments", f"@{alias.lower()} agreed", "comments")
other = synced_text("p2", "posts", "unrelated text", "posts")
renamed = f"Renamed{generate_uid()[-6:]}"
get_table("users").update({"uid": uid, "username": renamed}, ["uid"])
report = AliasHealer(ORIGIN).heal()
assert report.renamed == {alias: renamed} and report.rewritten == 2
row = get_table("sync_identities").find_one(origin=ORIGIN, source_key="alice")
assert row["our_name"] == renamed and row["our_key"] == renamed.lower()
assert get_table("posts").find_one(uid=post)["content"] == f"thanks @{renamed} and {renamed} again, @{alias}x stays"
assert get_table("comments").find_one(uid=comment)["content"] == f"@{renamed} agreed"
assert get_table("posts").find_one(uid=other)["content"] == "unrelated text"
assert not AliasHealer(ORIGIN).heal().renamed
def test_a_suffixed_clone_name_is_adopted(local_db):
uid, alias = clone("bob", "Taken")
suffixed = f"{alias}-heal"
get_table("users").update({"uid": uid, "username": suffixed}, ["uid"])
assert AliasHealer(ORIGIN).heal().renamed == {alias: suffixed}
assert get_table("sync_identities").find_one(origin=ORIGIN, source_key="bob")["our_name"] == suffixed
def test_a_name_that_belongs_to_another_identity_is_a_conflict_not_a_steal():
first_uid, first_alias = clone("alice", "Una")
_, second_alias = clone("carol", "Duo")
get_table("users").update({"uid": first_uid, "username": second_alias}, ["uid"])
report = AliasHealer(ORIGIN).heal()
assert report.conflicts == ["alice"] and not report.renamed
assert get_table("sync_identities").find_one(origin=ORIGIN, source_key="alice")["our_name"] == first_alias
def test_a_purged_clone_is_ignored():
uid, _ = clone()
get_table("users").delete(uid=uid)
assert AliasHealer(ORIGIN).heal().renamed == {}
def test_the_rewrite_work_per_run_is_bounded(monkeypatch):
from dataclasses import replace
from devplacepy.services.sync import healing
from devplacepy.services.sync.spec import ALIAS
uid, alias = clone()
for index in range(5):
synced_text(f"p{index}", "posts", f"hello @{alias}", "posts")
monkeypatch.setattr(healing, "ALIAS", replace(ALIAS, heal_rewrites_per_run=2))
get_table("users").update({"uid": uid, "username": f"Bounded{generate_uid()[-6:]}"}, ["uid"])
assert AliasHealer(ORIGIN).heal().rewritten == 2
+64
View File
@@ -83,3 +83,67 @@ def test_envelope_normalizes_origin_and_bounds_batch():
SyncBatchForm(origin="other.org", records=[{}] * (LIMITS.max_batch_records + 1))
with pytest.raises(ValidationError):
SyncBatchForm(origin="bad origin", records=[{}])
def test_new_kinds_have_models_and_a_published_schema():
from devplacepy.services.sync.records import RECORD_MODELS
from devplacepy.services.sync.spec import KINDS
assert set(RECORD_MODELS) == set(KINDS)
for model in RECORD_MODELS.values():
assert model.model_json_schema()["title"]
def test_vote_value_and_target_kind_are_closed_sets():
import pytest
from pydantic import ValidationError
from devplacepy.services.sync.records import VoteRecord
base = {"origin_uid": "v", "voter_uid": "u", "target_kind": "post", "target_uid": "p", "value": 1, "created_at": "2025-01-01T00:00:00Z"}
assert VoteRecord.model_validate(base).value == 1
for change in ({"value": 0}, {"value": 2}, {"target_kind": "gist"}, {"voter_uid": "bad id"}):
with pytest.raises(ValidationError):
VoteRecord.model_validate({**base, **change})
def test_attachment_fingerprint_ignores_the_blob_but_not_its_hash():
import base64
from devplacepy.services.sync.records import AttachmentRecord
def make(data):
return AttachmentRecord.model_validate({
"origin_uid": "a", "author_uid": "u", "target_kind": "post", "target_uid": "p",
"filename": "dir/evil\\name.png", "content_b64": base64.b64encode(data).decode(), "created_at": "2025-01-01T00:00:00Z",
})
first, second = make(b"one"), make(b"two")
assert first.filename == "name.png"
assert "content_b64" not in first.content_fields() and first.content_fields()["sha256"] != second.content_fields()["sha256"]
assert first.content_bytes() == b"one"
def test_batch_byte_cap(monkeypatch):
import pytest
from dataclasses import replace
from pydantic import ValidationError
from devplacepy.services.sync import records
from devplacepy.services.sync.spec import LIMITS
monkeypatch.setattr(records, "LIMITS", replace(LIMITS, max_batch_bytes=100))
with pytest.raises(ValidationError):
records.SyncBatchForm(origin="a.b", records=[{"x": "y" * 200}])
def test_ack_form_validates_kinds_and_ids():
import pytest
from pydantic import ValidationError
from devplacepy.services.sync.records import SyncAckForm
assert SyncAckForm(origin="A.B", items='[{"kind": "posts", "origin_uid": "p1"}]').items == [{"kind": "posts", "origin_uid": "p1"}]
for items in ([], [{"kind": "gists", "origin_uid": "x"}], [{"kind": "posts", "origin_uid": ""}], "not json"):
with pytest.raises(ValidationError):
SyncAckForm(origin="a.b", items=items)
+125
View File
@@ -0,0 +1,125 @@
# retoor <retoor@molodetz.nl>
import json
import pytest
from devplacepy.database import get_table, init_db
from devplacepy.database.sync import claim_link, load_candidates
from devplacepy.services.sync.options import SyncOptions
from devplacepy.services.sync.selection import Selector
from devplacepy.services.sync.spec import LIMITS, SKIP_MIN_COMMENTS
ORIGIN = "selection.example.org"
@pytest.fixture
def world(local_db):
init_db()
get_table("sync_candidates").delete()
get_table("sync_links").delete(origin=ORIGIN)
yield
get_table("sync_candidates").delete()
get_table("sync_links").delete(origin=ORIGIN)
def options(**extra):
return SyncOptions(min_comments=2, **extra)
def user(uid):
return {"origin_uid": uid, "username": f"user{uid}", "created_at": "2026-01-01T00:00:00Z"}
def post(uid, author="a"):
return {"origin_uid": uid, "author_uid": author, "content": f"post body {uid} long enough", "created_at": "2026-01-02T00:00:00Z"}
def comment(uid, on, author="b", parent=None):
record = {"origin_uid": uid, "post_uid": on, "author_uid": author, "content": f"comment {uid}", "created_at": "2026-01-03T00:00:00Z"}
if parent:
record["parent_uid"] = parent
return record
def run(records, persist=True, **extra):
selector = Selector(ORIGIN, options(**extra), persist=persist)
return selector, *selector.select(records)
def test_a_post_below_the_minimum_is_parked_with_its_comments_and_authors(world):
selector, kept, skipped = run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]})
assert kept["posts"] == [] and kept["comments"] == [] and skipped[SKIP_MIN_COMMENTS] == 1
assert selector.parked == 1
stored = json.loads(load_candidates(ORIGIN)[0]["record"])
assert stored["post"]["origin_uid"] == "p1" and [item["origin_uid"] for item in stored["comments"]] == ["c1"]
assert {item["origin_uid"] for item in stored["users"]} == {"a", "b"}
def test_comments_arriving_in_a_later_run_complete_the_post(world):
run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]})
selector, kept, skipped = run({"users": [user("b")], "posts": [], "comments": [comment("c2", "p1")]})
assert selector.revived == 1
assert [item["origin_uid"] for item in kept["posts"]] == ["p1"]
assert [item["origin_uid"] for item in kept["comments"]] == ["c1", "c2"]
assert {item["origin_uid"] for item in kept["users"]} == {"a", "b"}
assert not skipped and load_candidates(ORIGIN) == []
def test_a_repeated_comment_is_counted_once(world):
run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]})
selector, kept, skipped = run({"users": [], "posts": [], "comments": [comment("c1", "p1")]})
assert kept["posts"] == [] and skipped[SKIP_MIN_COMMENTS] == 1
assert len(load_candidates(ORIGIN)) == 1
def test_reply_order_is_kept_parent_before_reply(world):
run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]})
_, kept, _ = run({"users": [], "posts": [], "comments": [comment("c2", "p1", parent="c1")]})
assert [item["origin_uid"] for item in kept["comments"]] == ["c1", "c2"]
def test_a_dry_run_neither_reads_nor_writes_candidates(world):
selector, _, skipped = run({"users": [user("a")], "posts": [post("p1")], "comments": []}, persist=False)
assert skipped[SKIP_MIN_COMMENTS] == 1 and selector.parked == 0 and load_candidates(ORIGIN) == []
def test_a_known_post_is_never_parked_or_dropped(world):
claim_link(ORIGIN, "posts", "p9", "local-9", "x", "2026-01-01T00:00:00+00:00")
selector, kept, skipped = run({"users": [user("a")], "posts": [post("p9")], "comments": []})
assert [item["origin_uid"] for item in kept["posts"]] == ["p9"] and not skipped and selector.parked == 0
def test_the_candidate_list_is_capped_per_origin(world, monkeypatch):
from dataclasses import replace
from devplacepy.services.sync import selection
monkeypatch.setattr(selection, "LIMITS", replace(LIMITS, candidate_max_per_origin=2))
selector, _, _ = run({"users": [user("a")], "posts": [post("p1"), post("p2"), post("p3")], "comments": []})
assert selector.parked == 2 and len(load_candidates(ORIGIN)) == 2
def test_an_oversize_candidate_is_dropped_not_stored(world, monkeypatch):
from dataclasses import replace
from devplacepy.services.sync import selection
monkeypatch.setattr(selection, "LIMITS", replace(LIMITS, candidate_max_bytes=50))
selector, _, skipped = run({"users": [user("a")], "posts": [post("p1")], "comments": []})
assert skipped[SKIP_MIN_COMMENTS] == 1 and selector.parked == 0
def test_expired_candidates_are_not_revived(world):
run({"users": [user("a")], "posts": [post("p1")], "comments": []})
get_table("sync_candidates").update({"uid": load_candidates(ORIGIN)[0]["uid"], "expires_at": "2000-01-01T00:00:00+00:00"}, ["uid"])
selector, _, _ = run({"users": [], "posts": [], "comments": []})
assert selector.revived == 0 and load_candidates(ORIGIN) == []
def test_other_filters_stay_final_and_clear_the_candidate(world):
run({"users": [user("a")], "posts": [post("p1")], "comments": []})
selector, kept, skipped = run({"users": [user("a")], "posts": [], "comments": []}, topics=("devlog",))
from devplacepy.services.sync.spec import SKIP_TOPIC
assert kept["posts"] == [] and skipped[SKIP_TOPIC] == 1 and load_candidates(ORIGIN) == []