Code Farm: per-plot auto-replant pause and crop-lock check; community sync and node work

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
+132
View File
@@ -92,3 +92,135 @@ def test_decode_text():
decode_text(b"a\x00b")
with pytest.raises(SkillFormatError):
decode_text(b"\xff\xfe\xfa")
import ipaddress
import socket
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from devplacepy.net_guard import is_blocked_address
from devplacepy.services.skills.fetcher import fetch_skill
from tests.conftest import run_async
SKILL_TEXT = (
"---\nname: wire-skill\ndescription: Fetched over a real socket.\n---\n"
"# Wire skill\n\nRead [the guide](references/guide.md) and `references/missing.md`.\n"
)
class Handler(BaseHTTPRequestHandler):
routes: dict = {}
def do_GET(self):
entry = self.routes.get(self.path.split("?", 1)[0])
if entry is None:
self.send_response(404)
self.end_headers()
return
status, headers, body = entry
self.send_response(status)
for name, value in headers.items():
self.send_header(name, value)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
return None
def global_address() -> str | None:
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
probe.connect(("8.8.8.8", 80))
address = probe.getsockname()[0]
except OSError:
return None
finally:
probe.close()
return None if is_blocked_address(ipaddress.ip_address(address)) else address
needs_global_address = pytest.mark.skipif(
global_address() is None,
reason="this host has no globally routable IPv4 address, so a local server cannot pass the SSRF guard without patching it",
)
@pytest.fixture
def origin():
port_holder = {}
def text(body, kind="text/markdown"):
return 200, {"Content-Type": kind}, body.encode()
Handler.routes = {
"/ok/": text("<html><body>skill directory</body></html>", "text/html"),
"/ok/SKILL.md": text(SKILL_TEXT),
"/ok/references/guide.md": text("# Guide\n\nStep one.\n"),
"/big/SKILL.md": text("---\nname: big\ndescription: too big\n---\n" + "x" * (limits.SKILL_MD_MAX_BYTES + 10)),
"/html/SKILL.md": text("<html><body>not a skill</body></html>", "text/html"),
"/bounce/SKILL.md": (302, {"Location": "PLACEHOLDER"}, b""),
"/loop/SKILL.md": (302, {"Location": "/loop/SKILL.md"}, b""),
}
server = ThreadingHTTPServer(("0.0.0.0", 0), Handler)
port_holder["port"] = server.server_address[1]
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
yield port_holder["port"]
server.shutdown()
server.server_close()
@needs_global_address
def test_a_skill_is_fetched_over_a_real_socket_with_its_bundled_file(origin):
address = global_address()
fetched = run_async(fetch_skill(f"http://{address}:{origin}/ok/SKILL.md"))
assert fetched.parsed.name == "wire-skill" and fetched.source_url.endswith("/ok/SKILL.md")
assert fetched.files == {"references/guide.md": "# Guide\n\nStep one.\n"}
assert [(item.path, item.reason) for item in fetched.skipped] == [("references/missing.md", "the server returned 404")]
@needs_global_address
def test_the_directory_address_resolves_to_the_skill_file(origin):
address = global_address()
fetched = run_async(fetch_skill(f"http://{address}:{origin}/ok/"))
assert fetched.parsed.name == "wire-skill"
@needs_global_address
def test_loopback_is_refused_even_though_the_server_is_right_there(origin):
with pytest.raises(SkillFetchError, match="private or local"):
run_async(fetch_skill(f"http://127.0.0.1:{origin}/ok/SKILL.md"))
@needs_global_address
def test_a_redirect_to_loopback_is_refused_on_the_second_hop(origin):
address = global_address()
Handler.routes["/bounce/SKILL.md"] = (302, {"Location": f"http://127.0.0.1:{origin}/ok/SKILL.md"}, b"")
with pytest.raises(SkillFetchError, match="private or local"):
run_async(fetch_skill(f"http://{address}:{origin}/bounce/SKILL.md"))
@needs_global_address
def test_a_redirect_loop_ends_with_a_clean_error(origin):
address = global_address()
with pytest.raises(SkillFetchError):
run_async(fetch_skill(f"http://{address}:{origin}/loop/SKILL.md"))
@needs_global_address
def test_an_oversize_skill_is_cut_at_the_limit_and_html_is_rejected(origin):
address = global_address()
with pytest.raises(SkillFetchError, match="byte limit"):
run_async(fetch_skill(f"http://{address}:{origin}/big/SKILL.md"))
with pytest.raises(SkillFetchError, match="HTML page"):
run_async(fetch_skill(f"http://{address}:{origin}/html/SKILL.md"))
@needs_global_address
def test_a_missing_skill_reports_the_status(origin):
address = global_address()
with pytest.raises(SkillFetchError, match="404"):
run_async(fetch_skill(f"http://{address}:{origin}/nothing/SKILL.md"))