# muxplex — developer targets
#
# `make test` runs the suite inside a Digital Twin Universe container rather
# than on your host. This is not optional hygiene: running pytest on a box that
# is also serving muxplex has destroyed a live settings.json and SIGTERMed the
# running server. See AGENTS.md -> "Testing & workflow".

DTU      ?= muxplex-test
PROFILE  ?= ../.amplifier/digital-twin-universe/profiles/muxplex-test.yaml
TARBALL  ?= ../.amplifier/digital-twin-universe/profiles/muxplex-src.tar.gz

.PHONY: test test-python test-frontend test-host check fmt check-container-drift

## Run BOTH suites inside the DTU (the safe, default path).
##
## THIS REPO HAS TWO SUITES. `make test` used to run only the Python one,
## and that gap shipped a red release candidate at v0.49.0: five commits
## whose largest surface was frontend JavaScript were verified against
## pytest alone, went green, and were pushed -- CI then failed with 31
## frontend failures (a stale test fixture and a set of tests still
## asserting a retired localStorage contract). Nothing was wrong with
## either suite. The gap was that only one of them was in anybody's loop,
## so the frontend suite was effectively opt-in and nobody opted in.
##
## Both now run here, and `test` depends on both, so the default local
## command covers the same ground CI does. Ordered frontend-first: it is
## ~15s against pytest's ~100s, so the fast suite reports before the slow
## one starts.
test: test-frontend test-python

## Frontend unit suite (node:test). Zero package dependencies, node:
## builtins only -- no install step, matching CI's own job. Runs in the DTU
## against the SAME git-archive snapshot as the Python suite, so both test
## the artifact you would push rather than your working tree.
test-frontend: dtu-sync
	@echo "==> frontend suite (node --test)"
	@amplifier-digital-twin exec $(DTU) -- bash -lc 'command -v node >/dev/null 2>&1 || { \
	  echo "node not found in the DTU -- the frontend suite CANNOT run."; \
	  echo "This is a FAILURE, not a skip: CI runs this suite and will fail"; \
	  echo "on what was never checked here. Install node in the DTU image."; \
	  exit 1; }; cd /opt/muxplex/muxplex/frontend && node --test tests/*.mjs'

## Python suite.
test-python: dtu-sync
	@echo "==> python suite (pytest)"
	@amplifier-digital-twin exec $(DTU) -- bash -lc 'cd /opt/muxplex && .venv/bin/pytest -q'

## Push HEAD into the DTU. Factored out so `make test` syncs ONCE and both
## suites run against the identical snapshot -- two separate syncs could
## otherwise test two different trees and report a green that never
## existed as one commit.
.PHONY: dtu-sync
dtu-sync:
	@command -v amplifier-digital-twin >/dev/null 2>&1 || { \
	  echo "amplifier-digital-twin not found."; \
	  echo "Install: uv tool install git+https://github.com/microsoft/amplifier-bundle-digital-twin-universe"; \
	  exit 1; }
	@git diff --quiet || echo "NOTE: uncommitted changes — commit first so the DTU tests what you'd push (AGENTS.md)."
	@echo "==> snapshotting HEAD"
	@git archive --format=tar.gz --prefix=muxplex/ -o "$(TARBALL)" HEAD
	@amplifier-digital-twin status $(DTU) >/dev/null 2>&1 || amplifier-digital-twin launch "$(PROFILE)" --name $(DTU)
	@amplifier-digital-twin file-push $(DTU) "$(TARBALL)" /root/muxplex-src.tar.gz >/dev/null
	@amplifier-digital-twin update $(DTU) >/dev/null

## Escape hatch: run on this host. Safe alongside a live muxplex by isolation.
##
## This used to claim "Refuses if a live muxplex is serving." It does not, and
## has not since the host-network probe was retired (see conftest.py's "RETIRED
## FIX"): the surviving pytest_sessionstart is an AST scan of test SOURCE and
## never looks at the host at all. Verified 2026-09-05 -- a full run completed
## repeatedly on a host serving a live muxplex, with no refusal. A guard people
## believe in that cannot fire is worse than no guard, so the claim is gone.
## What actually protects the host is conftest.py's autouse isolation of
## settings.json / pruning.json / state.json / sessions.json, the tmux socket
## dir, the port killer, and uvicorn.run.
test-host:
	@echo "Running on the HOST. Host files are protected by conftest.py's autouse isolation rails, not by a refusal."
	uv run pytest

## Verify the tree. NOTHING in here rewrites your files -- that is `fmt`.
##
## This target used to be `check: fmt`, and `fmt` runs the REWRITING form of
## `ruff format`. So `make check` silently reformatted whatever had drifted on
## the way past and then reported success -- a gate that repairs the thing it
## is meant to be checking is not a gate. Two files (auth.py, carrying an
## explicitly "[not final]" WIP commit, and a test file from a sibling lane)
## sat unformatted on main with every local run reporting green; the drift was
## only ever visible as unrelated dirty files in somebody's working tree, and
## the person who noticed had to be looking. Filed and fixed as muxplex-sxi.
##
## `ruff format --check` verifies and FAILS on drift. `fmt` remains the
## separate, explicit "rewrite my files" verb. CI runs this same checking form
## (.github/workflows/ci.yml -> the `lint` job), so a local green and a CI
## green now mean the same thing -- before this, CI ran no formatting check at
## all, so there was nothing to be inconsistent with.
check:
	@$(MAKE) --no-print-directory check-container-drift
	uv run ruff format --check muxplex/
	uv run ruff check muxplex/
	uv run pyright muxplex/

## Fail if the browser-verification container has drifted from this checkout.
##
## Browser proof is this project's reality gate, and it only means anything if
## the tree being clicked IS the tree being committed. That invariant decayed
## silently for 54 commits once (muxplex-cxd -> muxplex-cky) and was caught only
## because a person happened to look. This is the machine that looks instead.
##
## exit 1 (DRIFT) fails the build. exit 2 (could not verify -- no twin CLI, no
## container) is reported on screen but not fatal: a contributor without the LAN
## twin has no container to be stale. It is never silently treated as a pass.
check-container-drift:
	@./scripts/check_container_drift.py; rc=$$?; \
	  if [ $$rc -eq 1 ]; then exit 1; fi; \
	  if [ $$rc -eq 2 ]; then \
	    echo "[container-drift] NOT FATAL for 'make check' -- but this was NOT a pass."; \
	  fi; \
	  exit 0

## Rewrite files in place. This is the ONLY target that edits your tree, and
## it is deliberately not a prerequisite of `check` -- see `check`'s comment.
fmt:
	uv run ruff format muxplex/
