SHELL := /usr/bin/env bash
.DEFAULT_GOAL := help

KUBECONFIG ?= $(firstword $(wildcard $(HOME)/.kube/config-railiance01) $(wildcard $(HOME)/.kube/config))
KUBECTL_BIN ?= $(firstword $(shell command -v kubectl 2>/dev/null) $(wildcard $(HOME)/.local/bin/kubectl) kubectl)
KUBECTL     := $(KUBECTL_BIN) --kubeconfig=$(KUBECONFIG)

# Cluster identity pin (RPF-WP-0019).
#
# Two reachable clusters each carry a CNPG Cluster named `apps-pg` in a
# namespace named `databases`. Selecting by kubeconfig FILENAME does not
# protect against applying to the wrong one: KUBECONFIG is an environment
# variable, `?=` does not override it, and both files resolve to a
# 127.0.0.1 tunnel port. So the guard pins cluster IDENTITY instead —
# the kube-system namespace UID, which is unique per cluster and stable
# for its lifetime.
#
# railiance01 (k3s v1.35.1) carries platform-pg, forgejo-db and both
# apps-pg consumers. The other cluster carries gitea-db and only one
# apps-pg consumer; `db-status` legitimately targets it, which is why the
# guard is applied per-target rather than globally.
RAILIANCE01_CLUSTER_UID ?= a553c742-0115-43d4-99a4-a5ca56fe0786
HELM        := helm --kubeconfig=$(KUBECONFIG)
NAMESPACE   := platform

PG_CHART_VERSION  ?= 16.2.2
VALKEY_CHART_VERSION ?= 2.x
OPENBAO_CHART_VERSION ?= 0.28.2
OPENBAO_NAMESPACE ?= openbao
OPENBAO_RELEASE ?= openbao
OPENBAO_VALUES ?= helm/openbao-values.yaml
OPENBAO_MIDDLEWARE ?= helm/openbao-middleware.yaml
OPENBAO_UI_OVERLAY_DIR ?= helm/openbao-ui-overlay
OPENBAO_UI_OVERLAY_K8S ?= helm/openbao-ui-overlay-k8s.yaml
OPENBAO_PUBLIC_INGRESS_ROLLBACK ?= helm/openbao-public-ingress.rollback.yaml
OPENBAO_VERIFY_AUTH_ARGS ?=
OPENBAO_RESTORE_EVIDENCE ?= /tmp/netkingdom-openbao-restore-drill/evidence.json
OPENBAO_EMERGENCY_EVIDENCE ?= /tmp/netkingdom-openbao-emergency-drill/evidence.json
EXTERNAL_SECRETS_NAMESPACE ?= external-secrets
ARGOCD_NAMESPACE ?= argocd
ARGOCD_BOOTSTRAP_DIR ?= argocd/bootstrap
ARGOCD_REPOSITORY_SECRET ?=
CREDENTIAL_GRANTS ?= credential-grants/catalog.yaml
CREDENTIAL_CHANGE ?= CCR-2026-0001
CREDENTIAL_CHANGE_EVIDENCE_ARGS ?=
CREDENTIAL_CHANGE_LIFECYCLE_ACTION ?= deactivate
CREDENTIAL_CHANGE_LIFECYCLE_ARGS ?=
CREDENTIAL_CHANGE_IMPORT_ARGS ?=
STATE_HUB_URL ?= http://127.0.0.1:8000
OPENBAO_TOKEN_GRANT_ARGS ?=
OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS ?=
OPENBAO_WORKLOAD_KV_ARGS ?=
CREDENTIAL_HELPER_GLOBAL_ARGS ?=
CREDENTIAL_HELPER_ARGS ?=
CREDENTIAL_HELPER_CHILD_ENV ?=
CREDENTIAL_HELPER_PURPOSE ?= flex-auth-openbao-smoke

##@ CloudNative PG (cnpg) — primary database operator

db-deploy: ## Apply Gitea cnpg Cluster (creates gitea-db in databases namespace)
	$(KUBECTL) apply -f helm/gitea-db-cluster.yaml

db-status: ## Show cnpg cluster health
	$(KUBECTL) cnpg status gitea-db -n databases 2>/dev/null || \
	  $(KUBECTL) get cluster gitea-db -n databases -o wide

db-shell: ## Open psql shell on gitea-db primary
	$(KUBECTL) cnpg psql gitea-db -n databases -- -U gitea gitea

db-logs: ## Tail gitea-db primary logs
	$(KUBECTL) logs -n databases -l cnpg.io/cluster=gitea-db -f --tail=50

##@ Forgejo database (railiance01 production forge)

forgejo-db-deploy: ## Apply forgejo-db cnpg Cluster + NetworkPolicies on railiance01
	$(KUBECTL) apply -f helm/forgejo-db-cluster.yaml
	$(KUBECTL) apply -f helm/forgejo-db-networkpolicies.yaml

forgejo-db-status: ## Show forgejo-db cnpg cluster health
	$(KUBECTL) cnpg status forgejo-db -n databases 2>/dev/null || \
	  $(KUBECTL) get cluster forgejo-db -n databases -o wide

forgejo-db-shell: ## Open psql shell on forgejo-db primary
	$(KUBECTL) cnpg psql forgejo-db -n databases -- -U forgejo forgejo

CONSUMING_ENTITY ?=
ESTIMATE_EUR ?=
CONSUMPTION_CLASS ?= new-order
CONSUMPTION_MODE_FILE ?= data/consumption-mode/current.json

##@ Consumption mode (RAILIANCE-WP-0017)

consumption-preflight: ## Refuse a new order that exceeds a restricted entity's allowance
	@test -n "$(CONSUMING_ENTITY)" || { echo 'set CONSUMING_ENTITY=entity:<slug>' >&2; exit 2; }
	CONSUMPTION_MODE_FILE='$(CONSUMPTION_MODE_FILE)' python3 scripts/consumption_mode.py check \
	  --entity '$(CONSUMING_ENTITY)' \
	  --class '$(CONSUMPTION_CLASS)' \
	  $(if $(ESTIMATE_EUR),--estimate-eur '$(ESTIMATE_EUR)')

##@ Shared apps-pg (S5 application databases)

cluster-id: ## Print the kube-system UID of the cluster KUBECONFIG currently selects
	@echo "kubeconfig: $(KUBECONFIG)"
	@echo "cluster-uid: $$($(KUBECTL) get ns kube-system -o jsonpath='{.metadata.uid}' 2>/dev/null || echo UNREACHABLE)"

railiance01-guard: ## Fail closed unless KUBECONFIG selects railiance01
	@uid=$$($(KUBECTL) get ns kube-system -o jsonpath='{.metadata.uid}' 2>/dev/null); \
	if [ -z "$$uid" ]; then \
	  echo "railiance01-guard: cluster unreachable via $(KUBECONFIG)" >&2; \
	  echo "  hint: unset KUBECONFIG, or set it to ~/.kube/config-railiance01" >&2; \
	  exit 2; \
	fi; \
	if [ "$$uid" != "$(RAILIANCE01_CLUSTER_UID)" ]; then \
	  echo "railiance01-guard: REFUSING - wrong cluster" >&2; \
	  echo "  kubeconfig: $(KUBECONFIG)" >&2; \
	  echo "  expected:   $(RAILIANCE01_CLUSTER_UID)" >&2; \
	  echo "  found:      $$uid" >&2; \
	  echo "  both clusters carry apps-pg in namespace databases; applying here would hit the wrong one." >&2; \
	  exit 2; \
	fi; \
	echo "railiance01-guard: ok ($$uid)"

apps-pg-verify-capacity: ## Verify cell ceiling, role limits and distinct backup prefixes
	python3 tools/verify_apps_pg_capacity.py helm/apps-pg-cluster.yaml helm/apps-pg-2-cluster.yaml

apps-pg-deploy: railiance01-guard apps-pg-verify-capacity ## Apply shared apps-pg cnpg Cluster + NetworkPolicies
	@if [ -n "$(CONSUMING_ENTITY)" ]; then \
	  $(MAKE) consumption-preflight CONSUMING_ENTITY='$(CONSUMING_ENTITY)' \
	    ESTIMATE_EUR='$(ESTIMATE_EUR)' CONSUMPTION_CLASS=new-order; \
	fi
	$(KUBECTL) apply -f helm/apps-pg-cluster.yaml
	$(KUBECTL) apply -f helm/apps-pg-networkpolicies.yaml

apps-pg-backup-deploy: railiance01-guard ## Apply the daily backup after the governed S3 Secret exists
	@$(KUBECTL) get secret platform-pg-backup-s3 -n databases >/dev/null || { echo 'missing governed platform-pg-backup-s3 secret' >&2; exit 2; }
	$(KUBECTL) apply -f helm/apps-pg-backup.yaml

apps-pg-isolation-probe: railiance01-guard ## Run bounded live consumer-boundary and connection-saturation probes
	KUBECONFIG='$(KUBECONFIG)' KUBECTL_BIN='$(KUBECTL_BIN)' tools/apps-pg-isolation-probe.sh

apps-pg-overflow-dry-run: railiance01-guard apps-pg-verify-capacity ## Server-validate the unapplied overflow cell
	@$(KUBECTL) get secret apps-pg-2-credentials -n databases >/dev/null || { echo 'missing distinct apps-pg-2-credentials secret' >&2; exit 2; }
	$(KUBECTL) apply --dry-run=server -f helm/apps-pg-2-cluster.yaml
	$(KUBECTL) apply --dry-run=server -f helm/apps-pg-2-networkpolicies.yaml
	$(KUBECTL) apply --dry-run=server -f helm/apps-pg-2-backup.yaml

apps-pg-overflow-deploy: apps-pg-overflow-dry-run ## Provision only for an approved fourth consumer
	$(KUBECTL) apply -f helm/apps-pg-2-cluster.yaml
	$(KUBECTL) apply -f helm/apps-pg-2-networkpolicies.yaml
	$(KUBECTL) apply -f helm/apps-pg-2-backup.yaml

apps-pg-status: railiance01-guard ## Show apps-pg cnpg cluster health
	$(KUBECTL) cnpg status apps-pg -n databases 2>/dev/null || \
	  $(KUBECTL) get cluster apps-pg -n databases -o wide

apps-pg-shell: railiance01-guard ## Open psql shell on apps-pg primary as apps_admin / apps_meta
	$(KUBECTL) cnpg psql apps-pg -n databases -- -U apps_admin apps_meta 2>/dev/null || \
	  $(KUBECTL) exec -it -n databases apps-pg-1 -- psql -U apps_admin apps_meta

apps-pg-logs: ## Tail apps-pg primary logs
	$(KUBECTL) logs -n databases -l cnpg.io/cluster=apps-pg -f --tail=50

net-kingdom-pg-inter-hub-networkpolicy-deploy: ## Allow inter-hub to reach net-kingdom-pg
	$(KUBECTL) apply -f helm/net-kingdom-pg-inter-hub-networkpolicy.yaml

##@ PostgreSQL HA (retired — CNPG superseded this)

pg-deploy: ## Retired: refuse to deploy Bitnami postgresql-ha
	@echo 'pg-deploy is retired (RAILIANCE-WP-0016 item 14). Use apps-pg / forgejo-db / rapp-postgres.' >&2; exit 2

pg-status: ## Confirm Bitnami postgresql-ha is absent
	@if $(KUBECTL) get ns $(NAMESPACE) >/dev/null 2>&1; then \
	  $(KUBECTL) get pods -n $(NAMESPACE) -l app.kubernetes.io/name=postgresql-ha; \
	else \
	  echo 'postgresql-ha retired: namespace $(NAMESPACE) is absent'; \
	fi

pg-pgpool-check: ## Retired: no pgpool secret to check
	@echo 'pg-pgpool-check is retired with postgresql-ha (RAILIANCE-WP-0016 item 14).' >&2; exit 2

##@ Valkey (cache) — declared, not deployed

valkey-deploy: ## Gated: do not deploy Valkey until a consumer rapp exists
	@echo 'valkey-deploy is gated (RAILIANCE-WP-0016 item 14): no live consumer, nothing to extract from S2.' >&2; exit 2

valkey-status: ## Confirm Valkey is absent
	@if $(KUBECTL) get ns $(NAMESPACE) >/dev/null 2>&1; then \
	  $(KUBECTL) get pods -n $(NAMESPACE) -l app.kubernetes.io/name=valkey; \
	else \
	  echo 'valkey undeployed: namespace $(NAMESPACE) is absent'; \
	fi

##@ OpenBao (secrets)

openbao-repo: ## Add / update the official OpenBao Helm repository
	$(HELM) repo add openbao https://openbao.github.io/openbao-helm --force-update
	$(HELM) repo update openbao

openbao-dry-run: openbao-repo ## Render the OpenBao Helm release without applying it
	$(HELM) upgrade --install $(OPENBAO_RELEASE) openbao/openbao \
		--version $(OPENBAO_CHART_VERSION) \
		--namespace $(OPENBAO_NAMESPACE) \
		--create-namespace \
		-f $(OPENBAO_VALUES) \
		--dry-run

openbao-overlay-apply: ## Apply KeyCape login overlay gateway and assets
	OPENBAO_UI_OVERLAY_DIR=$(OPENBAO_UI_OVERLAY_DIR) \
	  OPENBAO_UI_OVERLAY_K8S=$(OPENBAO_UI_OVERLAY_K8S) \
	  KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  scripts/openbao-ui-overlay-apply.sh

openbao-verify-login-overlay: ## Verify public KeyCape login overlay is active
	OPENBAO_UI_OVERLAY_DIR=$(OPENBAO_UI_OVERLAY_DIR) \
	  scripts/openbao-verify-login-overlay.sh $(OPENBAO_VERIFY_LOGIN_OVERLAY_ARGS)

openbao-deploy: openbao-repo ## Deploy / upgrade OpenBao to the openbao namespace
	$(KUBECTL) create namespace $(OPENBAO_NAMESPACE) --dry-run=client -o yaml | $(KUBECTL) apply -f -
	$(HELM) upgrade --install $(OPENBAO_RELEASE) openbao/openbao \
		--version $(OPENBAO_CHART_VERSION) \
		--namespace $(OPENBAO_NAMESPACE) \
		-f $(OPENBAO_VALUES) \
		--wait --timeout 5m
	$(MAKE) openbao-overlay-apply

.PHONY: openbao-public-listener-dry-run openbao-public-listener-status openbao-public-listener-retract openbao-public-listener-rollback

openbao-public-listener-dry-run: ## Validate the guarded public-listener retraction without mutation
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_UI_OVERLAY_K8S=$(OPENBAO_UI_OVERLAY_K8S) \
	  OPENBAO_PUBLIC_INGRESS_ROLLBACK=$(OPENBAO_PUBLIC_INGRESS_ROLLBACK) \
	  scripts/openbao-public-listener-transition.sh dry-run

openbao-public-listener-status: ## Report public Ingress and private tunnel readiness
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  scripts/openbao-public-listener-transition.sh status

openbao-public-listener-retract: ## Attended deletion after exact tunnel/login confirmation
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_PUBLIC_INGRESS_ROLLBACK=$(OPENBAO_PUBLIC_INGRESS_ROLLBACK) \
	  scripts/openbao-public-listener-transition.sh apply

openbao-public-listener-rollback: ## Attended restoration of the prior public Ingress
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_MIDDLEWARE=$(OPENBAO_MIDDLEWARE) \
	  OPENBAO_PUBLIC_INGRESS_ROLLBACK=$(OPENBAO_PUBLIC_INGRESS_ROLLBACK) \
	  scripts/openbao-public-listener-transition.sh rollback

openbao-status: ## Show OpenBao pods, services, PVCs, and seal/init status
	$(KUBECTL) get pods,svc,pvc -n $(OPENBAO_NAMESPACE) \
		-l app.kubernetes.io/instance=$(OPENBAO_RELEASE) -o wide
	-$(KUBECTL) exec -n $(OPENBAO_NAMESPACE) $(OPENBAO_RELEASE)-0 -- bao status

openbao-verify: ## Run non-secret OpenBao deployment checks
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify.sh basic

openbao-verify-post-unseal: ## Run post-unseal OpenBao filesystem checks
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify.sh post-unseal

openbao-configure-initial: ## Apply first post-unseal audit, auth, mounts, and policies
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-apply-initial-config.sh

openbao-configure-ssh: ## Enable SSH secrets engine, roles, and warden-sign policy
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-apply-ssh-engine.sh

openbao-verify-ssh: ## Verify SSH engine mount, roles, and warden-sign policy
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify-ssh-engine.sh

openbao-verify-authenticated: ## Run authenticated non-mutating OpenBao audit/auth/mount checks
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify-authenticated.sh $(OPENBAO_VERIFY_AUTH_ARGS)

openbao-configure-external-secrets-issue-core: ## Configure OpenBao policy/role for issue-core ESO pilot
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) ESO_NAMESPACE=$(EXTERNAL_SECRETS_NAMESPACE) \
	  scripts/openbao-apply-external-secrets-issue-core.sh

openbao-configure-external-secrets-activity-core: ## Configure OpenBao policy/role for activity-core ESO lane
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) 	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) ESO_NAMESPACE=$(EXTERNAL_SECRETS_NAMESPACE) 	  OPENBAO_ESO_ROLE=external-secrets-activity-core 	  OPENBAO_ESO_POLICY=external-secrets-activity-core 	  POLICY_FILE='$(CURDIR)/openbao/policies/external-secrets-activity-core.hcl' 	  OPENBAO_ESO_NEXT_PATH=platform/workloads/issue-core/issue-core/issue-core-runtime 	  OPENBAO_ESO_NEXT_FIELDS=ISSUE_CORE_API_KEY 	  OPENBAO_ESO_NEXT_TARGET=ExternalSecret/activity-core/actcore-issue-core-runtime 	  scripts/openbao-apply-external-secrets-issue-core.sh

openbao-configure-external-secrets-forgejo: ## Configure OpenBao policy/role for Forgejo mailer ESO lane
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) ESO_NAMESPACE=$(EXTERNAL_SECRETS_NAMESPACE) \
	  OPENBAO_ESO_ROLE=external-secrets-forgejo \
	  OPENBAO_ESO_POLICY=external-secrets-forgejo \
	  POLICY_FILE='$(CURDIR)/openbao/policies/external-secrets-forgejo.hcl' \
	  OPENBAO_ESO_NEXT_PATH=platform/workloads/forgejo/forgejo-mailer \
	  OPENBAO_ESO_NEXT_FIELDS=MAILER_PASSWD \
	  OPENBAO_ESO_NEXT_TARGET=ExternalSecret/forgejo/forgejo-mailer \
	  scripts/openbao-apply-external-secrets-issue-core.sh

openbao-workload-kv-lanes-dry-run: ## Dry-run OpenBao workload KV read-lane policy apply
	scripts/openbao-apply-workload-kv-lanes.sh --dry-run $(OPENBAO_WORKLOAD_KV_ARGS)

openbao-configure-workload-kv-lanes: ## Configure OpenBao workload KV read-lane policies
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
	  scripts/openbao-apply-workload-kv-lanes.sh $(OPENBAO_WORKLOAD_KV_ARGS)

openbao-validate-restore-evidence: ## Validate non-secret OpenBao restore-drill evidence JSON
	OPENBAO_RESTORE_EVIDENCE='$(OPENBAO_RESTORE_EVIDENCE)' \
	  scripts/openbao-validate-restore-evidence.sh

openbao-validate-emergency-evidence: ## Validate non-secret OpenBao emergency seal/unseal drill evidence JSON
	OPENBAO_EMERGENCY_EVIDENCE='$(OPENBAO_EMERGENCY_EVIDENCE)' \
	  scripts/openbao-validate-emergency-drill-evidence.sh

##@ Credential broker

credential-grants-validate: ## Validate non-secret credential grant catalog
	scripts/credential-grants-validate.py $(CREDENTIAL_GRANTS)

credential-change-validate: ## Validate non-secret credential change requests
	scripts/credential-change.py validate

credential-change-render: ## Render a credential change request review summary
	scripts/credential-change.py render $(CREDENTIAL_CHANGE)

credential-change-plan: ## Render a credential change request apply plan for review
	scripts/credential-change.py plan $(CREDENTIAL_CHANGE)

credential-change-decision-templates: ## Render CCR approve/deny/needs-changes templates
	scripts/credential-change.py decision-templates $(CREDENTIAL_CHANGE)

credential-change-status: ## Render credential change request readiness status
	scripts/credential-change.py status $(CREDENTIAL_CHANGE)

credential-change-status-json: ## Render credential change request readiness status as JSON
	scripts/credential-change.py status --json $(CREDENTIAL_CHANGE)

credential-change-sync-decision: ## Sync resolved State Hub decision back into a CCR
	scripts/credential-change.py sync-decision $(CREDENTIAL_CHANGE) --state-hub-url $(STATE_HUB_URL)

credential-change-apply-plan: ## Render approved-only operator apply plan
	scripts/credential-change.py apply-plan $(CREDENTIAL_CHANGE)

credential-change-operator-commands: ## Render approved-only non-secret OpenBao operator commands
	scripts/credential-change.py operator-commands $(CREDENTIAL_CHANGE)

credential-change-applier-dry-run: ## Validate delegated OpenBao metadata mutations for a CCR
	scripts/credential-change.py applier-dry-run $(CREDENTIAL_CHANGE)

credential-change-applier-apply-plan: ## Render delegated OpenBao metadata apply plan
	scripts/credential-change.py applier-apply $(CREDENTIAL_CHANGE) --plan-only

credential-change-applier-apply: ## Apply delegated metadata; pass confirmation/actor args via CREDENTIAL_CHANGE_EVIDENCE_ARGS
	scripts/credential-change.py applier-apply $(CREDENTIAL_CHANGE) $(CREDENTIAL_CHANGE_EVIDENCE_ARGS)

credential-change-runbook: ## Render the attended CCR apply/verify runbook
	scripts/credential-change.py runbook $(CREDENTIAL_CHANGE)

credential-change-record-evidence: ## Record non-secret CCR evidence; pass CREDENTIAL_CHANGE_EVIDENCE_ARGS
	scripts/credential-change.py record-evidence $(CREDENTIAL_CHANGE) $(CREDENTIAL_CHANGE_EVIDENCE_ARGS)

credential-change-lifecycle-plan: ## Render deactivation/rotation/compromise lifecycle guidance
	scripts/credential-change.py lifecycle-plan $(CREDENTIAL_CHANGE) --action $(CREDENTIAL_CHANGE_LIFECYCLE_ACTION)

credential-change-lifecycle-event: ## Record lifecycle event; pass CREDENTIAL_CHANGE_LIFECYCLE_ARGS
	scripts/credential-change.py lifecycle-event $(CREDENTIAL_CHANGE) --action $(CREDENTIAL_CHANGE_LIFECYCLE_ACTION) $(CREDENTIAL_CHANGE_LIFECYCLE_ARGS)

agent-high-risk-boundary-check: ## Check policy against the versioned ops-warden high-risk path input
	python3 scripts/agent_high_risk_boundary.py --json

credential-change-import-inventory: ## Import existing lane as non-secret CCR; pass CREDENTIAL_CHANGE_IMPORT_ARGS
	scripts/credential-change.py import-inventory $(CREDENTIAL_CHANGE_IMPORT_ARGS)

openbao-credential-change-appliers-dry-run: ## Dry-run credential-change applier policies/token roles
	scripts/openbao-apply-credential-change-appliers.py --dry-run $(OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS)

openbao-configure-credential-change-appliers: ## Apply credential-change applier policies/token roles
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
	  scripts/openbao-apply-credential-change-appliers.py $(OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS)

openbao-token-grants-dry-run: ## Dry-run OpenBao token roles and issuer policies for credential grants
	scripts/openbao-apply-token-grants.py --dry-run $(OPENBAO_TOKEN_GRANT_ARGS)

openbao-configure-token-grants: ## Apply OpenBao token roles and issuer policies for credential grants
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
	  scripts/openbao-apply-token-grants.py $(OPENBAO_TOKEN_GRANT_ARGS)

openbao-verify-token-grants-dry-run: ## Dry-run OpenBao token grant verification
	scripts/openbao-verify-token-grants.py --dry-run $(OPENBAO_TOKEN_GRANT_ARGS)

openbao-verify-token-grants: ## Verify OpenBao token roles and issuer policies for credential grants
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
	  scripts/openbao-verify-token-grants.py $(OPENBAO_TOKEN_GRANT_ARGS)

openbao-verify-token-grants-smoke: ## Mint/revoke a child token and prove bounded warden-sign capabilities
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
	  scripts/openbao-verify-token-grants.py --issue-smoke-token $(OPENBAO_TOKEN_GRANT_ARGS)

credential-helper-dry-run: ## Dry-run credential request, exec, status, and revoke helper flows
	scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) request --dry-run \
	  --grant ops-warden/warden-sign --purpose $(CREDENTIAL_HELPER_PURPOSE) \
	  $(CREDENTIAL_HELPER_ARGS)
	scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) request --dry-run \
	  --grant ops-warden/warden-sign --purpose $(CREDENTIAL_HELPER_PURPOSE) \
	  --delivery kubernetes-auth $(CREDENTIAL_HELPER_ARGS)
	scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) exec --dry-run \
	  --grant ops-warden/warden-sign --purpose $(CREDENTIAL_HELPER_PURPOSE) \
	  $(CREDENTIAL_HELPER_ARGS) -- SMOKE_VAULT=1 /bin/true
	scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) status --dry-run example-accessor
	scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) revoke --dry-run example-accessor

credential-tests: ## Run offline credential broker unit tests
	python3 -m unittest discover -s tests -p 'test_credential*.py'

credential-change-tests: ## Run credential change request unit tests
	python3 -m unittest discover -s tests -p 'test_credential_change.py'

credential-exec-ops-warden-smoke: ## Run ops-warden smoke with an exec-injected warden-sign token
	KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
	  OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
	  scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) exec \
	  --grant ops-warden/warden-sign --purpose ops-warden-production-sign-smoke \
	  $(CREDENTIAL_HELPER_ARGS) -- \
	  $(CREDENTIAL_HELPER_CHILD_ENV) \
	  SMOKE_VAULT=1 /home/worsch/ops-warden/scripts/policy_gate_production_smoke.sh

##@ ArgoCD GitOps bootstrap

argocd-bootstrap-dry-run: ## Server-side dry-run ArgoCD AppProjects and root Application
	$(KUBECTL) apply --dry-run=server -k $(ARGOCD_BOOTSTRAP_DIR)

argocd-bootstrap-deploy: ## Apply ArgoCD AppProjects and root Application
	$(KUBECTL) apply -k $(ARGOCD_BOOTSTRAP_DIR)

argocd-repo-apply: ## Apply a SOPS-encrypted ArgoCD repository Secret (set ARGOCD_REPOSITORY_SECRET)
	@test -n "$(ARGOCD_REPOSITORY_SECRET)" || \
	  (echo "ERROR: set ARGOCD_REPOSITORY_SECRET=argocd/repositories/<repo>.repository.sops.yaml"; exit 1)
	sops -d $(ARGOCD_REPOSITORY_SECRET) | $(KUBECTL) apply -f -

argocd-status: ## Show Railiance ArgoCD projects, root app, and registered repos
	$(KUBECTL) get appprojects.argoproj.io -n $(ARGOCD_NAMESPACE) \
	  railiance-bootstrap railiance-tenants railiance-platform-addons
	$(KUBECTL) get applications.argoproj.io -n $(ARGOCD_NAMESPACE) \
	  railiance-apps-root external-secrets openbao-secretstore issue-core
	$(KUBECTL) get secrets -n $(ARGOCD_NAMESPACE) \
	  -l argocd.argoproj.io/secret-type=repository

##@ Backup

RAILIANCE01_KUBECONFIG ?= $(HOME)/.kube/config-railiance01

backup: ## Backup platform services (PostgreSQL logical dump) — age-encrypted to Nextcloud
	@test -x tools/cmd/railiance-backup || { echo "tools/cmd/railiance-backup not installed; use forgejo-backup for Forgejo." >&2; exit 1; }
	sudo tools/cmd/railiance-backup

forgejo-backup: ## Forgejo dump + forgejo-db pg_dump → age → Nextcloud (T04/T09 Option A)
	KUBECONFIG="$(RAILIANCE01_KUBECONFIG)" tools/cmd/forgejo-backup

forgejo-backup-dry-run: ## Forgejo backup without Nextcloud upload
	KUBECONFIG="$(RAILIANCE01_KUBECONFIG)" RAILIANCE_BACKUP_DRY_RUN=1 tools/cmd/forgejo-backup

forgejo-backup-status: ## Show last Forgejo backup success and 7-day gate
	@STAMP="$(HOME)/.cache/railiance/backups/forgejo/success-log"; \
	if [[ ! -f "$$STAMP" ]]; then echo "No forgejo backups recorded yet."; exit 1; fi; \
	echo "Last success: $$(tail -1 "$$STAMP")"; \
	echo "Recent successes:"; tail -7 "$$STAMP"; \
	echo "7-day gate: $$(tail -7 "$$STAMP" | wc -l)/7 consecutive days logged (verify cron separately)"

forgejo-package-prune-dry-run: ## List Forgejo package versions beyond retention depth (no deletes)
	tools/cmd/forgejo-package-prune

forgejo-package-prune: ## Prune Forgejo packages — keep newest 3 versions per package
	tools/cmd/forgejo-package-prune --apply

##@ Family declarations

reef-deployables: ## Capture live reef-railiance deployables for the family validator
	python3 scripts/capture-reef-deployables.py -o docs/evidence/reef-railiance-deployables.json

apps-pg-evidence: ## Capture non-secret apps-pg capacity evidence for resource-control
	python3 scripts/capture-apps-pg-evidence.py -o docs/evidence/RAILIANCE-WP-0016-apps-pg-observation.json

##@ Help

help: ## Show this help
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n"} \
	  /^[a-zA-Z_-]+:.*?##/ { printf "  \033[36m%-22s\033[0m %s\n", $$1, $$2 } \
	  /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)

audit-whitehat-e2-preflight: ## Value-safe live preflight for the approved audit-core E2 window
	python3 scripts/audit-core-whitehat-e2-credentials.py preflight

audit-whitehat-e2-project: ## Project two exact mount-only credentials during the approved window
	python3 scripts/audit-core-whitehat-e2-credentials.py project --confirm WH-ENG-20260822-AUDIT-E2-01:attended

audit-whitehat-e2-cleanup: ## Remove the exact engagement credentials and reload audit-core
	python3 scripts/audit-core-whitehat-e2-credentials.py cleanup --confirm WH-ENG-20260822-AUDIT-E2-01:attended

audit-database-lease-recovery-preflight: ## Read-only gate for the restart-free database lease exercise
	python3 scripts/audit-core-recovery-preflight.py database-lease $(AUDIT_DATABASE_LEASE_PREFLIGHT_ARGS)

audit-database-lease-approval-check: ## Validate an approved T02 receipt without mutation
	@test -n "$(AUDIT_DATABASE_LEASE_APPROVAL)" || { echo 'set AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json' >&2; exit 2; }
	python3 scripts/audit-core-database-lease-recovery.py validate-approval \
	  --approval "$(AUDIT_DATABASE_LEASE_APPROVAL)"

audit-database-lease-recovery-exercise: ## Run the separately approved attended T02 exercise
	@test -n "$(AUDIT_DATABASE_LEASE_APPROVAL)" || { echo 'set AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json' >&2; exit 2; }
	@test -n "$(AUDIT_DATABASE_LEASE_DRIVER)" || { echo 'set AUDIT_DATABASE_LEASE_DRIVER=/path/to/approved-load-driver' >&2; exit 2; }
	python3 scripts/audit-core-database-lease-recovery.py exercise \
	  --approval "$(AUDIT_DATABASE_LEASE_APPROVAL)" \
	  --load-driver "$(AUDIT_DATABASE_LEASE_DRIVER)" \
	  --confirm RAILIANCE-WP-0024-T02:attended

railiance01-reboot-preflight: ## Read-only gate for the coordinated single-node reboot
	python3 scripts/audit-core-recovery-preflight.py node-reboot $(RAILIANCE01_REBOOT_PREFLIGHT_ARGS)

wp0024-owner-show: ## Show one owner's hash-bound review contract (set OWNER)
	@test -n "$(OWNER)" || { echo 'set OWNER=audit-core|rapp-postgres|railiance-cluster|railiance-infra' >&2; exit 2; }
	@python3 scripts/wp0024-owner-review.py show --owner "$(OWNER)"

wp0024-owner-verify: ## Run one owner's named read-only checks (set OWNER)
	@test -n "$(OWNER)" || { echo 'set OWNER=audit-core|rapp-postgres|railiance-cluster|railiance-infra' >&2; exit 2; }
	@python3 scripts/wp0024-owner-review.py verify --owner "$(OWNER)"

wp0024-owner-status: ## Collect current hash-bound owner receipts from State Hub
	@python3 scripts/wp0024-owner-review.py status

wp0024-t02-driver-show: ## Show the exact T02 synthetic-load driver interface
	@python3 scripts/wp0024-t02-driver-candidate.py show

wp0024-t02-driver-status: ## Collect a revision-pinned audit-core driver candidate
	@python3 scripts/wp0024-t02-driver-candidate.py status

wp0024-t02-driver-register: ## Register committed audit-core driver source without executing it
	@test -n "$(DRIVER)" || { echo 'set DRIVER to a path relative to the audit-core repo' >&2; exit 2; }
	@test -n "$(REVIEWER)" || { echo 'set REVIEWER to the audit-core reviewer identifier' >&2; exit 2; }
	@python3 scripts/wp0024-t02-driver-candidate.py register \
	  --repo "$(abspath ../audit-core)" --driver "$(DRIVER)" --reviewer "$(REVIEWER)"

.PHONY: audit-whitehat-e2-preflight audit-whitehat-e2-project audit-whitehat-e2-cleanup audit-database-lease-recovery-preflight audit-database-lease-approval-check audit-database-lease-recovery-exercise railiance01-reboot-preflight wp0024-owner-show wp0024-owner-verify wp0024-owner-status wp0024-t02-driver-show wp0024-t02-driver-status wp0024-t02-driver-register apps-pg-isolation-probe
.PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory agent-high-risk-boundary-check openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run reef-deployables apps-pg-evidence consumption-preflight help
