.PHONY: check schema-check compile-api test-api remote-dev-sync db-upgrade db-check campaign-client-policy-e2e assignment-client-binding-concurrency browser-session-assignment-concurrency web-transport-browser-acceptance opendaw-browser-acceptance opendaw-acceptance-candidate opendaw-acceptance-candidate-verify opendaw-acceptance-candidate-handoff-seal opendaw-acceptance-candidate-handoff-verify opendaw-acceptance-candidate-transfer-pack opendaw-acceptance-candidate-transfer-receive opendaw-local-acceptance-env opendaw-local-acceptance-config opendaw-local-acceptance-build-api opendaw-local-acceptance-up opendaw-local-acceptance-seed opendaw-local-acceptance-browser-handoff opendaw-local-acceptance-packet opendaw-local-acceptance-down opendaw-macos-preflight opendaw-operator-acceptance-prepare opendaw-operator-acceptance-finalize opendaw-operator-acceptance-verify release-image-publication-recover production-web-release-switch production-web-service-recreate structure-opendaw-browser-acceptance structure-opendaw-transport-browser-acceptance reaper-release reaper-internal-portable recover-reaper-044-release prepare-beat-web-acceptance-handoff complete-beat-web-acceptance-handoff windows-beat-acceptance-packet windows-beat-acceptance-handoff-status ten-annotator-resilience review-claim-concurrency review-launch-check review-cohort-plan review-cohort-launch-evidence review-cohort-launch-verify batch-assignment-scale batch-assignment-concurrency research-export-http dependency-lock-check sbom
.PHONY: agent-doctor verify-fast verify-full quality-budget lint-python reaper-version-check test-reaper test-operations web-lint web-test web-browser-smoke

PYTHON ?= .venv/bin/python

check: verify-fast

schema-check:
	$(PYTHON) scripts/validate_schemas.py

compile-api:
	$(PYTHON) -m compileall -q apps/api/src

test-api:
	$(PYTHON) -m pytest apps/api/tests

lint-python:
	$(PYTHON) -m ruff check apps/api/src apps/api/tests apps/reaper-client/scripts apps/reaper-client/tests scripts

reaper-version-check:
	$(PYTHON) apps/reaper-client/scripts/release_package.py check-source-version

test-reaper:
	$(PYTHON) -m pytest apps/reaper-client/tests -q

test-operations:
	$(PYTHON) -m pytest scripts/tests -q

web-lint:
	cd apps/web && pnpm lint

web-test:
	cd apps/web && pnpm test:unit

web-browser-smoke:
	cd apps/web && pnpm test:browser-smoke

quality-budget:
	$(PYTHON) scripts/agent/quality_gate.py

agent-doctor:
	$(PYTHON) scripts/agent/verify.py doctor

verify-fast:
	$(PYTHON) scripts/agent/verify.py fast $(if $(VERIFY_BASE),--base "$(VERIFY_BASE)") $(if $(VERIFY_RECEIPT),--receipt "$(VERIFY_RECEIPT)") $(if $(REUSE_RECEIPT),--reuse-receipt "$(REUSE_RECEIPT)")

verify-full:
	$(PYTHON) scripts/agent/verify.py full $(if $(VERIFY_RECEIPT),--receipt "$(VERIFY_RECEIPT)") $(if $(REUSE_RECEIPT),--reuse-receipt "$(REUSE_RECEIPT)")

remote-dev-sync:
	.venv/bin/python scripts/operations/sync_remote_development.py $(if $(BRANCH),--branch "$(BRANCH)") $(if $(REMOTE_TEST),--test-command "$(REMOTE_TEST)") $(if $(REMOTE_WORKTREE),--remote-worktree "$(REMOTE_WORKTREE)")

db-upgrade:
	cd apps/api && ../../.venv/bin/alembic upgrade head

db-check:
	cd apps/api && ../../.venv/bin/alembic check

campaign-client-policy-e2e:
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/verify_campaign_client_policy_e2e.py

assignment-client-binding-concurrency:
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/verify_assignment_client_binding_concurrency.py

browser-session-assignment-concurrency:
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/verify_browser_session_assignment_concurrency.py

web-transport-browser-acceptance:
	cd apps/web && pnpm test:browser-transport

opendaw-browser-acceptance:
	cd apps/web && pnpm test:browser-opendaw

opendaw-acceptance-candidate:
	@test -n "$(SOURCE_CODE_URL)" || (echo "SOURCE_CODE_URL=https://git.internal.example/source is required" >&2; exit 2)
	@test "$(APPLY)" != "1" -o -n "$(CONFIRM)" || (echo "CONFIRM=sha256:... is required with APPLY=1" >&2; exit 2)
	@test "$(APPLY)" != "1" -o -n "$(OUTPUT)" || (echo "OUTPUT=/private/release/opendaw-candidate is required with APPLY=1" >&2; exit 2)
	.venv/bin/python scripts/operations/build_opendaw_acceptance_candidate.py --source-code-url "$(SOURCE_CODE_URL)" $(if $(IMAGE_REPOSITORY),--image-repository "$(IMAGE_REPOSITORY)") $(if $(filter 1,$(APPLY)),--apply --confirm "$(CONFIRM)" --output-root "$(OUTPUT)")

opendaw-acceptance-candidate-verify:
	@test -n "$(RECEIPT)" || (echo "RECEIPT=/private/release/opendaw-candidate/candidate.json is required" >&2; exit 2)
	@test -n "$(CONTAINER)" || (echo "CONTAINER=mirannot-opendaw-acceptance-web-1 is required" >&2; exit 2)
	@.venv/bin/python scripts/operations/verify_opendaw_acceptance_candidate.py --receipt "$(RECEIPT)" --container "$(CONTAINER)" $(if $(REPOSITORY),--repository "$(REPOSITORY)")

opendaw-acceptance-candidate-handoff-seal:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	.venv/bin/python scripts/operations/seal_opendaw_candidate_handoff.py --candidate "$(CANDIDATE)"

opendaw-acceptance-candidate-handoff-verify:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	.venv/bin/python scripts/operations/verify_opendaw_candidate_handoff.py --candidate "$(CANDIDATE)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)"

opendaw-acceptance-candidate-transfer-pack:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/transfer/mirannot-opendaw-candidate-COMMIT.tar.gz is required" >&2; exit 2)
	.venv/bin/python scripts/operations/transfer_opendaw_candidate_handoff.py pack --candidate "$(CANDIDATE)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)" --output "$(OUTPUT)"

opendaw-acceptance-candidate-transfer-receive:
	@test -n "$(ARCHIVE)" || (echo "ARCHIVE=/private/transfer/mirannot-opendaw-candidate-COMMIT.tar.gz is required" >&2; exit 2)
	@test -n "$(TRANSFER_ARCHIVE_SHA256)" || (echo "TRANSFER_ARCHIVE_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/release/opendaw-candidate is required" >&2; exit 2)
	.venv/bin/python scripts/operations/transfer_opendaw_candidate_handoff.py receive --archive "$(ARCHIVE)" --expected-transfer-archive-sha256 "$(TRANSFER_ARCHIVE_SHA256)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)" --output "$(OUTPUT)"

opendaw-local-acceptance-env:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=\$$HOME/.mirannot/opendaw-acceptance/.env.local is required" >&2; exit 2)
	python3 scripts/operations/prepare_opendaw_local_acceptance_env.py --candidate "$(CANDIDATE)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)" --output "$(OUTPUT)"

opendaw-local-acceptance-config:
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=\$$HOME/.mirannot/opendaw-acceptance/.env.local is required" >&2; exit 2)
	docker compose --project-name mirannot-opendaw-acceptance --env-file "$(ENV_FILE)" --file deploy/docker-compose.yml --file deploy/docker-compose.opendaw-acceptance.yml --profile tls config --quiet
	docker compose --project-name mirannot-opendaw-acceptance --env-file "$(ENV_FILE)" --file deploy/docker-compose.yml --file deploy/docker-compose.opendaw-acceptance.yml --profile tls config --services
	docker compose --project-name mirannot-opendaw-acceptance --env-file "$(ENV_FILE)" --file deploy/docker-compose.yml --file deploy/docker-compose.opendaw-acceptance.yml --profile tls config --images

opendaw-local-acceptance-build-api:
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=\$$HOME/.mirannot/opendaw-acceptance/.env.local is required" >&2; exit 2)
	@test -n "$(SOURCE_REVISION)" || (echo "SOURCE_REVISION=<40-character candidate commit> is required" >&2; exit 2)
	@git cat-file -e "$(SOURCE_REVISION)^{commit}" || (echo "candidate source revision is unavailable" >&2; exit 2)
	@git merge-base --is-ancestor "$(SOURCE_REVISION)" HEAD || (echo "candidate source revision is not an ancestor of the tooling checkout" >&2; exit 2)
	@git diff --quiet "$(SOURCE_REVISION)..HEAD" -- apps/api/src apps/api/migrations apps/api/Dockerfile apps/api/alembic.ini apps/api/pyproject.toml apps/api/requirements-api.lock apps/api/requirements-build.lock apps/api/requirements-worker.lock packages/annotation-schemas LICENSE LICENSES THIRD_PARTY_NOTICES.md || (echo "API runtime, dependency, schema, or license content differs from the candidate revision; rebuild a new candidate instead" >&2; exit 2)
	@git diff --quiet && git diff --cached --quiet && test -z "$$(git status --porcelain --untracked-files=all)" || (echo "tooling checkout must be clean before building isolated API images" >&2; exit 2)
	docker compose --project-name mirannot-opendaw-acceptance --env-file "$(ENV_FILE)" --file deploy/docker-compose.yml --file deploy/docker-compose.opendaw-acceptance.yml build api worker

opendaw-local-acceptance-up:
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=\$$HOME/.mirannot/opendaw-acceptance/.env.local is required" >&2; exit 2)
	docker compose --project-name mirannot-opendaw-acceptance --env-file "$(ENV_FILE)" --file deploy/docker-compose.yml --file deploy/docker-compose.opendaw-acceptance.yml --profile tls up -d --no-build --wait postgres redis migrate api worker web caddy

opendaw-local-acceptance-seed:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=\$$HOME/.mirannot/opendaw-acceptance/.env.local is required" >&2; exit 2)
	@test -n "$(SOURCE_AUDIO)" || (echo "SOURCE_AUDIO=/private/fixtures/approved.wav is required" >&2; exit 2)
	@test -n "$(SOURCE_AUDIO_TITLE)" || (echo "SOURCE_AUDIO_TITLE='Approved fixture' is required" >&2; exit 2)
	@test -n "$(APPROVAL_LABEL)" || (echo "APPROVAL_LABEL='Owner approval reference' is required" >&2; exit 2)
	@test -n "$(OPERATOR_ID)" || (echo "OPERATOR_ID=owner.identity is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=\$$HOME/.mirannot/opendaw-acceptance/seed-receipt.json is required" >&2; exit 2)
	python3 scripts/operations/seed_opendaw_local_acceptance.py --candidate "$(CANDIDATE)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)" --env-file "$(ENV_FILE)" --audio "$(SOURCE_AUDIO)" --title "$(SOURCE_AUDIO_TITLE)" $(if $(SOURCE_AUDIO_ARTIST),--artist "$(SOURCE_AUDIO_ARTIST)") $(if $(SOURCE_AUDIO_ALBUM),--album "$(SOURCE_AUDIO_ALBUM)") --approval-label "$(APPROVAL_LABEL)" --operator-id "$(OPERATOR_ID)" --output "$(OUTPUT)"

opendaw-local-acceptance-browser-handoff:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=\$$HOME/.mirannot/opendaw-acceptance/.env.local is required" >&2; exit 2)
	@test -n "$(SEED_RECEIPT)" || (echo "SEED_RECEIPT=/private/seed-receipt.json is required" >&2; exit 2)
	python3 scripts/operations/handoff_opendaw_local_acceptance_browser.py --candidate "$(CANDIDATE)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)" --env-file "$(ENV_FILE)" --seed-receipt "$(SEED_RECEIPT)"

opendaw-local-acceptance-packet:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(SEED_RECEIPT)" || (echo "SEED_RECEIPT=/private/seed-receipt.json is required" >&2; exit 2)
	@test -n "$(SOURCE_AUDIO)" || (echo "SOURCE_AUDIO=/private/fixtures/approved.wav is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/opendaw-acceptance-packet is required" >&2; exit 2)
	python3 scripts/operations/prepare_opendaw_local_acceptance_packet.py --candidate "$(CANDIDATE)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)" --seed-receipt "$(SEED_RECEIPT)" --source-audio "$(SOURCE_AUDIO)" --output "$(OUTPUT)"

opendaw-local-acceptance-down:
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=\$$HOME/.mirannot/opendaw-acceptance/.env.local is required" >&2; exit 2)
	@test "$(CONFIRM)" = "DELETE-mirannot-opendaw-acceptance" || (echo "CONFIRM=DELETE-mirannot-opendaw-acceptance is required; this removes only the isolated project's containers and volumes" >&2; exit 2)
	docker compose --project-name mirannot-opendaw-acceptance --env-file "$(ENV_FILE)" --file deploy/docker-compose.yml --file deploy/docker-compose.opendaw-acceptance.yml --profile tls down --volumes --remove-orphans

opendaw-macos-preflight:
	@test -n "$(CANDIDATE)" || (echo "CANDIDATE=/private/release/opendaw-candidate is required" >&2; exit 2)
	@test -n "$(HANDOFF_MANIFEST_SHA256)" || (echo "HANDOFF_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(WEB_ORIGIN)" || (echo "WEB_ORIGIN=https://acceptance.example is required" >&2; exit 2)
	@test -n "$(SOURCE_CODE_URL)" || (echo "SOURCE_CODE_URL=https://.../mirannot-system-COMMIT.tar.gz is required" >&2; exit 2)
	@test -n "$(SOURCE_REVISION)" || (echo "SOURCE_REVISION=<40-character-Git-commit> is required" >&2; exit 2)
	@test -n "$(HOSTING_MODE)" || (echo "HOSTING_MODE=remote or local is required" >&2; exit 2)
	@test "$(HOSTING_MODE)" != "local" -o -n "$(CONTAINER)" || (echo "CONTAINER=<running-candidate-container> is required with HOSTING_MODE=local" >&2; exit 2)
	@python3 scripts/operations/check_opendaw_macos_preflight.py --repository "$(or $(REPOSITORY),$(CURDIR))" --candidate "$(CANDIDATE)" --expected-handoff-manifest-sha256 "$(HANDOFF_MANIFEST_SHA256)" --web-origin "$(WEB_ORIGIN)" --source-code-url "$(SOURCE_CODE_URL)" --source-revision "$(SOURCE_REVISION)" --hosting-mode "$(HOSTING_MODE)" $(if $(CONTAINER),--container "$(CONTAINER)")

opendaw-operator-acceptance-prepare:
	@test -n "$(WEB_ORIGIN)" || (echo "WEB_ORIGIN=https://deployed.internal.example is required" >&2; exit 2)
	@test -n "$(SOURCE_CODE_URL)" || (echo "SOURCE_CODE_URL=https://git.internal.example/source is required" >&2; exit 2)
	@test -n "$(SOURCE_REVISION)" || (echo "SOURCE_REVISION=<40-character-Git-commit> is required" >&2; exit 2)
	@test -n "$(SOURCE_AUDIO)" || (echo "SOURCE_AUDIO=/private/path/approved.wav is required" >&2; exit 2)
	@test -n "$(SOURCE_AUDIO_MEDIA_TYPE)" || (echo "SOURCE_AUDIO_MEDIA_TYPE=audio/wav is required" >&2; exit 2)
	@test -n "$(SOURCE_AUDIO_TITLE)" || (echo "SOURCE_AUDIO_TITLE='Approved fixture' is required" >&2; exit 2)
	@test -n "$(ASSIGNMENT_ID)" || (echo "ASSIGNMENT_ID=<uuid> is required" >&2; exit 2)
	@test -n "$(ASSIGNMENT_ATTEMPT_NUMBER)" || (echo "ASSIGNMENT_ATTEMPT_NUMBER=1 is required" >&2; exit 2)
	@test -n "$(TRACK_ID)" || (echo "TRACK_ID=<uuid> is required" >&2; exit 2)
	@test -n "$(TASK_KEY)" || (echo "TASK_KEY=structure is required" >&2; exit 2)
	@test -n "$(TASK_VERSION)" || (echo "TASK_VERSION=1 is required" >&2; exit 2)
	@test -n "$(REQUESTED_REVISION_ID)" || (echo "REQUESTED_REVISION_ID=<uuid> is required" >&2; exit 2)
	@test -n "$(REQUESTED_REVISION_PAYLOAD_SHA256)" || (echo "REQUESTED_REVISION_PAYLOAD_SHA256=sha256:<hex> is required" >&2; exit 2)
	@test -n "$(SOURCE_ASSET_ID)" || (echo "SOURCE_ASSET_ID=<uuid> is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/release/opendaw-acceptance-packet is required" >&2; exit 2)
	.venv/bin/python scripts/operations/prepare_opendaw_operator_acceptance.py --web-origin "$(WEB_ORIGIN)" --source-code-url "$(SOURCE_CODE_URL)" --source-code-revision "$(SOURCE_REVISION)" --source-audio "$(SOURCE_AUDIO)" --source-audio-media-type "$(SOURCE_AUDIO_MEDIA_TYPE)" --source-audio-title "$(SOURCE_AUDIO_TITLE)" --assignment-id "$(ASSIGNMENT_ID)" --assignment-attempt-number "$(ASSIGNMENT_ATTEMPT_NUMBER)" --track-id "$(TRACK_ID)" --task-key "$(TASK_KEY)" --task-version "$(TASK_VERSION)" --requested-revision-id "$(REQUESTED_REVISION_ID)" --requested-revision-payload-sha256 "$(REQUESTED_REVISION_PAYLOAD_SHA256)" --source-asset-id "$(SOURCE_ASSET_ID)" --output "$(OUTPUT)"

opendaw-operator-acceptance-finalize:
	@test -n "$(PACKET)" || (echo "PACKET=/private/path/opendaw-acceptance-packet is required" >&2; exit 2)
	@test -n "$(PACKET_MANIFEST_SHA256)" || (echo "PACKET_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(CHROME_RUN)" || (echo "CHROME_RUN=/private/path/chrome-run.draft.json is required" >&2; exit 2)
	@test -n "$(SAFARI_RUN)" || (echo "SAFARI_RUN=/private/path/safari-run.draft.json is required" >&2; exit 2)
	@test -n "$(REVIEWED_BY)" || (echo "REVIEWED_BY=release.owner is required" >&2; exit 2)
	@test -n "$(REVIEWED_AT)" || (echo "REVIEWED_AT=<timezone-bearing-ISO-8601> is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/path/opendaw-operator-acceptance.json is required" >&2; exit 2)
	.venv/bin/python scripts/operations/finalize_opendaw_operator_acceptance.py --packet "$(PACKET)" --expected-packet-manifest-sha256 "$(PACKET_MANIFEST_SHA256)" --chrome-run "$(CHROME_RUN)" --safari-run "$(SAFARI_RUN)" --reviewed-by "$(REVIEWED_BY)" --reviewed-at "$(REVIEWED_AT)" $(if $(REVIEW_NOTES),--review-notes "$(REVIEW_NOTES)") --output "$(OUTPUT)"

opendaw-operator-acceptance-verify:
	@test -n "$(REPORT)" || (echo "REPORT=/private/path/opendaw-operator-acceptance.json is required" >&2; exit 2)
	@test -n "$(SOURCE_REVISION)" || (echo "SOURCE_REVISION=<40-character-Git-commit> is required" >&2; exit 2)
	@test -n "$(WASM_MANIFEST_SHA256)" || (echo "WASM_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(LEGAL_MANIFEST_SHA256)" || (echo "LEGAL_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(SOURCE_MANIFEST_SHA256)" || (echo "SOURCE_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(SOURCE_ARCHIVE_SHA256)" || (echo "SOURCE_ARCHIVE_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(SOURCE_AUDIO_SHA256)" || (echo "SOURCE_AUDIO_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(ANNOTATION_TARGET_SHA256)" || (echo "ANNOTATION_TARGET_SHA256=sha256:... is required" >&2; exit 2)
	.venv/bin/python scripts/operations/verify_opendaw_operator_acceptance.py --report "$(REPORT)" --expected-source-revision "$(SOURCE_REVISION)" --expected-wasm-manifest-sha256 "$(WASM_MANIFEST_SHA256)" --expected-legal-manifest-sha256 "$(LEGAL_MANIFEST_SHA256)" --expected-source-manifest-sha256 "$(SOURCE_MANIFEST_SHA256)" --expected-source-archive-sha256 "$(SOURCE_ARCHIVE_SHA256)" --expected-source-audio-sha256 "$(SOURCE_AUDIO_SHA256)" --expected-annotation-target-sha256 "$(ANNOTATION_TARGET_SHA256)"

release-image-publication-recover:
	@test -n "$(PARTIAL_PUBLICATION)" || (echo "PARTIAL_PUBLICATION=/private/release/images/publication.json is required" >&2; exit 2)
	@test -n "$(REPORT)" || (echo "REPORT=/private/release/opendaw-operator-acceptance.json is required" >&2; exit 2)
	@test -n "$(REPOSITORY)" || (echo "REPOSITORY=/clean/accepted/source-worktree is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/release/images/publication-recovery.json is required" >&2; exit 2)
	.venv/bin/python scripts/operations/recover_release_image_publication.py --partial-publication "$(PARTIAL_PUBLICATION)" --acceptance-report "$(REPORT)" --repository "$(REPOSITORY)" --output "$(OUTPUT)"

production-web-release-switch:
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=/private/.env.production is required" >&2; exit 2)
	@test -n "$(WEB_IMAGE)" || (echo "WEB_IMAGE=registry.example/private/mirannot/web@sha256:... is required" >&2; exit 2)
	@test -n "$(SOURCE_CODE_URL)" || (echo "SOURCE_CODE_URL=https://.../mirannot-system-COMMIT.tar.gz is required" >&2; exit 2)
	@test -n "$(SOURCE_REVISION)" || (echo "SOURCE_REVISION=<40-character-Git-commit> is required" >&2; exit 2)
	@test -n "$(CURRENT_WEB_IMAGE)" || (echo "CURRENT_WEB_IMAGE=<local-or-registry-web-repository>@sha256:... is required" >&2; exit 2)
	@test -n "$(CURRENT_SOURCE_CODE_URL)" || (echo "CURRENT_SOURCE_CODE_URL=https://.../mirannot-system-COMMIT.tar.gz is required" >&2; exit 2)
	@test -n "$(CURRENT_SOURCE_REVISION)" || (echo "CURRENT_SOURCE_REVISION=<40-character-Git-commit> is required" >&2; exit 2)
	@test -n "$(EVIDENCE_ROOT)" || (echo "EVIDENCE_ROOT=/private/release-evidence/RELEASE is required" >&2; exit 2)
	@test "$(or $(CURRENT_OPENDAW_PROFILE),disabled)" != "structure-transport" -o -n "$(CURRENT_OPENDAW_OPERATOR_ACCEPTANCE_SHA256)" || (echo "CURRENT_OPENDAW_OPERATOR_ACCEPTANCE_SHA256=sha256:... is required with CURRENT_OPENDAW_PROFILE=structure-transport" >&2; exit 2)
	@test "$(or $(OPENDAW_PROFILE),disabled)" != "structure-transport" -o -n "$(OPENDAW_OPERATOR_ACCEPTANCE_SHA256)" || (echo "OPENDAW_OPERATOR_ACCEPTANCE_SHA256=sha256:... is required with OPENDAW_PROFILE=structure-transport" >&2; exit 2)
	@test "$(APPLY)" != "1" -o -n "$(CONFIRM)" || (echo "CONFIRM=sha256:... is required with APPLY=1" >&2; exit 2)
	.venv/bin/python scripts/operations/update_production_web_release.py --env-file "$(ENV_FILE)" --web-image "$(WEB_IMAGE)" --source-code-url "$(SOURCE_CODE_URL)" --source-revision "$(SOURCE_REVISION)" --expected-current-web-image "$(CURRENT_WEB_IMAGE)" --expected-current-source-url "$(CURRENT_SOURCE_CODE_URL)" --expected-current-source-revision "$(CURRENT_SOURCE_REVISION)" --evidence-root "$(EVIDENCE_ROOT)" --expected-current-opendaw-profile "$(or $(CURRENT_OPENDAW_PROFILE),disabled)" $(if $(CURRENT_OPENDAW_OPERATOR_ACCEPTANCE_SHA256),--expected-current-opendaw-operator-acceptance-sha256 "$(CURRENT_OPENDAW_OPERATOR_ACCEPTANCE_SHA256)") --opendaw-profile "$(or $(OPENDAW_PROFILE),disabled)" $(if $(OPENDAW_OPERATOR_ACCEPTANCE_SHA256),--opendaw-operator-acceptance-sha256 "$(OPENDAW_OPERATOR_ACCEPTANCE_SHA256)") $(if $(filter 1,$(APPLY)),--apply --confirm "$(CONFIRM)")

production-web-service-recreate:
	@test -n "$(ENV_FILE)" || (echo "ENV_FILE=/private/.env.production is required" >&2; exit 2)
	@test -n "$(COMPOSE_FILE)" || (echo "COMPOSE_FILE=/reviewed/deploy/docker-compose.yml is required" >&2; exit 2)
	@test -n "$(ENV_SHA256)" || (echo "ENV_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(CURRENT_WEB_IMAGE)" || (echo "CURRENT_WEB_IMAGE=repository@sha256:... is required" >&2; exit 2)
	@test -n "$(WEB_IMAGE)" || (echo "WEB_IMAGE=registry.example/private/mirannot/web@sha256:... is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/release/web-container-switch.json is required" >&2; exit 2)
	@test "$(APPLY)" != "1" -o -n "$(CONFIRM)" || (echo "CONFIRM=sha256:... is required with APPLY=1" >&2; exit 2)
	.venv/bin/python scripts/operations/recreate_production_web_service.py --project "$(or $(PROJECT),mirannot)" --env-file "$(ENV_FILE)" --compose-file "$(COMPOSE_FILE)" --expected-environment-sha256 "$(ENV_SHA256)" --expected-current-web-image "$(CURRENT_WEB_IMAGE)" --target-web-image "$(WEB_IMAGE)" --evidence-file "$(OUTPUT)" --expected-current-opendaw-profile "$(or $(CURRENT_OPENDAW_PROFILE),disabled)" --target-opendaw-profile "$(or $(OPENDAW_PROFILE),structure-transport)" $(if $(filter 1,$(APPLY)),--apply --confirm "$(CONFIRM)")

structure-opendaw-browser-acceptance:
	cd apps/web && pnpm test:browser-structure-opendaw

structure-opendaw-transport-browser-acceptance:
	cd apps/web && pnpm test:browser-structure-opendaw-transport

reaper-release:
	.venv/bin/python apps/reaper-client/scripts/release_package.py build

reaper-internal-portable:
	@test -n "$(REAPER_TEMPLATE)" || (echo "REAPER_TEMPLATE=/private/templates/reaper-win64-portable is required" >&2; exit 2)
	@test -n "$(CLIENT_RELEASE)" || (echo "CLIENT_RELEASE=dist/reaper-client/mirannot-reaper-client-VERSION.zip is required" >&2; exit 2)
	@test -n "$(ENROLLMENT_FILE)" || (echo "ENROLLMENT_FILE=/private/enrollment-issued.json is required" >&2; exit 2)
	@test -n "$(API_ORIGIN)" || (echo "API_ORIGIN=https://mirannot.example.org is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/internal-reaper-downloads is required" >&2; exit 2)
	.venv/bin/python apps/reaper-client/scripts/build_internal_portable.py --reaper-template "$(REAPER_TEMPLATE)" --client-release "$(CLIENT_RELEASE)" --enrollment-file "$(ENROLLMENT_FILE)" --api-url "$(API_ORIGIN)" --output-dir "$(OUTPUT)" $(if $(filter 1,$(FORCE)),--force)

recover-reaper-044-release:
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/path/reaper-client-0.4.4-accepted is required" >&2; exit 2)
	.venv/bin/python apps/reaper-client/scripts/recover_accepted_release.py --output "$(OUTPUT)"

prepare-beat-web-acceptance-handoff:
	@test -n "$(API_ORIGIN)" || (echo "API_ORIGIN=https://annotate.example.org is required" >&2; exit 2)
	@test -n "$(CREDENTIALS)" || (echo "CREDENTIALS=/private/path/operator.env is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/path/beat-web-acceptance-handoff is required" >&2; exit 2)
	.venv/bin/python scripts/operations/prepare_beat_web_acceptance_handoff.py --api-origin "$(API_ORIGIN)" --credentials "$(CREDENTIALS)" --output "$(OUTPUT)" $(if $(LABEL),--label "$(LABEL)")

complete-beat-web-acceptance-handoff:
	@test -n "$(API_ORIGIN)" || (echo "API_ORIGIN=https://annotate.example.org is required" >&2; exit 2)
	@test -n "$(CREDENTIALS)" || (echo "CREDENTIALS=/private/path/operator.env is required" >&2; exit 2)
	@test -n "$(HANDOFF)" || (echo "HANDOFF=/private/path/handoff.json is required" >&2; exit 2)
	@test -n "$(PACKET)" || (echo "PACKET=/private/path/completed/windows-beat-acceptance-packet is required" >&2; exit 2)
	@test -n "$(CAMPAIGN_ID)" || (echo "CAMPAIGN_ID=exact-confirmed-uuid is required" >&2; exit 2)
	.venv/bin/python scripts/operations/complete_beat_web_acceptance_handoff.py --api-origin "$(API_ORIGIN)" --credentials "$(CREDENTIALS)" --handoff "$(HANDOFF)" --packet "$(PACKET)" --confirm-campaign-id "$(CAMPAIGN_ID)" $(if $(RESULT_OUTPUT),--result-output "$(RESULT_OUTPUT)")

windows-beat-acceptance-packet:
	@test -n "$(RELEASE)" || (echo "RELEASE=/private/path/mirannot-reaper-client-0.4.4.zip is required" >&2; exit 2)
	@test -n "$(MANIFEST)" || (echo "MANIFEST=/private/path/beat-web-only-CAMPAIGN.manifest.json is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/path/windows-beat-acceptance-packet is required" >&2; exit 2)
	.venv/bin/python apps/api/scripts/prepare_windows_beat_acceptance_packet.py build --release "$(RELEASE)" --release-record "$(or $(RELEASE_RECORD),releases/reaper-client/0.4.4.json)" --manifest "$(MANIFEST)" --output "$(OUTPUT)"

windows-beat-acceptance-handoff-status:
	@test -n "$(PACKET)" || (echo "PACKET=/private/path/windows-beat-acceptance-packet is required" >&2; exit 2)
	@test -n "$(PACKET_MANIFEST_SHA256)" || (echo "PACKET_MANIFEST_SHA256=sha256:... is required" >&2; exit 2)
	@test -z "$(BINDING)" -o -n "$(BINDING_SHA256)" || (echo "BINDING_SHA256=sha256:... is required with BINDING" >&2; exit 2)
	@test -z "$(BINDING_SHA256)" -o -n "$(BINDING)" || (echo "BINDING=/private/path/registration-binding.json is required with BINDING_SHA256" >&2; exit 2)
	.venv/bin/python scripts/operations/inspect_windows_beat_acceptance_handoff.py --packet "$(PACKET)" --expected-packet-manifest-sha256 "$(PACKET_MANIFEST_SHA256)" $(if $(BINDING),--binding "$(BINDING)" --expected-binding-sha256 "$(BINDING_SHA256)")

ten-annotator-resilience:
	.venv/bin/python apps/api/scripts/run_ten_annotator_resilience.py

review-claim-concurrency:
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/verify_review_claim_concurrency.py

review-launch-check:
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/check_legacy_review_launch.py

review-cohort-plan:
	@test -n "$(ROSTER)" || (echo "ROSTER=/private/path/roster.json is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/path/review-cohort-plan.json is required" >&2; exit 2)
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/plan_review_cohort.py --roster "$(ROSTER)" --output "$(OUTPUT)"

review-cohort-launch-evidence:
	@test -n "$(PLAN)" || (echo "PLAN=/private/path/review-cohort-plan.json is required" >&2; exit 2)
	@test -n "$(PLAN_SHA256)" || (echo "PLAN_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(OUTPUT)" || (echo "OUTPUT=/private/path/review-cohort-launch.json is required" >&2; exit 2)
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/check_review_cohort_launch.py build --plan "$(PLAN)" --expected-plan-sha256 "$(PLAN_SHA256)" --output "$(OUTPUT)"

review-cohort-launch-verify:
	@test -n "$(PLAN)" || (echo "PLAN=/private/path/review-cohort-plan.json is required" >&2; exit 2)
	@test -n "$(PLAN_SHA256)" || (echo "PLAN_SHA256=sha256:... is required" >&2; exit 2)
	@test -n "$(EVIDENCE)" || (echo "EVIDENCE=/private/path/review-cohort-launch.json is required" >&2; exit 2)
	@test -n "$(EVIDENCE_SHA256)" || (echo "EVIDENCE_SHA256=sha256:... is required" >&2; exit 2)
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/check_review_cohort_launch.py verify --plan "$(PLAN)" --expected-plan-sha256 "$(PLAN_SHA256)" --evidence "$(EVIDENCE)" --expected-evidence-sha256 "$(EVIDENCE_SHA256)" --require-ready --max-age-minutes "$${MAX_AGE_MINUTES:-15}"

batch-assignment-scale:
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/verify_batch_assignment_scale.py

batch-assignment-concurrency:
	PYTHONPATH=apps/api/src .venv/bin/python apps/api/scripts/verify_batch_assignment_concurrency.py

research-export-http:
	.venv/bin/python apps/api/scripts/run_research_export_http.py

dependency-lock-check:
	.venv/bin/python scripts/verify_dependency_locks.py

sbom:
	.venv/bin/python scripts/operations/generate_sbom.py

.PHONY: release-plan release-apply release-status
release-plan:
	$(PYTHON) scripts/operations/release.py plan --profile deploy/release/mercury-dev0.json --previous-release "$(PREVIOUS_RELEASE)" --release-id "$(RELEASE_ID)" --output "$(RELEASE_OUTPUT)"

release-apply:
	$(PYTHON) scripts/operations/release.py apply --output "$(RELEASE_OUTPUT)" --confirm "$(RELEASE_CONFIRM)" $(if $(PREPARE_ONLY),--prepare-only,)

release-status:
	$(PYTHON) scripts/operations/release.py status --output "$(RELEASE_OUTPUT)"

.PHONY: release-rollback
release-rollback:
	$(PYTHON) scripts/operations/release.py rollback --output "$(RELEASE_OUTPUT)" --confirm "$(RELEASE_CONFIRM)"

.PHONY: release-verify
release-verify:
	$(PYTHON) scripts/operations/release.py verify --profile deploy/release/mercury-dev0.json
