2026-05-11 03:14:43 +02:00
|
|
|
LOCUST_PORT ?= 10502
|
|
|
|
|
LOCUST_WEB_PORT ?= 10503
|
|
|
|
|
LOCUST_DB_DIR ?= /tmp/devplace_locust
|
|
|
|
|
LOCUST_DB ?= $(LOCUST_DB_DIR)/datastore.db
|
|
|
|
|
LOCUST_USERS ?= 20
|
|
|
|
|
LOCUST_SPAWN_RATE ?= 5
|
|
|
|
|
LOCUST_RUN_TIME ?= 120s
|
2026-06-14 16:46:36 +02:00
|
|
|
LOCUST_WEB_WORKERS ?= 4
|
|
|
|
|
WEB_WORKERS ?= $(shell nproc 2>/dev/null || echo 2)
|
2026-05-23 05:57:05 +02:00
|
|
|
DEVPLACE_RATE_LIMIT ?= 1000000
|
2026-05-11 03:14:43 +02:00
|
|
|
|
2026-06-13 18:27:41 +02:00
|
|
|
PYTHONDONTWRITEBYTECODE := 1
|
|
|
|
|
export PYTHONDONTWRITEBYTECODE
|
|
|
|
|
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
.PHONY: install dev clean tree tree-loc zip test test-headed test-unit test-api test-e2e test-fast test-failed test-first-failure test-slowest test-cache-clean coverage coverage-headed coverage-html locust locust-headless
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
install:
|
|
|
|
|
pip install -e .
|
2026-06-14 02:16:22 +02:00
|
|
|
python -m playwright install chromium
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
dev:
|
2026-06-09 06:41:27 +02:00
|
|
|
uvicorn devplacepy.main:app --reload --reload-dir devplacepy --host 0.0.0.0 --port 10500 --backlog 4096
|
2026-05-11 05:30:51 +02:00
|
|
|
|
|
|
|
|
prod:
|
2026-06-14 16:46:36 +02:00
|
|
|
DEVPLACE_STATIC_VERSION=$$(date +%s) DEVPLACE_TEMPLATE_AUTO_RELOAD=0 DEVPLACE_WEB_WORKERS=$(WEB_WORKERS) uvicorn devplacepy.main:app --host 0.0.0.0 --port 10500 --workers $(WEB_WORKERS) --backlog 8192 --proxy-headers --forwarded-allow-ips '*'
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-06-14 09:48:10 +02:00
|
|
|
delete-pyc:
|
|
|
|
|
find . -name "__pycache__" -type d -prune -exec rm -rf {} + 2>/dev/null || true
|
|
|
|
|
find . -name "*.pyc" -delete
|
|
|
|
|
|
2026-06-13 18:27:41 +02:00
|
|
|
tree:
|
2026-06-13 20:34:47 +02:00
|
|
|
git ls-files | tree --fromfile --noreport
|
|
|
|
|
|
|
|
|
|
tree-loc:
|
|
|
|
|
@git ls-files | while IFS= read -r f; do \
|
|
|
|
|
loc=$$(wc -l < "$$f" 2>/dev/null || echo 0); \
|
|
|
|
|
printf '%s [%s LOC]\n' "$$f" "$$loc"; \
|
|
|
|
|
done | tree --fromfile --noreport
|
|
|
|
|
|
|
|
|
|
zip:
|
|
|
|
|
@rm -f $(notdir $(CURDIR)).zip
|
|
|
|
|
@git ls-files -z | xargs -0 zip -q $(notdir $(CURDIR)).zip
|
|
|
|
|
@printf 'Wrote %s (%s files)\n' "$(notdir $(CURDIR)).zip" "$$(git ls-files | wc -l)"
|
2026-06-13 18:27:41 +02:00
|
|
|
|
2026-05-10 09:08:12 +02:00
|
|
|
test:
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
PLAYWRIGHT_HEADLESS=1 python -m pytest tests/
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
test-headed:
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
PLAYWRIGHT_HEADLESS=0 python -m pytest tests/
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-06-13 16:32:33 +02:00
|
|
|
test-unit:
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
python -m pytest tests/unit
|
2026-06-13 16:32:33 +02:00
|
|
|
|
|
|
|
|
test-api:
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
python -m pytest tests/api
|
2026-06-13 16:32:33 +02:00
|
|
|
|
|
|
|
|
test-e2e:
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
PLAYWRIGHT_HEADLESS=1 python -m pytest tests/e2e
|
|
|
|
|
|
|
|
|
|
test-fast:
|
|
|
|
|
python -m pytest tests/unit tests/api
|
|
|
|
|
|
|
|
|
|
test-failed:
|
|
|
|
|
PLAYWRIGHT_HEADLESS=1 python -m pytest tests/ --last-failed --last-failed-no-failures none
|
|
|
|
|
|
|
|
|
|
test-first-failure:
|
|
|
|
|
PLAYWRIGHT_HEADLESS=1 python -m pytest tests/ -x
|
|
|
|
|
|
|
|
|
|
test-slowest:
|
|
|
|
|
PLAYWRIGHT_HEADLESS=1 python -m pytest tests/ --durations=40
|
2026-06-13 16:32:33 +02:00
|
|
|
|
2026-06-05 20:33:35 +02:00
|
|
|
coverage:
|
|
|
|
|
rm -f .coverage .coverage.*
|
|
|
|
|
COVERAGE_PROCESS_START=$(CURDIR)/.coveragerc PLAYWRIGHT_HEADLESS=1 \
|
2026-06-12 07:43:33 +02:00
|
|
|
python -m coverage run -m pytest tests/
|
2026-06-05 20:33:35 +02:00
|
|
|
python -m coverage combine
|
|
|
|
|
python -m coverage report
|
|
|
|
|
|
|
|
|
|
coverage-headed:
|
|
|
|
|
rm -f .coverage .coverage.*
|
|
|
|
|
COVERAGE_PROCESS_START=$(CURDIR)/.coveragerc PLAYWRIGHT_HEADLESS=0 \
|
2026-06-12 07:43:33 +02:00
|
|
|
python -m coverage run -m pytest tests/
|
2026-06-05 20:33:35 +02:00
|
|
|
python -m coverage combine
|
|
|
|
|
python -m coverage report
|
|
|
|
|
|
|
|
|
|
coverage-html: coverage
|
|
|
|
|
python -m coverage html
|
|
|
|
|
@echo "Report written to htmlcov/index.html"
|
|
|
|
|
|
2026-05-11 03:14:43 +02:00
|
|
|
locust:
|
|
|
|
|
export DEVPLACE_DATABASE_URL="sqlite:///$(LOCUST_DB)"; \
|
2026-05-23 05:57:05 +02:00
|
|
|
export DEVPLACE_RATE_LIMIT=$(DEVPLACE_RATE_LIMIT); \
|
2026-06-05 17:44:12 +02:00
|
|
|
fuser -k $(LOCUST_PORT)/tcp 2>/dev/null || true; \
|
|
|
|
|
sleep 1; \
|
2026-05-11 03:14:43 +02:00
|
|
|
mkdir -p $(LOCUST_DB_DIR); \
|
|
|
|
|
rm -f $(LOCUST_DB); \
|
2026-06-14 16:46:36 +02:00
|
|
|
DEVPLACE_TEMPLATE_AUTO_RELOAD=0 DEVPLACE_WEB_WORKERS=$(LOCUST_WEB_WORKERS) uvicorn devplacepy.main:app --host 127.0.0.1 --port $(LOCUST_PORT) --workers $(LOCUST_WEB_WORKERS) --backlog 8192 > /tmp/devplace_locust_server.log 2>&1 & \
|
2026-05-11 03:14:43 +02:00
|
|
|
PID=$$!; \
|
2026-06-05 17:44:12 +02:00
|
|
|
while kill -0 $$PID 2>/dev/null && ! curl -s http://127.0.0.1:$(LOCUST_PORT)/ > /dev/null 2>&1; do sleep 0.5; done; \
|
|
|
|
|
if ! kill -0 $$PID 2>/dev/null; then echo "Server failed to start (port $(LOCUST_PORT) busy?). See /tmp/devplace_locust_server.log"; exit 1; fi; \
|
2026-05-11 03:14:43 +02:00
|
|
|
locust --host http://127.0.0.1:$(LOCUST_PORT) --web-port $(LOCUST_WEB_PORT); \
|
|
|
|
|
kill $$PID 2>/dev/null || true; \
|
|
|
|
|
rm -rf $(LOCUST_DB_DIR)
|
|
|
|
|
|
|
|
|
|
locust-headless:
|
|
|
|
|
export DEVPLACE_DATABASE_URL="sqlite:///$(LOCUST_DB)"; \
|
2026-05-23 05:57:05 +02:00
|
|
|
export DEVPLACE_RATE_LIMIT=$(DEVPLACE_RATE_LIMIT); \
|
2026-06-05 17:44:12 +02:00
|
|
|
fuser -k $(LOCUST_PORT)/tcp 2>/dev/null || true; \
|
|
|
|
|
sleep 1; \
|
2026-05-11 03:14:43 +02:00
|
|
|
mkdir -p $(LOCUST_DB_DIR); \
|
|
|
|
|
rm -f $(LOCUST_DB); \
|
2026-06-14 16:46:36 +02:00
|
|
|
DEVPLACE_TEMPLATE_AUTO_RELOAD=0 DEVPLACE_WEB_WORKERS=$(LOCUST_WEB_WORKERS) uvicorn devplacepy.main:app --host 127.0.0.1 --port $(LOCUST_PORT) --workers $(LOCUST_WEB_WORKERS) --backlog 8192 > /tmp/devplace_locust_server.log 2>&1 & \
|
2026-05-11 03:14:43 +02:00
|
|
|
PID=$$!; \
|
2026-06-05 17:44:12 +02:00
|
|
|
while kill -0 $$PID 2>/dev/null && ! curl -s http://127.0.0.1:$(LOCUST_PORT)/ > /dev/null 2>&1; do sleep 0.5; done; \
|
|
|
|
|
if ! kill -0 $$PID 2>/dev/null; then echo "Server failed to start (port $(LOCUST_PORT) busy?). See /tmp/devplace_locust_server.log"; exit 1; fi; \
|
2026-05-11 03:14:43 +02:00
|
|
|
locust --host http://127.0.0.1:$(LOCUST_PORT) --headless -u $(LOCUST_USERS) -r $(LOCUST_SPAWN_RATE) --run-time $(LOCUST_RUN_TIME) --html=$(LOCUST_DB_DIR)/report.html; \
|
|
|
|
|
kill $$PID 2>/dev/null || true; \
|
|
|
|
|
rm -rf $(LOCUST_DB_DIR)
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
clean:
|
|
|
|
|
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
|
|
|
find . -type f -name '*.pyc' -delete
|
|
|
|
|
rm -rf devplacepy.egg-info
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
rm -rf .pytest_cache
|
2026-05-10 09:08:12 +02:00
|
|
|
rm -rf .venv
|
2026-05-12 15:07:34 +02:00
|
|
|
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
test-cache-clean:
|
|
|
|
|
rm -rf .pytest_cache
|
|
|
|
|
|
2026-06-09 06:41:27 +02:00
|
|
|
# Container Manager works out of the box: the overlay installs the docker CLI in
|
|
|
|
|
# the image and mounts the host socket. DOCKER_GID is read straight from the
|
chore: consolidate runtime data layout under single `data/` root directory
Migrate all runtime artifacts (database, uploads, VAPID keys, locks, bot state, container workspaces, zip staging) from scattered locations (`var/`, `devplacepy/static/uploads/`) into a unified `data/` directory. Update `config.py` as single source of truth with `DATA_PATHS` registry and `ensure_data_dirs()`, add `devplace migrate-data` CLI command with CRC verification and idempotent relocation, adjust `.dockerignore`, `.env.example`, `.gitignore`, `Dockerfile`, `Makefile`, `README.md`, `AGENTS.md`, `CLAUDE.md`, and all import paths in `attachments.py` to reference `config.UPLOADS_DIR`/`ATTACHMENTS_DIR` instead of computing from `STATIC_DIR`.
2026-06-13 21:06:43 +02:00
|
|
|
# socket so the UID-1000 app can use it; the data dir is the project's own data/
|
2026-06-09 06:41:27 +02:00
|
|
|
# at its real host path, so the DooD bind-mount (host == container path) holds
|
|
|
|
|
# with no /srv dir and no sudo.
|
|
|
|
|
COMPOSE := docker compose -f docker-compose.yml -f docker-compose.containers.yml
|
chore: consolidate runtime data layout under single `data/` root directory
Migrate all runtime artifacts (database, uploads, VAPID keys, locks, bot state, container workspaces, zip staging) from scattered locations (`var/`, `devplacepy/static/uploads/`) into a unified `data/` directory. Update `config.py` as single source of truth with `DATA_PATHS` registry and `ensure_data_dirs()`, add `devplace migrate-data` CLI command with CRC verification and idempotent relocation, adjust `.dockerignore`, `.env.example`, `.gitignore`, `Dockerfile`, `Makefile`, `README.md`, `AGENTS.md`, `CLAUDE.md`, and all import paths in `attachments.py` to reference `config.UPLOADS_DIR`/`ATTACHMENTS_DIR` instead of computing from `STATIC_DIR`.
2026-06-13 21:06:43 +02:00
|
|
|
DEVPLACE_DATA_DIR ?= $(CURDIR)/data
|
2026-06-09 06:41:27 +02:00
|
|
|
DOCKER_GID ?= $(shell stat -c '%g' /var/run/docker.sock 2>/dev/null)
|
Route container proxies through the leg that is actually reachable
The workspace editor hung for 60s and then 504'd. Three independent faults were
stacked behind that one symptom.
Reachability: editor_target delegated to proxy_target, which returns
CONTAINER_PROXY_HOST plus the published host port and never falls back to the
container. From inside the app container that address crosses docker0 into the
host INPUT chain, whose policy is DROP with an allow-list that does not include
the published port range, so the packet was dropped and the request hung rather
than being refused. Measured from the app container: container_ip:8443 answers
302, gateway:20006 is dropped. One shared reachable_target now prefers the
direct container leg and falls back to the published port, and editor_target
uses tunnel_target as services/containers/CLAUDE.md already required. The same
defect affected /p/{slug} ingress and every tunnel, since all three resolved
through proxy_target.
The recorded measurement that motivated the old order (container_ip times out,
gateway connects) no longer holds: make docker-attach puts the app on the
instances' bridge network, which is what makes the direct leg work.
Duplicate response headers: the forwarding core relayed the upstream Date and
Server alongside the ones the serving layer generates, so every proxied
response carried two of each. Both are singleton headers and duplicating them
is malformed HTTP.
Serialization: WorkspaceViewOut declared flag_reason and three sibling strings
as str, so a NULL column made the workspace page 500 for JSON clients.
Documents the two public hostnames and the devplace.net SSH tunnel, so a future
session does not conclude the site is down after pointing curl --resolve at an
address the hostname does not resolve to, and adds the layered procedure for
diagnosing a production failure.
Verified on production with Playwright over both hostnames: the code-server
login renders and the workbench loads. Suite: 3345 passed.
2026-08-11 20:03:15 +02:00
|
|
|
DEVPLACE_CONTAINER_NETWORK ?= bridge
|
2026-06-09 06:41:27 +02:00
|
|
|
export DEVPLACE_DATA_DIR
|
|
|
|
|
export DOCKER_GID
|
2026-05-12 15:07:34 +02:00
|
|
|
|
Route container proxies through the leg that is actually reachable
The workspace editor hung for 60s and then 504'd. Three independent faults were
stacked behind that one symptom.
Reachability: editor_target delegated to proxy_target, which returns
CONTAINER_PROXY_HOST plus the published host port and never falls back to the
container. From inside the app container that address crosses docker0 into the
host INPUT chain, whose policy is DROP with an allow-list that does not include
the published port range, so the packet was dropped and the request hung rather
than being refused. Measured from the app container: container_ip:8443 answers
302, gateway:20006 is dropped. One shared reachable_target now prefers the
direct container leg and falls back to the published port, and editor_target
uses tunnel_target as services/containers/CLAUDE.md already required. The same
defect affected /p/{slug} ingress and every tunnel, since all three resolved
through proxy_target.
The recorded measurement that motivated the old order (container_ip times out,
gateway connects) no longer holds: make docker-attach puts the app on the
instances' bridge network, which is what makes the direct leg work.
Duplicate response headers: the forwarding core relayed the upstream Date and
Server alongside the ones the serving layer generates, so every proxied
response carried two of each. Both are singleton headers and duplicating them
is malformed HTTP.
Serialization: WorkspaceViewOut declared flag_reason and three sibling strings
as str, so a NULL column made the workspace page 500 for JSON clients.
Documents the two public hostnames and the devplace.net SSH tunnel, so a future
session does not conclude the site is down after pointing curl --resolve at an
address the hostname does not resolve to, and adds the layered procedure for
diagnosing a production failure.
Verified on production with Playwright over both hostnames: the code-server
login renders and the workbench loads. Suite: 3345 passed.
2026-08-11 20:03:15 +02:00
|
|
|
.PHONY: docker-build docker-up docker-attach docker-reload docker-down docker-logs docker-clean docker-prep ppy
|
2026-06-09 16:06:02 +02:00
|
|
|
|
|
|
|
|
# Build the single shared container image every instance runs. Build once;
|
|
|
|
|
# rebuild only when ppy.Dockerfile, the sudo shim, or pagent change.
|
|
|
|
|
ppy:
|
|
|
|
|
docker build --network=host -f ppy.Dockerfile -t ppy:latest devplacepy/services/containers/files
|
2026-05-12 15:07:34 +02:00
|
|
|
|
2026-06-09 06:41:27 +02:00
|
|
|
docker-prep:
|
|
|
|
|
mkdir -p $(DEVPLACE_DATA_DIR)
|
|
|
|
|
|
|
|
|
|
docker-build: docker-prep
|
|
|
|
|
$(COMPOSE) build
|
|
|
|
|
|
|
|
|
|
docker-up: docker-prep
|
|
|
|
|
$(COMPOSE) up -d
|
Route container proxies through the leg that is actually reachable
The workspace editor hung for 60s and then 504'd. Three independent faults were
stacked behind that one symptom.
Reachability: editor_target delegated to proxy_target, which returns
CONTAINER_PROXY_HOST plus the published host port and never falls back to the
container. From inside the app container that address crosses docker0 into the
host INPUT chain, whose policy is DROP with an allow-list that does not include
the published port range, so the packet was dropped and the request hung rather
than being refused. Measured from the app container: container_ip:8443 answers
302, gateway:20006 is dropped. One shared reachable_target now prefers the
direct container leg and falls back to the published port, and editor_target
uses tunnel_target as services/containers/CLAUDE.md already required. The same
defect affected /p/{slug} ingress and every tunnel, since all three resolved
through proxy_target.
The recorded measurement that motivated the old order (container_ip times out,
gateway connects) no longer holds: make docker-attach puts the app on the
instances' bridge network, which is what makes the direct leg work.
Duplicate response headers: the forwarding core relayed the upstream Date and
Server alongside the ones the serving layer generates, so every proxied
response carried two of each. Both are singleton headers and duplicating them
is malformed HTTP.
Serialization: WorkspaceViewOut declared flag_reason and three sibling strings
as str, so a NULL column made the workspace page 500 for JSON clients.
Documents the two public hostnames and the devplace.net SSH tunnel, so a future
session does not conclude the site is down after pointing curl --resolve at an
address the hostname does not resolve to, and adds the layered procedure for
diagnosing a production failure.
Verified on production with Playwright over both hostnames: the code-server
login renders and the workbench loads. Suite: 3345 passed.
2026-08-11 20:03:15 +02:00
|
|
|
$(MAKE) docker-attach
|
|
|
|
|
|
|
|
|
|
# Workspace tunnels reach a container port that was never published on the host,
|
|
|
|
|
# so the app must sit on the same docker network as the instances it runs. The
|
|
|
|
|
# default bridge rejects the network-scoped aliases compose always sends, so
|
|
|
|
|
# this cannot live in docker-compose.containers.yml and is wired here instead.
|
|
|
|
|
docker-attach:
|
|
|
|
|
@app=$$($(COMPOSE) ps -q app); \
|
|
|
|
|
test -n "$$app" || { echo "app container is not running"; exit 1; }; \
|
|
|
|
|
docker network connect $(DEVPLACE_CONTAINER_NETWORK) $$app 2>/dev/null \
|
|
|
|
|
&& echo "attached app to the $(DEVPLACE_CONTAINER_NETWORK) network" \
|
|
|
|
|
|| echo "app is already on the $(DEVPLACE_CONTAINER_NETWORK) network"
|
2026-05-12 15:07:34 +02:00
|
|
|
|
Fix circular import, primary-admin NULL trap, and add gateway quota reset
Restores a working import graph and closes two data-correctness bugs, plus
adds a reset for the AI gateway's rolling 24h spend.
Circular import: database/__init__ -> engagement -> content -> utils ->
database made the package unimportable. get_project_devlog moves out of
database/engagement.py into content.py, where enrich_items already lives.
Primary administrator: _can_hold_primary_admin read is_active with
bool(row.get("is_active")), so an admin row whose is_active column is SQL
NULL (any row predating the column) was treated as deactivated and skipped.
Every other site defaults an unknown is_active to active; this one now does
too.
Profile JSON: xp_next_level and xp_progress_pct were computed but only put on
the top-level context, never on profile_user, so they serialised as null even
though UserOut declares them and the API docs document them as embedded there.
Gateway quota reset: a cap previously lifted only with the passage of time.
quota.reset upserts a watermark row into gateway_quota_resets, scoped by the
same three nullable dimensions as a quota rule, and spent_24h sums from
max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on
/admin/ai-usage stay intact. Reaches every surface: POST
/admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool
gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API
docs. Admin's Reset all quotas now stamps a global gateway watermark too,
which is what a caller stuck on "AI gateway daily quota exceeded" needed.
Startup: _backfill_gamification swept every xp=0 user on every boot in every
worker and could never converge, since a user with no content earns no XP.
It now intersects pending users with _milestone_candidates(). db.tables is a
live reflection, so it is hoisted out of the loops that probed it per row.
Docker: the dependency layer now depends on pyproject.toml only, so a source
edit no longer reinstalls every dependency and re-downloads Chromium.
Adds start_interval so the healthcheck probes during the start period, and a
docker-reload target, since docker-up does not restart an unchanged container.
Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz
docs and the tooling all referenced but which never existed: 288 keys across
28 categories, including the families built from a variable at the call site.
Test fixes: both devlog helpers dated post 0 as the newest while the tests
assumed post 2 was; a profile login posted username= to a form that takes
email=; a devlog assertion matched six buttons under strict mode; and the
primary-admin tests seeded founders newer than the back-dated fixture admin,
so they only passed without the api tier.
Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
|
|
|
docker-reload:
|
|
|
|
|
$(COMPOSE) restart app
|
|
|
|
|
$(COMPOSE) up -d --wait
|
Route container proxies through the leg that is actually reachable
The workspace editor hung for 60s and then 504'd. Three independent faults were
stacked behind that one symptom.
Reachability: editor_target delegated to proxy_target, which returns
CONTAINER_PROXY_HOST plus the published host port and never falls back to the
container. From inside the app container that address crosses docker0 into the
host INPUT chain, whose policy is DROP with an allow-list that does not include
the published port range, so the packet was dropped and the request hung rather
than being refused. Measured from the app container: container_ip:8443 answers
302, gateway:20006 is dropped. One shared reachable_target now prefers the
direct container leg and falls back to the published port, and editor_target
uses tunnel_target as services/containers/CLAUDE.md already required. The same
defect affected /p/{slug} ingress and every tunnel, since all three resolved
through proxy_target.
The recorded measurement that motivated the old order (container_ip times out,
gateway connects) no longer holds: make docker-attach puts the app on the
instances' bridge network, which is what makes the direct leg work.
Duplicate response headers: the forwarding core relayed the upstream Date and
Server alongside the ones the serving layer generates, so every proxied
response carried two of each. Both are singleton headers and duplicating them
is malformed HTTP.
Serialization: WorkspaceViewOut declared flag_reason and three sibling strings
as str, so a NULL column made the workspace page 500 for JSON clients.
Documents the two public hostnames and the devplace.net SSH tunnel, so a future
session does not conclude the site is down after pointing curl --resolve at an
address the hostname does not resolve to, and adds the layered procedure for
diagnosing a production failure.
Verified on production with Playwright over both hostnames: the code-server
login renders and the workbench loads. Suite: 3345 passed.
2026-08-11 20:03:15 +02:00
|
|
|
$(MAKE) docker-attach
|
Fix circular import, primary-admin NULL trap, and add gateway quota reset
Restores a working import graph and closes two data-correctness bugs, plus
adds a reset for the AI gateway's rolling 24h spend.
Circular import: database/__init__ -> engagement -> content -> utils ->
database made the package unimportable. get_project_devlog moves out of
database/engagement.py into content.py, where enrich_items already lives.
Primary administrator: _can_hold_primary_admin read is_active with
bool(row.get("is_active")), so an admin row whose is_active column is SQL
NULL (any row predating the column) was treated as deactivated and skipped.
Every other site defaults an unknown is_active to active; this one now does
too.
Profile JSON: xp_next_level and xp_progress_pct were computed but only put on
the top-level context, never on profile_user, so they serialised as null even
though UserOut declares them and the API docs document them as embedded there.
Gateway quota reset: a cap previously lifted only with the passage of time.
quota.reset upserts a watermark row into gateway_quota_resets, scoped by the
same three nullable dimensions as a quota rule, and spent_24h sums from
max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on
/admin/ai-usage stay intact. Reaches every surface: POST
/admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool
gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API
docs. Admin's Reset all quotas now stamps a global gateway watermark too,
which is what a caller stuck on "AI gateway daily quota exceeded" needed.
Startup: _backfill_gamification swept every xp=0 user on every boot in every
worker and could never converge, since a user with no content earns no XP.
It now intersects pending users with _milestone_candidates(). db.tables is a
live reflection, so it is hoisted out of the loops that probed it per row.
Docker: the dependency layer now depends on pyproject.toml only, so a source
edit no longer reinstalls every dependency and re-downloads Chromium.
Adds start_interval so the healthcheck probes during the start period, and a
docker-reload target, since docker-up does not restart an unchanged container.
Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz
docs and the tooling all referenced but which never existed: 288 keys across
28 categories, including the families built from a variable at the call site.
Test fixes: both devlog helpers dated post 0 as the newest while the tests
assumed post 2 was; a profile login posted username= to a form that takes
email=; a devlog assertion matched six buttons under strict mode; and the
primary-admin tests seeded founders newer than the back-dated fixture admin,
so they only passed without the api tier.
Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
|
|
|
|
2026-05-12 15:07:34 +02:00
|
|
|
docker-down:
|
2026-06-09 06:41:27 +02:00
|
|
|
$(COMPOSE) down
|
2026-05-12 15:07:34 +02:00
|
|
|
|
|
|
|
|
docker-logs:
|
2026-06-09 06:41:27 +02:00
|
|
|
$(COMPOSE) logs -f
|
2026-05-12 15:07:34 +02:00
|
|
|
|
|
|
|
|
docker-clean:
|
2026-06-09 06:41:27 +02:00
|
|
|
$(COMPOSE) down -v
|
2026-05-23 08:27:04 +02:00
|
|
|
|
2026-06-09 16:06:02 +02:00
|
|
|
docker-bup: docker-build docker-up
|
|
|
|
|
|
2026-05-23 08:27:04 +02:00
|
|
|
deploy:
|
|
|
|
|
git checkout production
|
|
|
|
|
git merge master
|
|
|
|
|
git push origin production
|
feat: add autonomous maintenance agent fleet with shared engine and validator
Add the `agents/` directory containing a fleet of autonomous AI maintenance agents for codebase consistency, including a shared async engine (`agent.py`), a dependency-free validator (`validator.py`), and specialized agents for security, audit, docs, style, frontend, SEO, test coverage, and more. Wire the fleet into the Makefile with `--fix`/`--check` modes, document the architecture in `AGENTS.md`, `CLAUDE.md`, and `README.md`, and enforce a hard rule that the `agents/` directory itself is off-limits to any code modification to preserve detection data.
2026-06-12 01:35:31 +02:00
|
|
|
|