# OgmaProtect - OpenBSD build# Run on OpenBSD: make && sudo make install

PROG_NETD=	ogmaprotect-netd
PROG_AUTHD=	ogmaprotect-authd
PROG_RTD=	ogmaprotect-rtd
PROG_PFD=	ogmaprotect-pfd
PROG_DNSD=	ogmaprotect-dnsd
PROG_DHCPD=	ogmaprotect-dhcpd
PROG_ROUTED=	ogmaprotect-routed
PROG_DIAGD=	ogmaprotect-diagd
PROG_HEALTHD=	ogmaprotect-healthd
PROG_ARPD=	ogmaprotect-arpd
PROG_LOGD=	ogmaprotect-logd
PROG_TIMED=	ogmaprotect-timed
PROG_IPSECD=	ogmaprotect-ipsecd
PROG_SYSD=	ogmaprotect-sysd
PROG_ALERTD=	ogmaprotect-alertd
PROG_CTL=	ogmaprotectctl
PROG_DIAGSWEEP=	ogmaprotect-diag-sweep
PROG_WEBHOOK=	ogmaprotect-webhook
PROG_HASYNC=	ogmaprotect-hasync
PROG_UPDFETCH=	ogmaprotect-updfetch
PROG_UPDSTAGE=	ogmaprotect-updstage
PROG_GWMOND=	ogmaprotect-gwmond

BINDIR?=	/usr/local/sbin
MANDIR?=	/usr/local/man
WEBDIR?=	/var/www/htdocs/ogmaprotect
ETCDIR?=	/etc/ogmaprotect
DBDIR?=		/var/db/ogmaprotect
RUN_WWW?=	/var/www/run
RUN_NETD?=	/var/run

COMMON=	daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/json_dns.c \
		daemon/common/pf_structured.c \
		daemon/common/yaml_lite.c daemon/common/ops.c \
		daemon/common/idempotency.c \
		daemon/common/validate.c \
		daemon/common/util.c daemon/common/revision.c daemon/common/backup.c \
		daemon/common/text_diff.c \
		daemon/common/backup_archive.c daemon/common/apply_lock.c \
		daemon/common/apply_noop.c \
		daemon/common/backup_snap.c daemon/common/restore_lock.c \
		daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/cap.c daemon/common/cap_hmac.c \
		daemon/common/daemon_inventory.c \
		daemon/common/config_manifest.c \
		daemon/common/json_manifest.c \
		daemon/common/schema_state.c \
		daemon/common/schema_converge.c \
		daemon/common/json_schema_state.c \
		daemon/common/wg_drift.c \
		daemon/common/tunnel_drift.c \
		daemon/common/pppoe_drift.c \
		daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c \
		daemon/common/drift.c \
		daemon/common/iface_stats.c \
		daemon/common/bridge_stats.c \
		daemon/common/pf_states.c \
		daemon/common/pf_table.c \
		daemon/common/pf_bruteforce.c \
		daemon/common/log_lines.c daemon/common/pflog.c \
		daemon/common/json_routes.c daemon/common/routes_compose.c \
		daemon/common/cert_canonical.c \
		daemon/common/cert_drift.c \
		daemon/common/identity_drift.c \
		daemon/common/os_release.c \
		daemon/common/hasync.c \
		daemon/common/frame.c \
		daemon/common/lateral.c \
		daemon/common/egress_url.c
NETD_SRC=	daemon/netd/main.c daemon/common/daemon_base.c daemon/netd/peer.c daemon/netd/hostname.c \
		daemon/netd/dhcp_apply.c \
		daemon/common/dhcp.c daemon/common/dhcp_txn.c \
		daemon/common/dhcp6.c \
		daemon/common/canonical.c daemon/common/backup_validate.c \
		daemon/common/backup_apply.c daemon/common/routes_canonical.c \
		daemon/rtd/route.c \
		daemon/netd/wg_pending.c \
		daemon/netd/wg_apply.c \
		daemon/netd/tunnel_pending.c \
		daemon/netd/pending_mux.c \
		daemon/common/confirm_txn.c daemon/netd/address_pending.c \
		daemon/netd/carp_pending.c \
		daemon/netd/v6_pending.c \
		daemon/netd/ifconfig.c \
		daemon/common/boot_drift.c \
		daemon/netd/backup_apply_sysctl.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/backup_validate_sysctl.c \
		daemon/netd/order_repair.c \
		daemon/netd/audit.c daemon/netd/dispatch.c
AUTHD_SRC=	daemon/authd/main.c daemon/authd/auth_db.c \
		daemon/authd/proxy.c daemon/authd/proxy_slice.c \
		daemon/authd/dispatch.c daemon/authd/authorize.c \
		daemon/authd/authd_route.c \
		daemon/authd/supervision.c \
		daemon/authd/audit_verify.c daemon/common/audit_chain.c \
		daemon/common/audit_ledger.c \
		daemon/common/mfa_record.c \
		daemon/authd/routes.c daemon/authd/dhcp_apply.c \
		daemon/authd/backup_coordinator.c daemon/authd/backup_crypto.c \
		daemon/authd/restore_journal.c \
		daemon/authd/rbac.c \
		daemon/authd/secrets.c daemon/authd/migrate.c \
		daemon/authd/front_door.c \
		daemon/authd/jobs.c daemon/authd/job_runner.c \
		daemon/authd/job_tier_a.c \
		daemon/authd/job_dhcp.c \
		daemon/authd/job_routing.c \
		daemon/authd/job_ipsec.c \
		daemon/authd/job_pki.c \
		daemon/authd/job_archive.c \
		daemon/authd/job_support.c \
		daemon/authd/support_bundle.c \
		daemon/authd/support_sources.c \
		daemon/authd/archive_op.c \
		daemon/authd/backup_secret_report.c \
		daemon/authd/routing_apply.c \
		daemon/common/daemon_base.c \
		daemon/authd/wg.c daemon/authd/ospf.c \
		daemon/authd/bgp.c daemon/authd/pppoe.c daemon/authd/carp.c \
		daemon/common/dhcp.c daemon/common/dhcp_txn.c \
		daemon/common/canonical.c daemon/common/pf_canonical.c \
		daemon/common/dns_canonical.c \
		daemon/common/dhcpd_canonical.c \
		daemon/common/routing_canonical.c \
		daemon/common/routes_canonical.c \
		daemon/common/arp_canonical.c \
		daemon/common/backup_validate.c \
		daemon/common/backup_validate_pf.c \
		daemon/common/backup_validate_dns.c \
		daemon/common/backup_validate_dhcpd.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/backup_validate_arp.c \
		daemon/common/time_canonical.c \
		daemon/common/auth_canonical.c \
		daemon/common/backup_validate_time.c \
		daemon/common/backup_validate_cert.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/gateways_canonical.c \
		daemon/common/remotelog_canonical.c \
		daemon/common/alerts_canonical.c \
		daemon/common/backup_validate_remotelog.c \
		daemon/common/backup_validate_alerts.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/identity_canonical.c \
		daemon/common/backup_validate_sysctl.c \
		daemon/common/backup_validate_identity.c \
		daemon/common/ipsec_canonical.c daemon/common/ipsec_gate.c \
		daemon/common/backup_validate_ipsec.c \
		daemon/authd/ipsec.c daemon/authd/ipsec_pki.c \
		daemon/authd/hasync.c \
		daemon/authd/hasync_egress.c \
		daemon/authd/sign_helper.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/netd/audit.c \
		daemon/common/totp.c \
		daemon/common/service_table.c daemon/authd/svc_expect.c \
		daemon/common/boot_drift.c
RTD_CFLAGS+=	-DOGMA_HAVE_IFCONFIG
RTD_SRC=	daemon/rtd/main.c daemon/common/daemon_base.c daemon/rtd/peer.c daemon/rtd/dispatch.c \
		daemon/rtd/dhcp_reconcile.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/netd/ifconfig.c \
		daemon/rtd/route.c daemon/rtd/apply.c daemon/rtd/backup_apply.c \
		daemon/rtd/backup_apply_gateways.c \
		daemon/rtd/route_pending.c daemon/common/confirm_txn.c \
		daemon/rtd/gateways_pending.c daemon/rtd/pending_mux.c \
		daemon/rtd/audit.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/boot_drift.c \
		daemon/common/routes_canonical.c \
		daemon/common/gateways_canonical.c
PFD_SRC=	daemon/pfd/main.c daemon/common/daemon_base.c daemon/pfd/peer.c daemon/pfd/dispatch.c \
		daemon/pfd/pfctl.c daemon/pfd/apply.c daemon/pfd/states.c daemon/pfd/backup_apply.c \
		daemon/pfd/bruteforce.c \
		daemon/pfd/audit.c \
		daemon/common/pf_canonical.c daemon/common/confirm_txn.c \
		daemon/common/backup_validate_pf.c \
		daemon/common/boot_drift.c \
		daemon/common/routes_canonical.c \
		daemon/common/gateways_canonical.c
DNSD_CFLAGS+=	-DOGMA_HAVE_IFCONFIG
DNSD_SRC=	daemon/dnsd/main.c daemon/common/daemon_base.c daemon/dnsd/peer.c daemon/dnsd/dispatch.c \
		daemon/dnsd/apply.c daemon/dnsd/unbound.c daemon/dnsd/expand.c \
		daemon/dnsd/probe.c \
		daemon/dnsd/dns_pending.c daemon/common/confirm_txn.c \
		daemon/dnsd/backup_apply.c \
		daemon/dnsd/audit.c \
		daemon/common/dns_canonical.c \
		daemon/common/backup_validate_dns.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/boot_drift.c \
		daemon/common/routes_canonical.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c
DHCPD_CFLAGS+=	-DOGMA_HAVE_IFCONFIG
DHCPD_SRC=	daemon/dhcpd/main.c daemon/common/daemon_base.c daemon/dhcpd/peer.c daemon/dhcpd/dispatch.c \
		daemon/dhcpd/apply.c daemon/dhcpd/dhcpd_exec.c daemon/dhcpd/expand.c \
		daemon/dhcpd/audit.c daemon/dhcpd/backup_apply.c \
		daemon/common/dhcpd_canonical.c daemon/common/json_dhcpd.c \
		daemon/common/dhcpd_leases.c \
		daemon/common/backup_validate_dhcpd.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/boot_drift.c \
		daemon/common/routes_canonical.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c
# Phase 7.2 routed (ospfd). No net.yaml/ifconfig dependency — ospfd.conf renders
# directly from canonical, so no expansion (unlike dhcpd/dnsd).
ROUTED_SRC=	daemon/routed/main.c daemon/common/daemon_base.c daemon/routed/peer.c daemon/routed/dispatch.c \
		daemon/routed/apply.c daemon/routed/svc_apply.c daemon/routed/svc_exec.c daemon/routed/ospfd_exec.c daemon/routed/bgpd_exec.c daemon/routed/reload_classify.c daemon/routed/state.c \
		daemon/routed/audit.c daemon/routed/backup_apply.c \
		daemon/routed/status.c daemon/routed/boot_reconcile.c \
		daemon/routed/routing_pending.c daemon/common/confirm_txn.c \
		daemon/common/routing_canonical.c daemon/common/json_routing.c \
		daemon/common/routing_status.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/boot_drift.c \
		daemon/common/routes_canonical.c

DIAGD_SRC=	daemon/diagd/main.c daemon/common/daemon_base.c daemon/diagd/peer.c daemon/diagd/dispatch.c \
		daemon/diagd/audit.c daemon/diagd/diag.c daemon/common/routes_canonical.c

# Phase 9.O6 sweep/port-scan helper — a standalone bounded probe runner diagd
# execs (NOT a daemon: no daemon_base/socket). Links ${COMMON} for the shared
# validators (ogma_valid_sweep_cidr/_scan_range/_port_range) + util;
# routes_canonical.c is required because ${COMMON} objects reference
# ogma_route_matches/ogma_routes_free (the same per-binary add every other
# COMMON-linked program makes).
DIAGSWEEP_SRC=	daemon/diagd/sweep_helper.c daemon/common/routes_canonical.c

# Phase 9.O5.2 alertd webhook egress helper — a standalone libtls HTTPS-POST
# binary (alertd execs it; the parent holds no inet promise). Self-contained (it
# does NOT link ${COMMON}) to keep the network-facing binary minimal, and
# OpenBSD-only (libtls + pledge/unveil) — built by `make all` in the OpenBSD VM,
# NEVER by the Linux CI test/fuzz tiers. It compiles the 10.C12 single-sourced
# egress gates (egress_url.c pure-C + egress_tls.c libtls) DIRECTLY — do NOT add
# ${COMMON} here: egress_url.c already rides COMMON, so that would double-link it.
WEBHOOK_SRC=	daemon/alertd/webhook_helper.c \
		daemon/common/egress_url.c daemon/common/egress_tls.c
WEBHOOK_LIBS=	-ltls -lssl -lcrypto

# Phase 9.R2.c peer config-sync egress helper — a standalone libtls HTTPS-POST
# binary with certificate PINNING (authd's egress child execs it; authd holds no
# inet/proc/exec promise). Self-contained (does NOT link ${COMMON}) and OpenBSD-only
# (libtls + pledge) — built by `make all` in the OpenBSD VM, NEVER by the Linux CI
# test/fuzz tiers. Must stay OUT of AUTHD_SRC/COMMON/every test target. It compiles
# the 10.C12 single-sourced egress gates (egress_url.c + egress_tls.c) DIRECTLY —
# do NOT add ${COMMON} here (egress_url.c already rides COMMON → double-link).
HASYNC_SRC=	daemon/authd/hasync_helper.c \
		daemon/common/egress_url.c daemon/common/egress_tls.c
HASYNC_LIBS=	-ltls -lssl -lcrypto

# Phase 10.E6 S3 update-manifest fetch helper (contract §2 P8: the THIRD egress
# helper) — a standalone libtls HTTPS-GET binary that returns a BOUNDED body on
# stdout (sysd execs it; sysd holds no inet/dns promise). System CA + DNS, 2xx
# only, Content-Length AND chunked framing decoded by the pure-C egress_http.c
# (which IS tested on the Linux leg, test-egress). Self-contained (does NOT
# link ${COMMON}) and OpenBSD-only — built by `make all` in the OpenBSD VM,
# NEVER by the Linux CI test/fuzz tiers. Same rule as its two siblings: do NOT
# add ${COMMON} here (egress_url.c already rides COMMON → double-link); keep it
# OUT of SYSD_SRC/every test target.
UPDFETCH_SRC=	daemon/sysd/updfetch_helper.c \
		daemon/common/egress_url.c daemon/common/egress_tls.c \
		daemon/common/egress_http.c
UPDFETCH_LIBS=	-ltls -lssl -lcrypto

# Phase 10.E10 S1+S2 two-step-update stage helper (contract §3.1 M-helper, §7
# D-2: the FOURTH egress helper) — a standalone libtls HTTPS-GET binary that
# STREAMS the body into a `file:` sink under the sysd-only stage leaf (sink-
# only unveil; Range/If-Range resume) or — S2 — gunzips it inline (zlib from
# base, egress_gunzip.c) onto a `dev:` inactive-root sink ("w" unveil, no
# resume), stall-alarmed; sysd's forked worker execs it. Same rules as
# updfetch: self-contained (does NOT link ${COMMON}), OpenBSD-only, keep it
# OUT of SYSD_SRC/every test target. It compiles the SAME egress_http.c as
# updfetch but with -DOGMA_EGRESS_STREAM, which is what switches the streaming
# half on; updfetch's build line is UNCHANGED and its object is byte-identical
# (D-2 — t/updstage_helper_shape.t pins both lines).
UPDSTAGE_SRC=	daemon/sysd/updstage_helper.c \
		daemon/common/egress_url.c daemon/common/egress_tls.c \
		daemon/common/egress_http.c daemon/common/egress_gunzip.c
UPDSTAGE_LIBS=	-ltls -lssl -lcrypto -lz

HEALTHD_SRC=	daemon/healthd/main.c daemon/common/daemon_base.c daemon/healthd/peer.c daemon/healthd/dispatch.c \
		daemon/healthd/audit.c daemon/healthd/health.c daemon/common/routes_canonical.c \
		daemon/common/system_canonical.c daemon/common/fsck_state.c \
		daemon/sysd/edition.c

ARPD_SRC=	daemon/arpd/main.c daemon/common/daemon_base.c daemon/arpd/peer.c daemon/arpd/dispatch.c \
		daemon/arpd/audit.c daemon/arpd/arp.c daemon/arpd/ndp.c daemon/arpd/backup_apply.c \
		daemon/common/arp_canonical.c daemon/common/backup_validate_arp.c \
		daemon/common/boot_drift.c \
		daemon/common/diag_view.c daemon/common/routes_canonical.c

LOGD_SRC=	daemon/logd/main.c daemon/common/daemon_base.c daemon/logd/peer.c daemon/logd/dispatch.c \
		daemon/logd/audit.c daemon/logd/logread.c daemon/common/routes_canonical.c

# Phase 9.R1 gwmond — the socket-LESS multi-WAN liveness probe. NO daemon_base.c
# (no client socket/peer/dispatch): main.c #includes probe.c and runs a forever
# probe loop. gateways_canonical.c for the monitor load + gw-state write +
# pool/quorum logic; routes_canonical.c is the COMMON link dep.
GWMOND_SRC=	daemon/gwmond/main.c \
		daemon/common/gateways_canonical.c \
		daemon/common/routes_canonical.c

# Phase NTP timed (NTP client/server + timezone + log-ts mode). Mirrors
# DNSD_SRC; keep routes_canonical.c (a COMMON object — routes_compose/
# json_routes — references its symbols; omitting it is a link failure).
TIMED_SRC=	daemon/timed/main.c daemon/common/daemon_base.c daemon/timed/peer.c daemon/timed/dispatch.c \
		daemon/timed/apply.c daemon/timed/ntpd.c daemon/timed/probe.c \
		daemon/timed/expand.c daemon/timed/backup_apply.c daemon/timed/audit.c \
		daemon/common/time_canonical.c daemon/common/json_time.c \
		daemon/common/backup_validate_time.c \
		daemon/common/boot_drift.c \
		daemon/common/routes_canonical.c

# Phase 9.O4 sysd (system control: reboot/service/identity/update). Minimal
# daemon (the LOGD_SRC shape): no canonical YAML, no expansion — just the base
# loop + the sysd handlers + its fixed-argv exec helpers. routes_canonical.c is
# the COMMON link dep (routes_compose/json_routes reference its symbols).
SYSD_SRC=	daemon/sysd/main.c daemon/common/daemon_base.c daemon/sysd/peer.c \
		daemon/sysd/dispatch.c daemon/sysd/audit.c daemon/sysd/sysd_exec.c \
		daemon/sysd/identity.c daemon/sysd/services.c daemon/sysd/power.c \
		daemon/sysd/power_pending.c \
		daemon/common/service_table.c \
		daemon/sysd/version.c daemon/sysd/host_facts.c \
		daemon/sysd/update.c daemon/common/update_check.c \
		daemon/sysd/edition.c daemon/sysd/stage.c daemon/sysd/stage_record.c \
		daemon/sysd/image_boot.c daemon/sysd/cfg_persist.c \
		daemon/sysd/fsck.c daemon/common/system_canonical.c \
		daemon/common/fsck_state.c \
		daemon/common/routes_canonical.c \
		daemon/sysd/cert.c daemon/sysd/acme.c daemon/sysd/cert_pending.c \
		daemon/sysd/backup_apply.c daemon/common/confirm_txn.c \
		daemon/common/boot_drift.c \
		daemon/common/backup_validate_cert.c \
		daemon/common/identity_canonical.c \
		daemon/common/backup_validate_identity.c

# Phase 9.O5 alertd (remote syslog + email alerting). A NEW daemon owning BOTH
# the /etc/syslog.conf managed-block writer/restart AND the alert tick/notify
# engine (timed config-writer + healthd metric-exec + diagd egress-via-exec
# models). routes_canonical.c is the COMMON link dep (routes_compose/json_routes
# reference its symbols). The two new canonical fragments (remotelog/alerts) and
# their backup validators link here.
ALERTD_SRC=	daemon/alertd/main.c daemon/common/daemon_base.c daemon/alertd/peer.c \
		daemon/alertd/dispatch.c daemon/alertd/apply.c daemon/alertd/syslogd.c \
		daemon/alertd/metrics.c daemon/alertd/capture.c \
		daemon/alertd/cfgdrift_hyst.c \
		daemon/alertd/df_parse.c daemon/alertd/notify.c \
		daemon/alertd/backup_apply.c daemon/alertd/audit.c \
		daemon/common/remotelog_canonical.c daemon/common/alerts_canonical.c \
		daemon/common/backup_validate_remotelog.c \
		daemon/common/backup_validate_alerts.c \
		daemon/common/routes_canonical.c \
		daemon/common/gateways_canonical.c \
		daemon/common/time_canonical.c \
		daemon/common/boot_drift.c \
		daemon/common/mfa_record.c \
		daemon/common/fsck_state.c \
		daemon/logd/logread.c daemon/logd/audit.c

# Phase 7b.3.2c added the commit-confirmed window; 7b.3.2d adds the gated live
# apply (gate_inputs.c gathers the live D6 inputs; apply.c set_ipsec). route.c
# gives ogma_routes_reapply_canonical_live + the gateway; canonical.c+hostname.c
# +ifconfig.c (the dnsd/dhcpd precedent, OGMA_HAVE_IFCONFIG) give the canonical
# interface subnets for the mgmt-CIDR set (load-only — ipsecd never execs
# ifconfig). routes_canonical.c is the COMMON link dep.
IPSECD_SRC=	daemon/ipsecd/main.c daemon/common/daemon_base.c daemon/ipsecd/peer.c \
		daemon/ipsecd/dispatch.c daemon/ipsecd/apply.c daemon/ipsecd/iked.c \
		daemon/ipsecd/audit.c daemon/ipsecd/ipsec_pending.c \
		daemon/ipsecd/gate_inputs.c daemon/ipsecd/apply_set.c daemon/ipsecd/pki.c \
		daemon/ipsecd/reload_additions.c \
		daemon/ipsecd/backup_apply.c \
		daemon/common/ipsec_canonical.c daemon/common/ipsec_txn.c \
		daemon/common/ipsec_gate.c daemon/common/backup_validate_ipsec.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/boot_drift.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/routes_canonical.c daemon/rtd/route.c daemon/common/confirm_txn.c
IPSECD_CFLAGS+=	-DOGMA_HAVE_IFCONFIG

CFLAGS+=	-Wall -Wextra -Werror -Wswitch-enum -O2 -I daemon/common -I/usr/local/include
# 10.D4 VD-D4-3: sanitize-sentinel hook. Empty by default; the OpenBSD
# authoritative `make test` leg passes OGMA_SANITIZE_CFLAGS=-DOGMA_SANITIZED_BUILD
# to extend the S1b exec-poison (OGMA_SYSCTL/OGMA_RCCTL -> /nonexistent) to the
# real-userland leg, where those tools EXIST and MUTATE. Scoped to `make test`
# ONLY: a separate `make all` invocation gets no macro, so production daemons
# build with the real tool paths. Inert on the Ubuntu leg (its command-line
# CFLAGS= override suppresses every in-Makefile CFLAGS+=; the sentinel already
# lives in PORTABLE_CFLAGS there). Never `export` this macro (it would leak into
# a chained `make all`); pass it only as a `make test` command-line macro.
OGMA_SANITIZE_CFLAGS ?=
CFLAGS+=	${OGMA_SANITIZE_CFLAGS}
AUTHD_CFLAGS+=	-Wno-deprecated-declarations -DOGMA_HAVE_IFCONFIG
NETD_LDFLAGS=
NETD_CFLAGS+=	-DOGMA_NETD_BUILD -DOGMA_HAVE_IFCONFIG
AUTHD_LDFLAGS=	-L/usr/local/lib -lsqlite3 -lpthread -lcrypto -lutil
# Extra libs for test binaries. Empty on OpenBSD (sha2 is in libc);
# Linux CI passes LDLIBS=-lmd (libmd provides <sha2.h>).
LDLIBS?=

# Generated git build-stamp consumed only by healthd (daemon/healthd/health.c
# #includes it) and surfaced on the System-health card as OGMA_BUILD_REV.
# Regenerated every build via the FRC force-prerequisite, but only REWRITTEN when
# the value changes (cmp guard), so the mtime moves — and healthd/test-health
# relink — only when the rev actually moves. Fail-OPEN precedence so neither a
# missing `git` binary (OpenBSD CI VM) nor a missing stamp file (router tarball
# build is git-less) can ever fail the recipe:
#   git describe  ->  cat build_rev.dist (deploy.py-injected)  ->  "unknown".
# `tr -cd` strips CR/LF and any stray byte, constraining the stamp to a safe
# charset. POSIX sh; portable across OpenBSD bmake and GNU make.
BUILD_REV_H=	daemon/common/build_rev.h

# OGMA_BUILD_DATE rides the same rule (10.E6 M-repair item 6). There was NO
# build date anywhere in the tree, so the one on-box provenance fact an age
# signal could use was absent precisely on the shipped SKU -- and since the
# release package is built in a git-less VM, OGMA_BUILD_REV stamped "unknown"
# there too. Both now read a .dist file the release workflow writes on the host
# (which has git and knows the tag), so the shipped artifact carries a real
# stamp. The date is the TAG's date, never `date(1)` at build time: a wall-clock
# stamp would differ on every rebuild of identical sources, and R4 already
# concedes enough on reproducibility without adding a fresh source of drift.
# Same fail-OPEN precedence and cmp guard as the rev: a missing file yields
# "unknown", never a failed recipe.
.PHONY: FRC
${BUILD_REV_H}: FRC
	@rev=`git describe --always --dirty --abbrev=10 2>/dev/null || cat daemon/common/build_rev.dist 2>/dev/null || echo unknown`; \
	rev=`printf '%s' "$$rev" | tr -cd '0-9A-Za-z._-'`; \
	[ -n "$$rev" ] || rev=unknown; \
	bdate=`cat daemon/common/build_date.dist 2>/dev/null || echo unknown`; \
	bdate=`printf '%s' "$$bdate" | tr -cd '0-9A-Za-z:.+-'`; \
	[ -n "$$bdate" ] || bdate=unknown; \
	{ printf '#define OGMA_BUILD_REV "%s"\n' "$$rev"; \
	  printf '#define OGMA_BUILD_DATE "%s"\n' "$$bdate"; } > $@.tmp; \
	if cmp -s $@.tmp $@ 2>/dev/null; then rm -f $@.tmp; else mv $@.tmp $@; fi
FRC:

all: ${PROG_NETD} ${PROG_AUTHD} ${PROG_RTD} ${PROG_PFD} ${PROG_DNSD} ${PROG_DHCPD} ${PROG_ROUTED} ${PROG_DIAGD} ${PROG_DIAGSWEEP} ${PROG_WEBHOOK} ${PROG_HASYNC} ${PROG_UPDFETCH} ${PROG_UPDSTAGE} ${PROG_HEALTHD} ${PROG_ARPD} ${PROG_LOGD} ${PROG_TIMED} ${PROG_IPSECD} ${PROG_SYSD} ${PROG_ALERTD} ${PROG_GWMOND} ${PROG_CTL}

${PROG_NETD}: ${COMMON} ${NETD_SRC}
	${CC} ${CFLAGS} ${NETD_CFLAGS} -o $@ ${COMMON} ${NETD_SRC} ${NETD_LDFLAGS}

${PROG_AUTHD}: ${COMMON} ${AUTHD_SRC}
	${CC} ${CFLAGS} ${AUTHD_CFLAGS} -o $@ ${COMMON} ${AUTHD_SRC} ${AUTHD_LDFLAGS}

${PROG_RTD}: ${COMMON} ${RTD_SRC}
	${CC} ${CFLAGS} ${RTD_CFLAGS} -o $@ ${COMMON} ${RTD_SRC}

${PROG_PFD}: ${COMMON} ${PFD_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${PFD_SRC}

${PROG_DNSD}: ${COMMON} ${DNSD_SRC}
	${CC} ${CFLAGS} ${DNSD_CFLAGS} -o $@ ${COMMON} ${DNSD_SRC}

${PROG_DHCPD}: ${COMMON} ${DHCPD_SRC}
	${CC} ${CFLAGS} ${DHCPD_CFLAGS} -o $@ ${COMMON} ${DHCPD_SRC}

${PROG_ROUTED}: ${COMMON} ${ROUTED_SRC}
	${CC} ${CFLAGS} ${ROUTED_CFLAGS} -o $@ ${COMMON} ${ROUTED_SRC}

${PROG_DIAGD}: ${COMMON} ${DIAGD_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${DIAGD_SRC}

${PROG_DIAGSWEEP}: ${COMMON} ${DIAGSWEEP_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${DIAGSWEEP_SRC} ${LDLIBS}

${PROG_WEBHOOK}: ${WEBHOOK_SRC}
	${CC} ${CFLAGS} -o $@ ${WEBHOOK_SRC} ${WEBHOOK_LIBS}

${PROG_HASYNC}: ${HASYNC_SRC}
	${CC} ${CFLAGS} -o $@ ${HASYNC_SRC} ${HASYNC_LIBS}

${PROG_UPDFETCH}: ${UPDFETCH_SRC}
	${CC} ${CFLAGS} -o $@ ${UPDFETCH_SRC} ${UPDFETCH_LIBS}

${PROG_UPDSTAGE}: ${UPDSTAGE_SRC}
	${CC} ${CFLAGS} -DOGMA_EGRESS_STREAM -o $@ ${UPDSTAGE_SRC} ${UPDSTAGE_LIBS}

${PROG_HEALTHD}: ${BUILD_REV_H} ${COMMON} ${HEALTHD_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${HEALTHD_SRC}

${PROG_ARPD}: ${COMMON} ${ARPD_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${ARPD_SRC}

${PROG_LOGD}: ${COMMON} ${LOGD_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${LOGD_SRC}

${PROG_GWMOND}: ${COMMON} ${GWMOND_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${GWMOND_SRC} ${LDLIBS}

${PROG_TIMED}: ${COMMON} ${TIMED_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${TIMED_SRC}

${PROG_IPSECD}: ${COMMON} ${IPSECD_SRC}
	${CC} ${CFLAGS} ${IPSECD_CFLAGS} -o $@ ${COMMON} ${IPSECD_SRC}

# 10.E6 S3: build_rev.h is a prerequisite because update.c #includes it (the
# healthd shape) — OGMA_BUILD_DATE is what the build-age line renders.
${PROG_SYSD}: ${BUILD_REV_H} ${COMMON} ${SYSD_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${SYSD_SRC}

${PROG_ALERTD}: ${COMMON} ${ALERTD_SRC}
	${CC} ${CFLAGS} -o $@ ${COMMON} ${ALERTD_SRC}

${PROG_CTL}: scripts/ogmaprotectctl.pl daemon/common/ogmaprotect.h scripts/drift_required.txt
	v=$$(sed -n 's/^#define OGMAPROTECT_VERSION[[:space:]]*"\(.*\)"/\1/p' \
	    daemon/common/ogmaprotect.h); \
	test -s scripts/drift_required.txt; \
	req=$$(cat scripts/drift_required.txt); \
	sed -e "s/@OGMA_VERSION@/$$v/" -e "s|@OGMA_DRIFT_REQUIRED@|$$req|" \
	    scripts/ogmaprotectctl.pl > ${PROG_CTL}
	chmod +x ${PROG_CTL}

# Versioned release tarball of the tracked tree (requires git).
# 10.D5: surface WHEN the live-apply suite was last run, and against which
# build. ADVISORY ONLY -- the `-` prefix and `|| true` keep it from ever failing
# a build. The suite cannot run in CI (no lab reach, contract Sec.2.6), so
# nothing automatic would otherwise notice it had gone unrun -- and the 10.D5
# campaign is exactly what that looks like. Blocking a build on a 3-hour manual
# suite would only teach people to bypass it, so this informs at the moment a
# build is cut, then gets out of the way.
live-verify-status:
	@-python3 scripts/check_live_verification.py 2>/dev/null || true

# `git archive` APPLIES core.autocrlf. This checkout sets it true (and any
# Windows clone will), so a bare `git archive HEAD` ships the release tarball
# with CRLF in every text file that carries no explicit `eol=lf` pin -- which is
# most of the tree, the daemon C sources included. Measured 2026-08-24 on this
# checkout: daemon/netd/address_pending.c came out of a bare archive with 1105
# CR bytes, and 0 once the two -c flags below were passed. .gitattributes pins a
# dozen file classes LF, each after a measured incident, but every one of those
# pins protects a WORKING-TREE upload (deploy.py sends working-tree bytes); the
# release tarball is a separate path and no pin reaches it. The flags are the
# fix; the scan below is the tooth, because a flag nobody checks is a convention
# and this target runs rarely enough that a silent regression would ship.
dist: live-verify-status
	v=$$(sed -n 's/^#define OGMAPROTECT_VERSION[[:space:]]*"\(.*\)"/\1/p' \
	    daemon/common/ogmaprotect.h); \
	git -c core.autocrlf=false -c core.eol=lf archive \
	    --format=tar.gz --prefix=ogmaprotect-$$v/ \
	    -o ogmaprotect-$$v.tar.gz HEAD; \
	tmp=$$(mktemp -d) || exit 1; \
	gzip -dc ogmaprotect-$$v.tar.gz | tar -xf - -C $$tmp || \
	    { rm -rf $$tmp; exit 1; }; \
	cr=$$(printf '\r'); \
	bad=$$(find $$tmp -type f \( -name '*.c' -o -name '*.h' -o -name '*.pl' \
	    -o -name '*.pm' -o -name '*.py' -o -name '*.sh' -o -name '*.t' \
	    -o -name '*.html' -o -name '*.css' -o -name '*.js' -o -name '*.md' \
	    -o -name '*.conf' -o -name '*.txt' -o -name '*.yaml' -o -name '*.json' \
	    -o -name '*.inc' -o -name '*.gen' -o -name 'Makefile' \) \
	    -exec grep -l -- "$$cr" {} + 2>/dev/null || true); \
	rm -rf $$tmp; \
	if [ -n "$$bad" ]; then \
	    echo "dist: CR bytes in the release tarball -- refusing to ship it."; \
	    echo "$$bad" | sed -e "s|^$$tmp/ogmaprotect-$$v/||"; \
	    rm -f ogmaprotect-$$v.tar.gz; \
	    exit 1; \
	fi; \
	echo "ogmaprotect-$$v.tar.gz"

install: all
	${INSTALL} -d -m 755 ${DESTDIR}${BINDIR}
	${INSTALL} -m 755 ${PROG_NETD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_AUTHD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_RTD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_PFD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_DNSD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_DHCPD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_ROUTED} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_DIAGD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_DIAGSWEEP} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_WEBHOOK} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_HASYNC} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_UPDFETCH} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_UPDSTAGE} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_HEALTHD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_ARPD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_LOGD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_TIMED} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_IPSECD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_SYSD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_ALERTD} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_GWMOND} ${DESTDIR}${BINDIR}/
	${INSTALL} -m 755 ${PROG_CTL} ${DESTDIR}${BINDIR}/
	# 10.E1: the firstboot/convergence provisioner (POSIX sh). A non-daemon sbin helper
	# (allow-listed in scripts/check-daemon-inventory.sh); repo file is 0644, install 0755.
	${INSTALL} -m 755 scripts/ogmaprotect-setup ${DESTDIR}${BINDIR}/
	# 10.E5 (L6-07): the ogmaprotectctl(8) man page -- the consolidated CLI
	# reference. Source lives in man/, deliberately not under the docs tree
	# (t/restore_runbook.t forbids any install line sourced from there; the
	# docs tree never ships via make install). Needs the matching pkg/PLIST
	# `@man` row; t/ctl_man_page.t asserts both BY NAME and cross-checks the
	# page's COMMANDS against the CLI dispatch.
	${INSTALL} -d -m 755 ${DESTDIR}${MANDIR}/man8
	${INSTALL} -m 644 man/ogmaprotectctl.8 ${DESTDIR}${MANDIR}/man8/
	${INSTALL} -d -m 755 ${DESTDIR}${WEBDIR}/cgi
	${INSTALL} -d -m 755 ${DESTDIR}${WEBDIR}/lib/OgmaProtect
	${INSTALL} -d -m 755 ${DESTDIR}${WEBDIR}/lib/OgmaProtect/Handlers
	${INSTALL} -d -m 755 ${DESTDIR}${WEBDIR}/lib/OgmaProtect/Template
	${INSTALL} -m 755 web/cgi/ogmaprotect.pl ${DESTDIR}${WEBDIR}/cgi/
	${INSTALL} -m 644 web/lib/OgmaProtect/*.pm ${DESTDIR}${WEBDIR}/lib/OgmaProtect/
	${INSTALL} -m 644 web/lib/OgmaProtect/Handlers/*.pm ${DESTDIR}${WEBDIR}/lib/OgmaProtect/Handlers/
	${INSTALL} -m 644 web/lib/OgmaProtect/Template/*.pm ${DESTDIR}${WEBDIR}/lib/OgmaProtect/Template/
	${INSTALL} -d -m 755 ${DESTDIR}${WEBDIR}/templates
	${INSTALL} -m 644 web/templates/* ${DESTDIR}${WEBDIR}/templates/
	${INSTALL} -d -m 755 ${DESTDIR}${WEBDIR}/static
	# 10.F5 S1: EXTENSION-ENUMERATED, never a bare `web/static/*` glob.
	# httpd serves this directory UNAUTHENTICATED and ahead of the CGI
	# (etc/httpd.conf:47-50), so a bare glob auto-ships whatever lands in the
	# source dir -- and `install` ABORTS on a directory source ("Inappropriate
	# file type or format" on OpenBSD; "omitting directory" on GNU), which would
	# silently skip every install line below this one. Both were measured before
	# this line changed. Adding a new asset TYPE here is a deliberate, reviewed
	# act; t/js_install_hygiene.t holds the shape both directions.
	${INSTALL} -m 644 web/static/*.css web/static/*.js ${DESTDIR}${WEBDIR}/static/
	${INSTALL} -d -m 755 ${DESTDIR}${ETCDIR}
	${INSTALL} -m 644 etc/ogmaprotect.conf ${DESTDIR}${ETCDIR}/
	# 10.C6 S7b: the drift-domain manifest that DRIVES ogmaprotectctl status (M11).
	# 0644 root:wheel in the root-owned ETCDIR — no non-root write to the file or
	# its parent; the CLI loads it fail-closed (missing/corrupt => exit 2).
	${INSTALL} -m 644 scripts/drift_registry.json ${DESTDIR}${ETCDIR}/
	# License + Required Notice must travel with every packaged install
	# (PolyForm Small Business 1.0.0 §Notices). Referenced by pkg/MESSAGE+DESCR.
	${INSTALL} -m 644 license ${DESTDIR}${ETCDIR}/LICENSE
	${INSTALL} -m 644 NOTICE ${DESTDIR}${ETCDIR}/
	# 10.E6 / VD-E6-22: the build's OpenBSD release, stated in the package.
	# A cross-release package installs clean, VERIFIES clean, and then every
	# daemon dies with `can't load library` -- @arch pins the CPU, not the OS
	# release, so nothing else in the artifact carries this fact. Generated
	# here (never hand-kept), and contrib/openbsd-port/make-package.sh COPIES
	# this exact file out as the release-directory sidecar, so the in-package
	# copy and the signed one cannot disagree.
	sh scripts/gen-release-info.sh > ${DESTDIR}${ETCDIR}/RELEASE-INFO
	chmod 644 ${DESTDIR}${ETCDIR}/RELEASE-INFO
	# 10.E6 S3 (§7 D10b / K-flat-carry): the package-shipped copy of the release
	# signing pubkey(s) — the trust root for the on-box update-manifest verifier
	# (sysd execs signify -V against it). A PLAIN row (never @sample: it must be
	# replaced verbatim on upgrade), 0644 root:wheel in the root-owned ETCDIR.
	# NOT the /etc/signify/ anchor the customer installs for pkg_add — that stays
	# an out-of-band step (docs/INSTALL.md step 2); this copy is trusted because it
	# arrived inside a pkg_add-verified package. Named by the D4a convention
	# (ogmaprotect-<series>-pkg.pub); the NEXT series' key joins this line the
	# release before it is first used (docs/RELEASE.md, "The update manifest").
	${INSTALL} -d -m 755 ${DESTDIR}${ETCDIR}/signify
	${INSTALL} -m 644 etc/signify/ogmaprotect-05-pkg.pub ${DESTDIR}${ETCDIR}/signify/
	${INSTALL} -d -m 750 ${DESTDIR}${DBDIR}
	${INSTALL} -d -m 750 ${DESTDIR}${DBDIR}/config
	${INSTALL} -d -m 750 ${DESTDIR}${DBDIR}/revisions
	# 10.B9 (L4-06/VD-B7-3): per-fragment-key revision subdirs, each unveiled rwc by
	# ONLY its owning daemon (dot-named so the flat rev-id lister never sees them).
	# Keep in step with revision_domain_keys[] (daemon/common/revision.c) —
	# .sysctl/.identity joined in 10.E2 S3 (E2M-5) for the converge mint.
	for k in .net .pf .routes .gateways .dns .dhcp .arp .time .ipsec .cert .routing .remotelog .alerts .sysctl .identity; do \
		${INSTALL} -d -m 750 ${DESTDIR}${DBDIR}/revisions/$$k; \
	done
	${INSTALL} -d -m 750 ${DESTDIR}${DBDIR}/staging
	${INSTALL} -d -m 750 ${DESTDIR}${DBDIR}/staging/exports
	${INSTALL} -d -m 700 ${DESTDIR}${DBDIR}/secrets
	${INSTALL} -d -m 700 ${DESTDIR}${DBDIR}/secrets/.alerts	# 10.B8: alertd-only webhook-bearer family subdir (L4-06/VD-B7-2)
	${INSTALL} -d -m 700 ${DESTDIR}${DBDIR}/capkeys	# I-02 per-daemon verify subkeys
	${INSTALL} -d -m 700 ${DESTDIR}${DBDIR}/update	# 10.E6 S3: sysd-only update-signal leaf (D10b; also mkdir'd by sysd pre-sandbox for upgraded boxes)
	${INSTALL} -d -o root -g www -m 770 ${DESTDIR}${DBDIR}/staging/uploads
	# 10.E3 S3 (Gate-0 §3 M9): root:www 0770, matching the uploads line below.
	# These shipped -m 755 with no group, so a root mkdir left them owned by
	# wheel and the _www CGI could neither read a 0640 mirror nor unlink one.
	# The PARENT stays 755 root:wheel. _www needs only `x` here to reach
	# exports/, and granting group www `w` on this directory would let the
	# CGI rename a leaf aside and plant a symlink for root to follow.
	${INSTALL} -d -m 755 ${DESTDIR}/var/www/staging
	${INSTALL} -d -o root -g www -m 770 ${DESTDIR}/var/www/staging/exports
	${INSTALL} -d -o root -g www -m 770 ${DESTDIR}/var/www/staging/uploads
	${INSTALL} -d -m 1777 ${DESTDIR}/var/www/tmp
	${INSTALL} -d -o root -g www -m 770 ${DESTDIR}${RUN_WWW}
	${INSTALL} -d -m 755 ${DESTDIR}/var/log
	${INSTALL} -d -m 755 ${DESTDIR}/etc/rc.d
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_netd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_authd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_rtd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_pfd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_dnsd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_dhcpd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_routed ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_diagd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_healthd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_arpd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_logd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_timed ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_ipsecd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_sysd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_alertd ${DESTDIR}/etc/rc.d/
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_gwmond ${DESTDIR}/etc/rc.d/
	# 10.E1 S2: the firstboot ONE-SHOT (runs ogmaprotect-setup at boot then exits).
	# Enabled LAST by the operator (pkg MESSAGE) / the appliance image. NOT a daemon,
	# so allow-listed in scripts/check-daemon-inventory.sh (RC_HELPERS).
	${INSTALL} -m 755 etc/rc.d/ogmaprotect_setup ${DESTDIR}/etc/rc.d/
	${INSTALL} -d -m 755 ${DESTDIR}/etc/examples/ogmaprotect
	${INSTALL} -m 644 etc/httpd.conf.snippet ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 644 etc/slowcgi.conf.example ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 644 etc/README.httpd ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 644 etc/README.authdb ${DESTDIR}/etc/examples/ogmaprotect/
	# 10.E8 S3 (Gate-0 §7 D13a): the root-local break-glass runbook. It ships
	# HERE, not under docs/, because docs/PHASE*.md is export-ignore'd and is
	# therefore NOT on the box -- which is the whole finding (§0.11). Any file
	# added here needs a matching pkg/PLIST row; t/recovery_runbook.t asserts
	# that set equality in both directions.
	${INSTALL} -m 644 etc/README.recovery ${DESTDIR}/etc/examples/ogmaprotect/
	# 10.E4 S2 (Gate-0 §7 D11): the rebuild/restore runbook -- the DISASTER
	# sibling of README.recovery, which is the working-box break-glass plane.
	# Ships here for the same reason: docs/RECOVERY.md is the audit-named tree
	# artifact and reaches the source tarball, but `make install` ships no
	# docs/, so only this copy is on a pkg_add box. Needs a matching pkg/PLIST
	# row; t/restore_runbook.t asserts BOTH rows by name, because the
	# t/recovery_runbook.t set-equality check passes vacuously when a file is
	# absent from both lists.
	${INSTALL} -m 644 etc/README.restore ${DESTDIR}/etc/examples/ogmaprotect/
	# VD-E3-21: the 10.E3 support triage runbook, readable where the bundle
	# is. Ships HERE for the same reason as its two siblings above: the
	# source-tree master (docs/PHASE10-E3-SUPPORT-TRIAGE-RUNBOOK.md) is
	# export-ignore'd and `make install` ships no docs/. Needs a matching
	# pkg/PLIST row; t/support_runbook.t asserts BOTH rows by name AND that
	# the copy is byte-identical to the master from its first "---" onward.
	${INSTALL} -m 644 etc/README.support ${DESTDIR}/etc/examples/ogmaprotect/
	# 10.E6 AP0 (Gate-0 §7 D-A10): the appliance unit's QUICK-START -- the
	# file the factory bench checklist (docs/FACTORY-BENCH-CHECKLIST.md) greps
	# for the PKG_PATH line before a unit is boxed, and the one a customer reads
	# to verify (not redo) the pre-installed anchor and to upgrade by name. On
	# the box for the same reason as its three siblings: `make install` ships no
	# docs/. Needs a matching pkg/PLIST row; t/factory_bench_checklist.t asserts
	# BOTH rows by name.
	${INSTALL} -m 644 etc/README.appliance ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 644 etc/newsyslog.conf.ogmaprotect \
		${DESTDIR}/etc/examples/ogmaprotect/
	# 10.E1 S2: the live httpd.conf template the provisioner installs to /etc/httpd.conf
	# (so no OGMA_SRC_DIR is needed on a pkg_add box), the fail-closed firstboot pf
	# bootstrap it loads before httpd binds, and the appliance rc.firsttime example
	# (the Lite SKU enables the rc.d one-shot instead).
	${INSTALL} -m 644 etc/httpd.conf ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 644 etc/pf.boot.conf ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 644 etc/rc.firsttime.sample ${DESTDIR}/etc/examples/ogmaprotect/
	# 10.E9 S1 (PHASE-BOOT-FSCK-RESILIENCE.md §2.0/§3): the fsck auto-repair
	# /etc/rc patch, shipped INERT beside the other examples (the operator
	# applies it with ogma-fsck-rc-apply — /etc/rc is base-OS territory,
	# never written by the package), and the helper the patched rc sources
	# from the package's own root-owned directory. Needs matching pkg/PLIST
	# rows; t/fsck_rc_patch.t asserts all three by name.
	${INSTALL} -m 644 etc/rc.fsck.patch ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 644 etc/rc.fsck.subr ${DESTDIR}/etc/ogmaprotect/
	${INSTALL} -m 755 scripts/ogma-fsck-rc-apply.sh ${DESTDIR}${BINDIR}/ogma-fsck-rc-apply
	# 10.E9 S2 (PHASE-BOOT-FSCK-RESILIENCE.md §2.4, Track B): the appliance
	# IMAGE edition's /etc/rc hooks -- the helper the image-patched rc sources
	# (root-owned, same directory + rule as rc.fsck.subr; inert on the
	# software edition: every entry point tests the image marker), the patch
	# itself (reference copy; contrib/resflash/build.sh applies it at image
	# build, never on a running box) and the Class-B /cfg save-back. The
	# save-list it reads (/etc/ogmaprotect/cfg-savelist) is NOT installed
	# here: the image build generates it from scripts/ogma-savelist.conf
	# through the 10.E9 S2a guard's own projection (check_durable_paths.pl
	# --list all), and the manifest stays in the tree -- an installed copy
	# would be a shipped file that guard counts as naming every path in it.
	# Matching pkg/PLIST rows; t/image_rc_patch.t asserts all three by name.
	${INSTALL} -m 644 etc/rc.image.subr ${DESTDIR}/etc/ogmaprotect/
	${INSTALL} -m 644 etc/rc.image.patch ${DESTDIR}/etc/examples/ogmaprotect/
	${INSTALL} -m 755 scripts/ogma-cfg-save.sh ${DESTDIR}${BINDIR}/ogma-cfg-save
	# 10.E9 S2 part 2 (Appliance D-A7): the boot-ok writer sysd execs once the
	# management plane is up (resets the A/B counter on /mbr) and the A/B
	# root apply (the post-write half 10.E10 S2 calls; finish / rollback /
	# inactive). Both root-only, both inert on the software edition. Matching
	# pkg/PLIST rows; t/image_rc_patch.t asserts both by name.
	${INSTALL} -m 755 scripts/ogma-image-bootok.sh ${DESTDIR}${BINDIR}/ogma-image-bootok
	${INSTALL} -m 755 scripts/ogma-image-apply.sh ${DESTDIR}${BINDIR}/ogma-image-apply
	# The daemon-inventory projection, on the box so `ogmaprotect-setup bootstrap`
	# reads the boot ORDER from the same C table every other surface is reconciled
	# against (10.C9), instead of carrying a hand-kept copy of it. Regenerated by
	# `make gen-inventory`; `make check-daemon-inventory` fails if it drifts.
	${INSTALL} -m 644 scripts/daemon_inventory.gen \
		${DESTDIR}/etc/examples/ogmaprotect/daemon_inventory
	# Ensure the base-system /etc/mygate exists, but ONLY on a real install
	# (empty DESTDIR). A staged/fake install (DESTDIR set, e.g. packaging) must
	# never create this base file, or it would be swept into the package and
	# pkg_delete would remove the host's default-gateway file.
	@test -n "${DESTDIR}" || test -f /etc/mygate || ${INSTALL} -m 644 /dev/null /etc/mygate

# clean removes generated build_rev.h (+ its .tmp) but NOT build_rev.dist: that is
# a deploy-injected build INPUT on the git-less router, not an artifact — deleting
# it before `make` (cmd_install runs `make clean; make`) would rebuild the stamp
# as "unknown".
clean:
	rm -f ${PROG_NETD} ${PROG_AUTHD} ${PROG_RTD} ${PROG_PFD} ${PROG_DNSD} ${PROG_DHCPD} ${PROG_ROUTED} ${PROG_DIAGD} ${PROG_DIAGSWEEP} ${PROG_WEBHOOK} ${PROG_HASYNC} ${PROG_UPDFETCH} ${PROG_UPDSTAGE} ${PROG_HEALTHD} ${PROG_ARPD} ${PROG_LOGD} ${PROG_TIMED} ${PROG_IPSECD} ${PROG_SYSD} ${PROG_ALERTD} ${PROG_GWMOND} ${PROG_CTL} \
		test_ipsec_canonical test_ipsec_render_bound test_ipsec_ikev1 test_ipsec_gate test_ipsec_txn test_ipsec_pending test_ipsec_apply \
		test_ipsec_pki test_authd_ipsec_pki test_ipsec_pf \
		test_hostname test_order_repair test_proto test_op_crossing test_dhcp test_rbac test_peer test_revision test_config_manifest test_schema_state test_schema_converge test_time test_cert_canonical test_sysctl_canonical \
		test_backup test_backup_coordinator test_restore_journal test_restore_daemons test_routes test_rtd test_pf \
		test_carp_pending test_v6_confirm test_pf_pending test_wg_pending test_wg_apply test_netd_delete test_wg_drift test_tunnel_drift test_iface_stats test_bridge_stats test_dns test_dnsd test_dns_pending test_dhcp_server test_dhcpd_daemon test_dhcpd_apply test_dhcp6leased test_rad \
		test_routing test_routing_status test_routing_capture test_routing_reload fake_ctl test_diag_view test_pf_states test_secrets test_authd_secrets test_authd_frontdoor test_authd_jobs test_fuzz \
		test_yaml_lite test_ops test_window_gate gen_rate_golden gen_rate_golden.out gen_class_golden gen_class_golden.out gen_req_golden gen_req_golden.out daemon/test/req_wire_golden.inc.new test_daemon_inventory gen_daemon_inventory gen_daemon_inventory.out test_proxy_transport test_daemon_base test_req_fields test_req_emit test_idempotency test_wg_derive test_authd_wg test_authd_wg_apply test_authd_pppoe test_authd_migrate test_authd_routing_binding test_authd_ipsec_binding test_authd_archive_job test_authd_support_walk test_auth_db test_auth_db_resilience test_diag test_health test_arp test_log_lines test_pflog fuzz_repro.txt \
		test_gateways test_gwmon_probe test_supervision test_frame test_lateral test_alertd test_alertd_apply test_timed_apply test_hasync \
		test_update_check test_egress \
		fuzz_crash.txt fuzz_yaml_lf test_fuzz.core \
		fuzz_untrusted fuzz_arp_feed fuzz_untrusted_lf fuzz_arp_feed_lf \
		fuzz_untrusted_crash.bin fuzz_arp_feed_crash.bin \
		fuzz.dict seed-dump.log dict-dump.log lf.log merge.log \
		${BUILD_REV_H} ${BUILD_REV_H}.tmp
	# 10.D2 S1 (H3): fuzz_corpus/ is a DIRECTORY — it cannot join the single
	# `rm -f` above, whose -f suppresses only ENOENT, so a directory operand
	# would error and fail `make clean`. 10.D2 S3 adds three more directories
	# for the same reason (the exploration lane's -artifact_prefix= target, its
	# OGMA_FUZZ_DUMP_DIR, and the -merge=1 destination).
	rm -rf fuzz_corpus fuzz_corpus_min lf_artifacts lf_dumps \
		corpus_fuzz_untrusted corpus_fuzz_arp_feed

test-dhcp:
	${CC} ${CFLAGS} -DTEST_DHCP -DOGMA_HAVE_IFCONFIG -o test_dhcp \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/revision.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_dhcp.c ${LDLIBS}
	./test_dhcp

# 9.R3.4 dhcp6leased.conf managed-block writer + PD canonical round-trip +
# validate_pd reject matrix + boundary-safe conflict detection. Same link
# closure as test-dhcp plus daemon/common/dhcp6.c (the writer under test).
test-dhcp6leased:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_dhcp6leased \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/revision.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/dhcp6.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_dhcp6leased.c ${LDLIBS}
	./test_dhcp6leased

# 9.R3.5 rad.conf managed-block writer (RA + RDNSS) + RA canonical round-trip +
# validate_ra reject matrix + ogma_v6_unicast_ok screen. Same closure as
# test-dhcp6leased (the D0 dnsd-listen oracle is unit-tested in test-dnsd, which
# already links dns_canonical.c + hostname.c).
test-rad:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_rad \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/revision.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/dhcp6.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_rad.c ${LDLIBS}
	./test_rad

# 9.R3.8 IPv6 operator-op integration: the web-handler/smoke v6_yaml fragment
# formats through the ops' unchecked-parse path, the reject matrix via
# parse->validate, and the graft slice-preservation invariant. Same closure as
# test-dhcp6leased/test-rad (pure logic; the ops' daemon dispatch is the lab smoke).
test-ipv6-ops:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_ipv6_ops \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/revision.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/dhcp6.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_ipv6_ops.c ${LDLIBS}
	./test_ipv6_ops

# Marker store/read runs against a per-uid scratch staging dir (see the
# test-pf-pending comment for why per-uid). OGMA_HOSTNAME_DIR is scratch too:
# the D7 rollback restore-propagation cases write/remove hostname.<if> copies.
# 10.D5 S1c: both are derived from OGMA_DHCP_TXN_TEST_ROOT so the test can
# rm_rf-at-head the whole root (state_reclaim.c) before its first mkdir.
test-authd-dhcp-txn:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_authd_dhcp_txn \
		-DOGMA_DHCP_TXN_TEST_ROOT='"/tmp/ogma-dhcp-txn-'`id -u`'"' \
		-DOGMA_STAGING_DIR='OGMA_DHCP_TXN_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_DHCP_TXN_TEST_ROOT "/hostname"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/routes_compose.c daemon/common/validate.c \
		daemon/common/util.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/dhcp_txn.c \
		daemon/common/dhcp.c daemon/common/dhcp_preflight.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c daemon/common/apply_lock.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/test/state_reclaim.c \
		daemon/test/test_authd_dhcp_txn.c ${LDLIBS}
	./test_authd_dhcp_txn

# 10.B3 D7 (12 M5 read side): the recovery DIRECTION decision in
# authd/dhcp_apply.c (committed => forward re-drive; else revert), driven
# through ogma_authd_dhcp_recover_stale with the proxy/audit layer stubbed
# (the test_authd_wg harness pattern). Convergence half runs on the lab.
# 10.C4 S2 additions in the same harness: the throttled-pass policy matrix
# (skip-while-job-live without advancing the throttle, 60 s cadence, the
# reap-forced immediate pass, the A4 squeeze toggle restored on every path,
# the degraded-orphan age floor) and the fork-and-collect DHCP job-worker
# block (descriptor round-trip -> stubbed saga -> spool bytes + exit code;
# job_dhcp.c references no jobs.c symbol, so the lean link stays lean).
test-authd-dhcp-recover:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_authd_dhcp_recover \
		-DOGMA_STAGING_DIR='"/tmp/ogma-dhcp-rec-'`id -u`'/staging"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/routes_compose.c daemon/common/validate.c \
		daemon/common/util.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/dhcp_txn.c \
		daemon/common/dhcp.c daemon/common/dhcp_preflight.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c daemon/common/apply_lock.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/authd/dhcp_apply.c \
		daemon/authd/job_dhcp.c \
		daemon/test/test_authd_dhcp_recover.c ${LDLIBS}
	./test_authd_dhcp_recover

# 10.C4 S3: the routing exec-proxy job family — host-independent pins for the
# extracted front slice + worker (routing_apply.c + job_routing.c): the local/
# rtd gate SPLIT (with_rtd=false runs no probe — the stubbed
# ogma_authd_default_route_ifaces counts calls), the spool writer's byte
# shapes (inserted comma before the window fragment, txn_id gated on
# ogma_valid_txn_id, escaped error — design review D4), the M13/D6
# descriptor pins (EC_HAVE emit gating + the materialize leading scrub), the
# reap tail's success-only rate accrual (stubbed ogma_auth_import_rate_record
# capture — the test_backup_coordinator convention), the set/dry op
# classification, and a fork-and-collect worker round-trip against a stubbed
# proxy (descriptor -> gates -> materialize -> spool bytes + exit code; the
# convergence half runs on the lab). job_routing.c references no jobs.c
# symbol, so this link stays lean.
test-authd-routing-job:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_authd_routing_job \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-rjob-'`id -u`'/secrets"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-authd-rjob-'`id -u`'/config"' \
		-DOGMA_LOG_AUTHD='"/tmp/ogma-authd-rjob-'`id -u`'/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/routes_compose.c daemon/common/validate.c \
		daemon/common/util.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c \
		daemon/common/routing_canonical.c \
		daemon/common/dhcp.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/authd/ospf.c daemon/authd/bgp.c \
		daemon/authd/routing_apply.c \
		daemon/authd/job_routing.c \
		daemon/test/test_authd_routing_job.c ${LDLIBS}
	./test_authd_routing_job

# 10.C4 S4: the ipsec exec-proxy job family — host-independent pins for the
# split front slice + worker (ipsec.c + job_ipsec.c): the C1 ipsec_enabled
# emit-row fix (both values round-trip marshal -> worker -> hop; the stub
# proxy REJECTS a mismatched hop so a forked worker's COMMITTED exit proves
# it), the S2 seeded-gate-flag overwrite, the F6 lenient enabled-arm (ANY
# load failure => empty v1 + flip), the all-LOCAL gate set with byte-exact
# deny strings, the D6 carrier pins (EC_HAVE gating + the R7 leading scrub),
# the D4 spool byte shapes (inserted comma incl. the pf-suggestion tail,
# txn gate, escape), the D7 reap accrual, and the hostile-descriptor
# rejects. The parser/gate TUs (ipsec_canonical.c/ipsec_gate.c) are
# deliberately NOT linked — the test stubs them with knobs (the
# test_authd_ipsec_binding convention); the slug helpers + store are REAL
# (secret.c). job_ipsec.c references no jobs.c symbol, so this link stays
# lean. Migration equivalence (real ipsecd/iked -n) is lab-only (§11 M23):
# run_phase10_c4_s4_smoke.py.
test-authd-ipsec-job:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_authd_ipsec_job \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-ijob-'`id -u`'/secrets"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-authd-ijob-'`id -u`'/config"' \
		-DOGMA_LOG_AUTHD='"/tmp/ogma-authd-ijob-'`id -u`'/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/routes_compose.c daemon/common/validate.c \
		daemon/common/util.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c \
		daemon/common/dhcp.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/authd/ipsec.c \
		daemon/authd/job_ipsec.c \
		daemon/test/test_authd_ipsec_job.c ${LDLIBS}
	./test_authd_ipsec_job

# 10.C4 S5: the ipsec_pki exec-proxy job family — host-independent pins for the
# split handler + worker (ipsec_pki.c + job_pki.c): the security-review S1 DN
# gate now hoisted to request_gates (issue_cert gated too, not just create_ca),
# the route classifier (writes -> job, list/unknown -> inline), submit_authz's
# L4-01 bindings / idempotence / managed-CA / export store-probe, the reap
# accrual (COMMITTED-only + the create_ca force-re-create hint), the D4 spool
# byte shapes (the BODY_TAIL slice rides its OWN leading comma — no inserted
# one), and the worker fork round-trip with a cross-fork proxy stub that echoes
# the round-tripped pki_ca_name into the spool (proving G_pki carries the pki
# fields into fwd_json — the C1 egress-drop fix). ipsec_canonical.c/ipsec_gate.c
# are stubbed (knobs); the store (secret.c) + value policy (validate.c) are
# REAL. job_pki.c references no jobs.c symbol, so this link stays lean.
# Migration equivalence (real ikectl/openssl) is lab-only (§11 M23):
# run_phase10_c4_s5_smoke.py.
test-authd-ipsec-pki-job:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_authd_ipsec_pki_job \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-pkijob-'`id -u`'/secrets"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-authd-pkijob-'`id -u`'/config"' \
		-DOGMA_LOG_AUTHD='"/tmp/ogma-authd-pkijob-'`id -u`'/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/routes_compose.c daemon/common/validate.c \
		daemon/common/util.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c \
		daemon/common/dhcp.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/authd/ipsec_pki.c \
		daemon/authd/job_pki.c \
		daemon/test/test_authd_ipsec_pki_job.c ${LDLIBS}
	./test_authd_ipsec_pki_job

# 10.E3 S3 - the support bundle's table + allowlist + gate battery. Links the
# REAL authorize.c/rbac.c/ops.c closure so the gate matrix meets production
# code; the proxy wrappers are stubbed in the test because no source is
# actually called here (the walk is a live-socket path, lab-only per VD-E3-9).
test-authd-support-job:
	${CC} ${CFLAGS} -I daemon/authd -o test_authd_support_job \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/authd/authorize.c daemon/authd/rbac.c \
		daemon/authd/support_sources.c \
		daemon/test/test_authd_support_job.c ${LDLIBS}
	./test_authd_support_job

# 10.E4 tail (VD-E4-18) adds the S-report closure: backup_secret_report.c is
# reached from archive_validate, so this target must link it and the three TUs
# it needs -- rbac.c (the secret:*:write gate the apply-time read also passes),
# backup_validate_routing.c (the fragment loader) and routing_canonical.c (the
# model plus the namespace-bound slug helpers the L4-01 chokepoints use). A
# per-uid -DOGMA_SECRETS_DIR comes with them: the probe is a real store read, so
# the PRESENT case has to plant a real file, and no case may reach /var/db.
# Nothing already in this target touches the store, so the -D moves no existing
# case.
# VD-E4-4 (the dangle class) adds the two other fragment loaders the probe
# walks: canonical.c + netd/hostname.c (the CHECKED net.yaml parse, which is
# where the wg/pppoe/carp bindings are enforced -- the test-hostname link
# shape) and backup_validate_ipsec.c + ipsec_canonical.c (the ipsec fragment
# loader and its parse+validate). Deliberately NOT backup_validate.c: its
# net loader re-runs the whole-bundle validate_file and would drag the entire
# per-fragment validator closure into this target for the privilege of
# repeating what netd's hop already did.
test-authd-archive-job:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_authd_archive_job \
		-DOGMA_STAGING_DIR='"/tmp/ogma-authd-arcjob-'`id -u`'/staging"' \
		-DOGMA_STAGING_WEB_DIR='"/tmp/ogma-authd-arcjob-'`id -u`'/web"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-authd-arcjob-'`id -u`'/config"' \
		-DOGMA_LOG_AUTHD='"/tmp/ogma-authd-arcjob-'`id -u`'/audit.log"' \
		-DOGMA_CAPKEYS_DIR='"/tmp/ogma-authd-arcjob-'`id -u`'/capkeys"' \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-arcjob-'`id -u`'/secrets"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/routes_compose.c daemon/common/validate.c \
		daemon/common/util.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c \
		daemon/common/dhcp.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/common/proto.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/routing_canonical.c \
		daemon/common/canonical.c daemon/netd/hostname.c \
		daemon/common/backup_validate_ipsec.c \
		daemon/common/ipsec_canonical.c \
		daemon/common/cap_hmac.c \
		daemon/authd/rbac.c \
		daemon/authd/backup_crypto.c \
		daemon/authd/archive_op.c \
		daemon/authd/backup_secret_report.c \
		daemon/authd/job_archive.c \
		daemon/test/test_authd_archive_job.c \
		$$([ "$$(uname -s)" = OpenBSD ] && echo '-lcrypto -lutil') ${LDLIBS}
	./test_authd_archive_job

test-hostname:
	${CC} ${CFLAGS} -DTEST_HOSTNAME -DOGMA_HAVE_IFCONFIG -o test_hostname \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_hostname.c ${LDLIBS}
	./test_hostname

# 10.F7.3 / VD-F7-8: the boot-time address-order repair, driven against planted
# on-disk fixtures. Every path constant is -D-redirected under /tmp so a
# mis-built binary writes scratch, never the live store -- the 10.D5 VD-D5-1
# lesson (test-routes once rewrote this lab's real routes.yaml while failing).
# The exec macros are poisoned for the same reason: nothing in this pass shells
# out, and a regression that made it do so must fail loudly rather than run
# ifconfig on the build host.
test-order-repair:
	-chmod -R u+rwx /tmp/ogma-order-repair-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-order-repair-`id -u`
	mkdir -p /tmp/ogma-order-repair-`id -u`
	chmod +x daemon/test/fake_poison.sh
	${CC} ${CFLAGS} -DOGMA_NETD_BUILD -DOGMA_HAVE_IFCONFIG -o test_order_repair \
		-DOGMA_IFCONFIG='"daemon/test/fake_poison.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_poison.sh"' \
		-DOGMA_SYSCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_NETSTAT='"daemon/test/fake_poison.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_ORDER_REPAIR_TEST_ROOT='"/tmp/ogma-order-repair-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_ORDER_REPAIR_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_ORDER_REPAIR_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_ORDER_REPAIR_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_ORDER_REPAIR_TEST_ROOT "/etc"' \
		-DOGMA_LOG_NETD='OGMA_ORDER_REPAIR_TEST_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_ORDER_REPAIR_TEST_ROOT "/run"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/dhcp_txn.c daemon/common/dhcp6.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/apply_lock.c \
		daemon/common/confirm_txn.c \
		daemon/common/boot_drift.c daemon/common/text_diff.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/drift.c daemon/common/yaml_lite.c \
		daemon/netd/audit.c daemon/netd/order_repair.c \
		daemon/test/test_order_repair.c ${LDLIBS}
	./test_order_repair

test-proto:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_proto \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/dhcp.c daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/legacy_parse.c \
		daemon/test/test_proto.c ${LDLIBS}
	./test_proto

# 10.D4 S1a (op contract, boundary (a) / L5-11): the in-process web<->daemon
# OP-CROSSING harness. Pushes a byte-exact Client.pm request frame through the
# REAL wire codec (frame.c) + request parser (ogma_parse.c) + op registry (ops.c)
# and round-trips the REAL egress envelope (proto.c/json.c) — WITHOUT linking
# ogma_authd_dispatch, which would drag in auth_db+sqlite+libcrypto (absent on the
# Ubuntu sanitized leg -> OpenBSD-only -> busts OPENBSD_ONLY_CAP) and, on the live
# lab, could connect a real daemon socket and mutate host state. Executing the full
# dispatch over a live socket is VD-D4-1 (S2). Same parse/proto/json closure as
# test-proto + frame.c + ops.c; fast-leg sanitizable (ci/sanitized-targets.txt),
# links no sqlite/crypto, cannot connect a daemon or mutate state.
test-op-crossing:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_op_crossing \
		daemon/common/frame.c \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/ops.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/dhcp.c daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_op_crossing.c ${LDLIBS}
	./test_op_crossing

# 10.C11 S1: req_fields.inc row-guards (off+size<=sizeof, uniqueness, kind
# totality, M2 OOB-safety) + the table-describes-parser differential (every row
# exercised against the real ogma_json_parse_request, valid + boundary) + the
# S1b numeric-boundary differential vs the frozen legacy parser (min-1/min/max/
# max+1 per RK_INT/RK_I64 row) — same parser link closure as test-proto.
test-req-fields:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_req_fields \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/dhcp.c daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/legacy_parse.c \
		daemon/test/test_req_fields.c ${LDLIBS}
	./test_req_fields

# 10.C11 S2b: the emit table (req_emit.inc) guards + the frozen-legacy emitter
# differential. NEVER-EMIT (no credential wire_key), the parse/emit off,size
# cross-check, a per-row differential (every arm, incl. the golden-uncovered
# leases_after) and a fragile-shape security corpus, all vs
# ogma_json_build_request_legacy. The M3 minimal link closure: json.c links with
# only yaml_lite.c (ogma_buf_append) — NOT net_l2.c / util.c — beside the frozen
# legacy_build.c; scripts/check_req_emit.py separately replays the table vs the
# byte-golden structurally (no compiler).
test-req-emit:
	${CC} ${CFLAGS} -o test_req_emit \
		daemon/common/json.c daemon/common/yaml_lite.c \
		daemon/test/legacy_build.c \
		daemon/test/test_req_emit.c ${LDLIBS}
	./test_req_emit

# Phase 10.B6 (L2-03 + L4-07 residual): the shared checked audit writer
# ogma_audit_write — canonical line, src-on-every-line, b64 detail redaction,
# field-injection scrub, and the checked-failure (-1 on unwritable target) that
# closes the silent write-drop. Host-portable (the log path is a parameter).
test-audit:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_audit \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/dhcp.c daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_audit.c ${LDLIBS}
	./test_audit

# Phase 10.B6.x (L4-07 at-rest / VD-B6-4): the at-rest integrity VERIFIER
# (audit_chain.c) over synthetic logs written by the REAL writer through an
# injected HMAC provider — so the writer/verifier covered-bytes contract is
# pinned, not assumed. S3 adds the generation LEDGER (audit_ledger.c): the walk
# alone still cannot see a truncated tail, a cross-generation rollback, a
# pre-migration substitution or a keyless head splice, and this target now pins
# BOTH halves — the walk's blindness AND the anchor catching it. Links cap_hmac.c
# for the tag; it is deliberately NOT added to the lean targets, which is the
# whole point of the writer's injected-provider design. Host-portable: the log
# path AND the ledger path are both runtime parameters, which is why the ledger
# TU may not depend on daemon_inventory.c or on a compile-time OGMA_DB_DIR.
test-audit-verify:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_audit_verify \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/dhcp.c daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c daemon/common/cap_hmac.c \
		daemon/common/audit_chain.c daemon/common/audit_ledger.c \
		daemon/test/test_audit_verify.c ${LDLIBS}
	./test_audit_verify

# Phase 10.B6 (VD-B6-6): the STANDALONE offline verifier
# (tools/ogma-audit-offline-verify.c) proven byte-equivalent to the product.
# The tool is a single dependency-free file with its OWN vendored SHA-256/HMAC,
# so nothing but this test pins it to the product; the test #includes the tool
# (OAV_NO_MAIN) and drives BOTH the tool's walk and ogma_audit_verify_log over
# logs from the REAL writer, asserting identical named verdicts. Host-portable
# (a per-uid /tmp log). Sanitized (ci/sanitized-targets.txt): pure C, execs
# nothing. Both sources live under tools/ (a dev/audit tree, export-ignored) —
# the tool ships to auditors from the repo, not the customer build tarball, and
# `make test` runs this target only on the git-checkout CI legs, never on the
# dist tarball (which builds `all`+`install` only).
test-offline-verify:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_offline_verify \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/dhcp.c daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c daemon/common/cap_hmac.c \
		daemon/common/audit_chain.c \
		tools/test_offline_verify.c ${LDLIBS}
	./test_offline_verify

# authd auth/RBAC database (users.db) through the privsep db child:
# schema + seeds, user add (duplicate constraint must not kill the
# child), login lockout, sessions, import rate limits, reopen. Runs
# against a scratch per-uid DB dir (see the test-pf-pending comment for
# why per-uid). Needs sqlite3 (package on OpenBSD, libsqlite3-dev on
# Linux CI).
test-auth-db:
	${CC} ${CFLAGS} -I daemon/authd -o test_auth_db \
		-DOGMA_DB_DIR='"/tmp/ogma-auth-db-'`id -u`'"' \
		-DOGMA_TEST_AUTH_BREAKGLASS \
		daemon/authd/auth_db.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/ops.c \
		daemon/common/auth_canonical.c daemon/common/yaml_lite.c \
		daemon/common/config_manifest.c \
		daemon/common/totp.c \
		daemon/test/state_reclaim.c \
		daemon/test/test_auth_db.c \
		-L/usr/local/lib -lsqlite3 ${LDLIBS}
	./test_auth_db

# Phase 10.A2 users.db resilience: spawner/quarantine/respawn/read-timeout/
# degraded-boot behaviours. Same link recipe as test-auth-db (own scratch dir)
# plus SHORT resilience windows (-D) so the suite never sleeps for minutes.
test-auth-db-resilience:
	${CC} ${CFLAGS} -o test_auth_db_resilience \
		-DOGMA_DB_DIR='"/tmp/ogma-auth-db-res-'`id -u`'"' \
		-DDB_IO_TIMEOUT_MS=300 -DDB_IO_TIMEOUT_BULK_MS=2000 \
		-DDB_REBIRTH_WINDOW_MS=3000 -DDB_MAX_REBIRTHS=3 \
		-DDB_SPAWN_MIN_INTERVAL_MS=50 \
		daemon/authd/auth_db.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/ops.c \
		daemon/common/auth_canonical.c daemon/common/yaml_lite.c \
		daemon/common/config_manifest.c \
		daemon/common/totp.c \
		daemon/test/test_auth_db_resilience.c \
		-L/usr/local/lib -lsqlite3 ${LDLIBS}
	./test_auth_db_resilience

# 10.B2 monotonic-time discipline (L8-06): boot-step + walk-the-clock lockout
# survival, non-denying verdict. Same link recipe + scratch dir as test-auth-db,
# plus -DOGMA_TEST_AUTH_CLOCK for the wall/mono/boot-wall injection seam.
test-auth-clock:
	${CC} ${CFLAGS} -o test_auth_clock \
		-DOGMA_DB_DIR='"/tmp/ogma-auth-clock-'`id -u`'"' \
		-DOGMA_TEST_AUTH_CLOCK \
		daemon/authd/auth_db.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/ops.c \
		daemon/common/auth_canonical.c daemon/common/yaml_lite.c \
		daemon/common/config_manifest.c \
		daemon/common/totp.c \
		daemon/test/test_auth_clock.c \
		-L/usr/local/lib -lsqlite3 ${LDLIBS}
	./test_auth_clock

# Phase 9.O2: the self-contained TOTP/MFA crypto (daemon/common/totp.c) against
# RFC 6238 / RFC 4648 / RFC 2202 published vectors. Links ONLY totp.c — the
# module pulls no crypto library (vendored SHA-1), so the vectors evaluate
# identically on OpenBSD and Linux CI.
test-totp:
	${CC} ${CFLAGS} -o test_totp \
		daemon/common/totp.c \
		daemon/test/test_totp.c ${LDLIBS}
	./test_totp

test-rbac:
	${CC} ${CFLAGS} -o test_rbac \
		daemon/authd/rbac.c daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/test_rbac.c ${LDLIBS}
	./test_rbac

# 10.C1.S4: drive the REAL ogma_authd_authorize switch (authorize.c) with a
# perm x seed-set allow/deny matrix — the net that catches a mis-routed/dropped/
# mis-tokenized arm the perm_token golden + -Wswitch-enum cannot. Sqlite-free.
test-authorize:
	${CC} ${CFLAGS} -o test_authorize \
		daemon/authd/authorize.c daemon/authd/rbac.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/ops.c \
		daemon/test/test_authorize.c ${LDLIBS}
	./test_authorize

test-peer:
	${CC} ${CFLAGS} -o test_peer \
		daemon/netd/peer.c daemon/rtd/peer.c daemon/pfd/peer.c \
		daemon/dnsd/peer.c daemon/routed/peer.c \
		daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/test_peer.c ${LDLIBS}
	./test_peer

test-diag:
	${CC} ${CFLAGS} \
		-DOGMA_DIAG_JOB_DIR='"/tmp/ogma-diag-test-'`id -u`'"' \
		-DOGMA_LOG_DIAGD='"/tmp/ogma-diag-test-'`id -u`'.audit"' \
		-DOGMA_PING='"/bin/echo"' -DOGMA_PING6='"/bin/echo"' \
		-DOGMA_TRACEROUTE='"/bin/echo"' -DOGMA_TRACEROUTE6='"/bin/echo"' \
		-DOGMA_HOST='"/bin/echo"' -DOGMA_NC='"/bin/echo"' \
		-DOGMA_TCPDUMP='"/bin/echo"' \
		-DOGMA_DIAG_SWEEP='"/bin/echo"' \
		-DOGMA_DIAG_TEST \
		-o test_diag \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/diagd/diag.c daemon/diagd/audit.c \
		daemon/test/test_diag.c ${LDLIBS}
	./test_diag

# test_health.c #includes daemon/healthd/health.c to reach the static parsers,
# so health.c is NOT listed separately (that would double-define its symbols).
# No tool paths are exec'd (parsers are driven from captured fixtures; the one
# capture+reap smoke uses /bin/echo), so no -D tool overrides are needed.
# build_rev.h is a prerequisite because health.c #includes it (the guarded
# include defaults to "unknown" if absent, but wiring the dep keeps a from-clean
# `make test` self-contained and the stamp fresh).
# 10.E9 S1: health.c now reads the fsck auto-repair posture (system.yaml, the
# opt-out marker, the /etc/rc sentinel, the repair record, the boot sentinel,
# the image marker) — every path -D'd into a scratch tree so the unit never
# stats the runner's /etc; the three new closure members are the pure
# canonical + comparators + the one-stat edition helper.
test-health: ${BUILD_REV_H}
	${CC} ${CFLAGS} -o test_health \
		-DOGMA_TEST_ROOT='"/tmp/ogma-health-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/run"' \
		-DOGMA_FSCK_MARKER='OGMA_TEST_ROOT "/etc/fsck-manual"' \
		-DOGMA_FSCK_STATE_DIR='OGMA_TEST_ROOT "/fsck"' \
		-DOGMA_FSCK_RC_PATH='OGMA_TEST_ROOT "/etc/rc"' \
		-DOGMA_IMAGE_LAYOUT_MARKER='OGMA_TEST_ROOT "/etc/image-layout"' \
		-DOGMA_SYSLOGD_CONF='OGMA_TEST_ROOT "/etc/syslog.conf"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c daemon/common/os_release.c \
		daemon/common/system_canonical.c daemon/common/fsck_state.c \
		daemon/sysd/edition.c \
		daemon/test/test_health.c ${LDLIBS}
	rm -rf /tmp/ogma-health-`id -u`
	./test_health
	rm -rf /tmp/ogma-health-`id -u`

# test_arp.c #includes daemon/arpd/arp.c (the test_health pattern) to reach its
# parser + argv builder; the compiled op handlers also reference the apply lock,
# the arp canonical model, and the arpd audit sink, so those are linked too. No
# arp(8)/route(8) is exec'd (pure functions are driven from fixtures), so no -D
# tool overrides are needed.
test-arp:
	${CC} ${CFLAGS} -o test_arp \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c daemon/common/apply_lock.c \
		daemon/common/arp_canonical.c daemon/common/drift.c \
		daemon/arpd/audit.c \
		daemon/test/test_arp.c ${LDLIBS}
	./test_arp

test-gateways:
	${CC} ${CFLAGS} -o test_gateways \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c daemon/common/apply_lock.c \
		daemon/common/gateways_canonical.c \
		daemon/test/test_gateways.c ${LDLIBS}
	./test_gateways

# 9.R2.d: gwmon_ha_pending_flush (probe.c) calls ogma_gw_effective_members and
# gwmon_flush_inhibited consumes ogma_carp_group_entry, so the pure-probe test
# links the test-gateways dependency cohort + carp_drift.c (which since 10.C6
# S5c additionally pulls drift.c for the unified eval/builder — json.c and
# yaml_lite.c, drift.c's only other deps, are already in the cohort).
test-gwmon-probe:
	${CC} ${CFLAGS} -o test_gwmon_probe \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c daemon/common/apply_lock.c \
		daemon/common/gateways_canonical.c daemon/common/carp_drift.c \
		daemon/common/drift.c \
		daemon/test/test_gwmon_probe.c ${LDLIBS}
	./test_gwmon_probe

# VD-F8-7 (10.F8 residual tail, 2026-08-27): gwmond's /var/run/ogmaprotect grant
# narrowed from dir-level "rwc" to the file-scoped triple (dir "r", own file +
# its ".tmp" "rwc"). This drives the REAL atomic writer under REAL unveil(2) in
# forked children: the narrowed triple suffices (write, rewrite, 0600, .tmp
# gone), dir "r" alone does not, a sibling record is readable but untouchable,
# and the OLD dir-level grant truncates it (the witness). unveil(2) is
# OpenBSD-only, so the arms run on the authoritative `openbsd` leg and the
# binary prints a skip on the sanitized Linux leg (exec-free, so it still
# belongs in ci/sanitized-targets.txt rather than an OpenBSD-only carve-out).
# OGMA_GW_STATE_DIR is #ifndef-guarded and OGMA_GW_STATE_FILE derives from it,
# so the -D'd pair is the production pair one directory over.
test-gwmon-unveil:
	${CC} ${CFLAGS} -o test_gwmon_unveil \
		-DOGMA_TEST_ROOT='"/tmp/ogma-gwmon-unveil-'`id -u`'"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/run"' \
		daemon/common/util.c daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/test/test_gwmon_unveil.c ${LDLIBS}
	rm -rf /tmp/ogma-gwmon-unveil-`id -u`
	mkdir -p /tmp/ogma-gwmon-unveil-`id -u`
	./test_gwmon_unveil

# 10.F8 S2. Links the authd verdict unit (which #includes svc_expect.c) against
# the five canonical parsers it resolves domains through, plus authd/rbac.c for
# the per-row grant check and boot_drift/schema_state for gates 2 and 3. Gates 2
# and 3 ARE exercised here, over a mkdtemp scratch dir — both helpers take a
# path, so neither needs root nor the real config dir.
# VD-F8-1 (10.F8 residual tail, 2026-08-27): gate 4 — a REAL canonical load — is
# exercised too, through the top-level ogma_svc_expect_domain, by -D'ing the
# config and staging roots under a per-uid scratch root (OGMA_CONFIG_DIR and
# OGMA_STAGING_DIR are #ifndef-guarded; every OGMA_*_YAML and *_PENDING_DIR
# derives from them). That is what lets rule 1 (`adopted:false` => unknown),
# rule 3 (a real txn-id marker under the real pending-dir path) and the ipsec
# verdict over a loader-produced non-empty struct run at Tier 2. Only the
# behavioural LIVE checks (the op routes; the page renders the verdict) stay
# Tier 3; see the S2 plan §6 and the VD-F8-1 ledger row.
test-svc-expect:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_svc_expect \
		-DOGMA_TEST_ROOT='"/tmp/ogma-svc-expect-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/canonical.c daemon/common/apply_lock.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/arp_canonical.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/dhcpd_canonical.c \
		daemon/common/routing_canonical.c daemon/common/time_canonical.c \
		daemon/common/ipsec_canonical.c daemon/common/ipsec_gate.c \
		daemon/common/cert_canonical.c daemon/common/auth_canonical.c \
		daemon/common/gateways_canonical.c daemon/common/egress_url.c \
		daemon/common/remotelog_canonical.c daemon/common/alerts_canonical.c \
		daemon/common/sysctl_canonical.c daemon/common/identity_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/pppoe_drift.c daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/schema_state.c daemon/common/boot_drift.c \
		daemon/common/service_table.c daemon/authd/rbac.c \
		daemon/test/test_svc_expect.c ${LDLIBS}
	rm -rf /tmp/ogma-svc-expect-`id -u`
	mkdir -p /tmp/ogma-svc-expect-`id -u`
	./test_svc_expect

# 10.C10 (L1-23): the supervision JSON builder. test_supervision.c #includes
# authd/supervision.c, which pulls in the C9 daemon inventory (ogma_daemon_at/
# count) plus json.c (ogma_json_escape) + yaml_lite.c (ogma_buf_append) — the
# same json.c link cohort as test-gwmon-probe, + daemon_inventory.c.
test-supervision:
	${CC} ${CFLAGS} -o test_supervision \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c daemon/common/apply_lock.c \
		daemon/common/gateways_canonical.c \
		daemon/common/daemon_inventory.c \
		daemon/common/lateral.c daemon/common/frame.c \
		daemon/test/test_supervision.c ${LDLIBS}
	./test_supervision

test-revision:
	${CC} ${CFLAGS} -o test_revision \
		-DOGMA_REVISIONS_DIR='"/tmp/ogma-rev-'`id -u`'/revs"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-rev-'`id -u`'/config"' \
		-DOGMA_REVISION_GC_KEEP_PER_KEY=4 \
		-DOGMA_REVISION_GC_MAX_DELETE_PER_RUN=2 \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_revision.c ${LDLIBS}
	./test_revision

# L8-08 config-dir manifest VERIFY. Uses an explicit mkdtemp config_dir (no
# -DOGMA_CONFIG_DIR needed — the verify fn takes the dir as an argument), and
# controls fragment/manifest mtimes with utimensat so the staleness split is
# deterministic regardless of filesystem clock granularity. Link closure mirrors
# test-revision (ogma_parse.c + validate.c both pull in routes_canonical.c, which
# transitively needs revision.c/text_diff.c) — the smallest proven-linkable set
# that includes config_manifest.c.
# 10.E2 S2 (T3): the config-fragment schema-state surface — the pure classifier
# (driven over synthetic (disk,ceiling) pairs, because REGRESSED/AHEAD are
# unreachable through the real inventory while every ceiling is 1), the on-disk
# scanner incl. the nested-hide_version substring trap and the routing torn case,
# and the egress builder. Link closure mirrors test-config-manifest: the scanner
# needs only util.c, the builder adds json.c + yaml_lite.c, and config_manifest.c
# carries the shared fragment inventory the walk reads.
test-schema-state:
	${CC} ${CFLAGS} -o test_schema_state \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/schema_state.c daemon/common/json_schema_state.c \
		daemon/common/yaml_lite.c \
		daemon/test/state_reclaim.c \
		daemon/test/test_schema_state.c ${LDLIBS}
	./test_schema_state

# 10.E2 S3 (T2): ogma_fragment_converge() — the pre-save version read, the
# migration-revision decision and the pre-migration snapshot. The ceiling is
# INJECTED (ogma_fragment_converge_at) because a forward crossing needs
# old < new and every shipped fragment ceiling is 1, so MINT is otherwise
# unreachable — the same reason 10.E2 S2 exposed ogma_schema_classify(). The
# -D redirects are load-bearing: OGMA_REVISIONS_DIR is baked into the mint call,
# and the test asserts its root is under /tmp/ogma- so a mis-built binary is a
# loud no-op rather than a write to the real store.
test-schema-converge:
	${CC} ${CFLAGS} -o test_schema_converge \
		-Wl,--wrap=write -Wl,--wrap=fsync -Wl,--wrap=rename \
		-DOGMA_TEST_ROOT='"/tmp/ogma-schema-converge-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/schema_state.c daemon/common/schema_converge.c \
		daemon/common/yaml_lite.c \
		daemon/test/fault_wrap.c \
		daemon/test/state_reclaim.c \
		daemon/test/test_schema_converge.c ${LDLIBS}
	./test_schema_converge

test-config-manifest:
	${CC} ${CFLAGS} -o test_config_manifest \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
		daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/json_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_config_manifest.c ${LDLIBS}
	./test_config_manifest

# 10.A4: the net-rollback cases drive the REAL ogma_backup_apply /
# ogma_backup_net_rollback / ogma_netd_render_recover against a per-uid /tmp
# scratch tree + the fake ifconfig shim (test_backup.c guards them off unless
# every path is -D'd under /tmp — a mis-built binary must never touch /etc or
# /var/db). An empty scratch routes.yaml makes the post-apply route re-assert a
# no-op (ogma_routes_reapply_canonical_live returns early on cfg_n==0), so no
# route(8) fake is needed. The legacy cases are unaffected (they assert on
# validation failures before any ifconfig exec).
# VD-E4-26(b) adds -DOGMA_ROUTE: ogma_backup_apply now asks the LIVE default
# route whether a planned route delete would strand the management path, and the
# answer must come from a fixture rather than from whatever default route the CI
# runner happens to hold. Without the fake, the un-ackable-gate case passes on the
# Ubuntu leg (no /sbin/route -> unreadable -> fail closed) and fails on the
# OpenBSD leg (a real, different default route -> not blocking) -- the same
# assertion, two verdicts. The pre-existing route(8) reach on this target was
# ogma_routes_reapply_canonical_live's, kept a no-op by an empty routes.yaml; the
# fake makes that hermetic too rather than merely unreached.
test-backup:
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh
	${CC} ${CFLAGS} -DOGMA_TEST_APPLY_LOCK -DOGMA_HAVE_IFCONFIG -o test_backup \
		-DOGMA_TEST_ROOT='"/tmp/ogma-backup-'`id -u`'"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_STAGING_WEB_DIR='OGMA_TEST_ROOT "/web"' \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/revision.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/routing_canonical.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/gateways_canonical.c \
		daemon/common/backup_apply.c daemon/common/apply_lock.c \
		daemon/common/backup_snap.c \
		daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_backup.c ${LDLIBS}
	./test_backup

# 10.B5: backup manifest signing + SIGNED/UNSIGNED/INVALID classify + the
# file-level sha256 self-pin. Same link set as test-backup + cap_hmac.c (the
# HMAC-SHA256 the deployment signature uses).
test-backup-sign:
	chmod +x daemon/test/fake_ifconfig.sh
	${CC} ${CFLAGS} -DOGMA_TEST_APPLY_LOCK -DOGMA_HAVE_IFCONFIG -o test_backup_sign \
		-DOGMA_TEST_ROOT='"/tmp/ogma-b5sign-'`id -u`'"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/revision.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/routing_canonical.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/gateways_canonical.c \
		daemon/common/backup_apply.c daemon/common/apply_lock.c \
		daemon/common/backup_snap.c \
		daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c daemon/common/cap_hmac.c \
		daemon/test/test_backup_sign.c ${LDLIBS}
	./test_backup_sign

# 10.B5 AEAD passphrase encryption of config bundles. OpenBSD-only (LibreSSL
# EVP_AEAD chacha20-poly1305 + libutil bcrypt_pbkdf); a no-op skip on other
# tiers (Linux c-tests never builds authd/backup_crypto.c). Links ONLY
# backup_crypto.c (+ -lcrypto -lutil) — it pulls no other COMMON unit.
test-backup-encrypt:
	@if [ "`uname -s`" = "OpenBSD" ]; then \
		${CC} ${CFLAGS} -o test_backup_encrypt \
			daemon/authd/backup_crypto.c \
			daemon/test/test_backup_encrypt.c -lcrypto -lutil ${LDLIBS} && \
		./test_backup_encrypt; \
	else \
		echo "test-backup-encrypt: skipped (OpenBSD-only: EVP_AEAD/bcrypt_pbkdf)"; \
	fi

# 10.B5.1 the backup ANCHOR sign/verify helper: the REAL signify(1) round-trip
# and the anchor lifecycle state machine. OpenBSD-only (execs /usr/bin/signify —
# real-userland-exec); the sanitized Linux leg covers the alg-first ladder with a
# fake hook in test-backup-sign. Runs under the test root so no /var/db is touched.
test-sign-helper:
	@if [ "`uname -s`" = "OpenBSD" ]; then \
		${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_sign_helper \
			-DOGMA_CAPKEYS_DIR='"/tmp/ogma-signh-'`id -u`'/capkeys"' \
			daemon/common/jsmn.c daemon/common/ogma_parse.c \
			daemon/common/validate.c daemon/common/util.c \
			daemon/common/json.c daemon/common/canonical.c \
			daemon/common/config_manifest.c daemon/common/net_l2.c \
			daemon/common/secret.c daemon/common/dhcp.c \
			daemon/common/revision.c \
			daemon/common/backup.c daemon/common/backup_archive.c \
			daemon/common/routes_canonical.c daemon/common/routes_compose.c \
			daemon/netd/hostname.c daemon/netd/ifconfig.c \
			daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
			daemon/common/tunnel_drift.c daemon/common/drift.c \
			daemon/common/yaml_lite.c daemon/common/cap_hmac.c \
			daemon/authd/sign_helper.c \
			daemon/test/test_sign_helper.c ${LDLIBS} && \
		./test_sign_helper; \
	else \
		echo "test-sign-helper: skipped (OpenBSD-only: execs signify(1))"; \
	fi

test-backup-coordinator:
	${CC} ${CFLAGS} -DOGMA_TEST_APPLY_LOCK -DOGMA_HAVE_IFCONFIG -o test_backup_coordinator \
		-DOGMA_TEST_RESTORE_HOOKS \
		-DOGMA_STAGING_DIR='"/tmp/ogma-backup-coord-'`id -u`'/staging"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-backup-coord-'`id -u`'/config"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/revision.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/backup_validate_pf.c \
		daemon/common/backup_validate_dns.c \
		daemon/common/backup_validate_dhcpd.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/backup_validate_arp.c \
		daemon/common/backup_validate_time.c \
		daemon/common/backup_validate_cert.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/backup_validate_remotelog.c \
		daemon/common/backup_validate_alerts.c \
		daemon/common/backup_validate_ipsec.c \
		daemon/common/backup_validate_sysctl.c \
		daemon/common/backup_validate_identity.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/dhcpd_canonical.c \
		daemon/common/routing_canonical.c \
		daemon/common/arp_canonical.c \
		daemon/common/time_canonical.c \
		daemon/common/cert_canonical.c \
		daemon/common/gateways_canonical.c \
		daemon/common/remotelog_canonical.c \
		daemon/common/alerts_canonical.c \
		daemon/common/egress_url.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/identity_canonical.c \
		daemon/common/ipsec_canonical.c daemon/common/ipsec_gate.c \
		daemon/common/backup_apply.c daemon/common/apply_lock.c \
		daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c \
		daemon/authd/rbac.c \
		daemon/authd/backup_coordinator.c daemon/authd/ospf.c \
		daemon/authd/bgp.c \
		daemon/authd/restore_journal.c \
		daemon/common/backup_snap.c daemon/common/restore_lock.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_backup_coordinator.c ${LDLIBS}
	./test_backup_coordinator

lint-docs:
	sh scripts/check-docs-daemon-names.sh
	sh scripts/check-docs-overclaim.sh
	perl scripts/check_roadmap_tombstones.pl

test-perl:
	OGMA_OP_TAP_FILE=`mktemp` OGMA_OP_TAP_EXPECTED=1 \
	prove -I web/lib -I t/lib t/net.t t/cgi_compile.t \
		t/handlers_dashboard.t t/handlers_routes.t t/handlers_firewall.t t/handlers_dns.t \
		t/handlers_netcreate.t t/handlers_tunnel.t t/handlers_system.t t/handlers_interface.t \
		t/handlers_wireguard.t t/handlers_backup.t t/handlers_routing.t t/handlers_dhcp.t \
		t/handlers_login.t t/handlers_status.t t/handlers_diagnostics.t t/handlers_ipsec.t \
		t/handlers_time.t t/handlers_arp.t t/handlers_logs.t \
		t/handlers_alerts.t \
		t/template_interface.t \
		t/template_interface_regions.t t/template_dashboard_regions.t t/check_render_equivalence.t \
		t/template_dhcp_regions.t t/template_prefs_regions.t t/template_gateways_regions.t \
		t/template_routing_regions.t t/template_net_regions.t t/template_ops_regions.t t/template_system_regions.t \
		t/template_interface_dhcp.t t/template_interface_ipv6.t t/template_firewall.t t/pf_macros.t t/pf_rule_editor.t t/pf_mgmt_scoping.t t/template_dns.t t/template_dns_regions.t \
		t/template_dhcp.t t/template_ospf.t t/template_bgp.t t/template_gateways.t t/template_ha.t t/handlers_ha.t t/handlers_hasync.t t/routing_p5.t t/template_wireguard.t t/template_pppoe.t \
		t/template_routes.t t/template_arp.t t/template_ipsec.t t/template_users.t \
		t/template_dashboard.t t/template_system.t t/template_diagnostics.t \
		t/template_system_health.t t/handlers_health.t t/nav_system.t t/template_logs.t \
		t/template_support.t t/support_runbook.t t/recovery_runbook.t \
	t/support_role_tokens.t \
		t/restore_runbook.t t/factory_bench_checklist.t t/fsck_rc_patch.t t/ctl_man_page.t t/release_eol_lockstep.t \
		t/image_rc_patch.t t/resflash_build.t t/release_image_job.t \
		t/update_manifest_guard.t \
		t/authd_sysd_rate_arm.t t/updstage_helper_shape.t t/ogmaprotectctl_update.t \
		t/pkg_repo.t t/pkg_depends.t \
		t/template_time.t t/template_remotelog.t t/template_alerts.t \
		t/template_system_sysctls.t \
		t/template_system_control.t t/template_system_identity.t \
		t/template_system_tls_cert.t \
		t/template_auth_policy.t t/revisions.t \
		t/auth_login.t t/logout_post.t t/auth_csrf.t t/login_csrf_stability.t \
		t/authd_csrf_reply.t t/auth_csrf_session.t t/cookie_host_prefix.t \
		t/check_cookie_names.t t/csrf_cookie_retirement.t \
		t/nav_users.t t/nav_routes.t t/users_handler.t t/force_change.t t/mfa_enroll.t \
		t/ui_safe_error.t t/sanitize_sites.t \
		t/ogmaprotectctl_status.t t/idempotency.t \
		t/merged_tabs.t t/no_retired_urls.t t/test_perl_wired.t t/css_drift.t \
		t/css_cascade.t \
		t/theme_js.t \
		t/asset_fingerprint.t \
		t/validate.t t/no_handler_validator_redef.t \
		t/yaml_emit.t t/yaml_roundtrip.t t/pf_emitter_census.t t/handlers_system_cert_yaml.t t/handlers_firewall_yaml.t t/handlers_base.t \
		t/schema_precheck.t \
		t/routing_ns_binding.t t/ratelimit.t t/template_backup.t \
		t/client_deadline.t t/deadline_chain.t t/httpd_conf_consistency.t \
		t/client_framed.t t/wire_version.t t/check_ci_coverage.t \
		t/check_lsan_suppressions.t t/check_exec_hermeticity.t \
		t/shim_fidelity.t t/op_contract.t \
		t/ogmaprotect_setup.t t/check_smoke_registry.t t/check_verification_debt.t \
		t/dist_manifest.t \
		t/check_roadmap_tombstones.t t/ipv6_client_smoke_selftest.t \
		t/run_smokes_manual_pin.t t/install_consistency_pin.t \
		t/hasync_egress_fork_pledge.t t/gwmond_unveil.t \
		t/web_install_complete.t t/template_escaping.t t/template_poison.t \
		t/render_corpus.t t/check_section_driver.t t/check_raw_splices.t \
		t/check_render_coverage.t \
		t/template_router.t \
		t/check_js_sinks.t t/js_install_hygiene.t t/js_lint_wiring.t \
		t/js_smoke_wiring.t t/js_module_split.t t/check_py_syntax_wiring.t \
		t/template_fill_adoption.t t/template_fill_each.t t/escape_html.t t/ui_redirect_response.t \
		t/render_tap_bodies.t t/check_schema_versions.t t/check_durable_paths.t \
		t/check_fuzz_reach.t t/check_fuzz_lane_liveness.t \
		t/check_truncation_baseline.t \
		t/check_route_guards.t t/check_route_guards_argsplit.t t/route_probe_goldens.t \
		t/route_metadata.t t/route_op_negatives.t t/dhcp_saga_message.t \
		t/request_surface.t t/check_request_surface.t t/route_probe_headers.t \
		t/cache_control_pins.t t/auth_cookie_engine.t t/request_too_large.t \
		t/deny_msg_preimage.t \
		t/check_page_dispatch.t t/check_template_resolve.t \
		t/check_polarity_set.t t/check_sweep_set.t \
		t/check_loc_budget.t t/check_constructed_tokens.t \
		t/sign_helper_pins.t t/signify_trust_roots.t t/release_info.t \
		t/successor_carry_guard.t \
		t/console_sentinel_closure.t t/console_resolver_admit.t \
		t/backup_export_census.t t/unattested_import_census.t \
		t/backup_preflight_version.t \
		t/ogmaprotectctl_lockout.t t/ogmaprotectctl_domain_verbs.t \
		t/ogmaprotectctl_windows.t \
		t/read_audit_rule.t t/rbac_token_shapes.t \
		t/serialize_checked_census.t t/json_escape_checked.t t/scaffold_page.t

test-net-l2:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_net_l2 \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/pppoe_drift.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_net_l2.c ${LDLIBS}
	./test_net_l2

# 10.D5 VD-D5-1: HERMETIC. Two non-hermeticities made this the one unit target
# that passes in CI and fails on a provisioned box -- and silently rewrote that
# box's live config while failing:
#   1. ogma_canonical_migrate_routes_paths_if_missing takes three injectable
#      paths but reads a FOURTH, OGMA_HOSTNAME_DIR, from the compile-time
#      literal "/etc". CI has no /etc/hostname.* so the import adds nothing; a
#      real router's /etc is full of them, and netd preserves any legacy
#      `!route add ...` line verbatim, so one imported default makes two inet
#      defaults and validate_list rejects the whole migration (rc -1).
#   2. test_routes.c's restore-file leg fires on `access(OGMA_CONFIG_DIR, W_OK)`
#      -- true for root on a provisioned box -- and wrote the FIXTURE's routes
#      over the live /var/db/ogmaprotect/config/routes.yaml (and re-baselined
#      the live manifest). That is how this lab acquired a phantom 10.10.0.0/16
#      static route of "unknown origin", rendered on into /etc/mygate and the FIB.
# Redirecting both (the test-address-pending idiom) makes the target read an
# EMPTY scratch hostname dir and write only scratch -- and, as a bonus, the
# restore leg now runs on EVERY host instead of being skipped in CI. The scratch
# hostname dir MUST exist: a missing one fails opendir and the migration returns
# -1 for a brand-new reason.
test-routes:
	rm -rf /tmp/ogma-routes-`id -u`
	mkdir -p /tmp/ogma-routes-`id -u`/etc /tmp/ogma-routes-`id -u`/config \
		/tmp/ogma-routes-`id -u`/revisions
	${CC} ${CFLAGS} -o test_routes \
		-DOGMA_TEST_ROOT='"/tmp/ogma-routes-'`id -u`'"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_MYgate_PATH='OGMA_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_TEST_ROOT "/mygate.tmp"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/drift.c daemon/common/yaml_lite.c \
		daemon/test/test_routes.c ${LDLIBS}
	./test_routes

test-routes-priority:
	${CC} ${CFLAGS} -o test_routes_priority \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c daemon/common/apply_lock.c \
		daemon/common/drift.c daemon/common/yaml_lite.c \
		daemon/test/test_routes_priority.c ${LDLIBS}
	./test_routes_priority

test-rtd:
	chmod +x daemon/test/fake_route.sh
	${CC} ${CFLAGS} -o test_rtd \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/json.c daemon/common/json_routes.c \
		daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c daemon/common/apply_lock.c \
		daemon/common/drift.c daemon/common/yaml_lite.c \
		daemon/test/test_rtd.c ${LDLIBS}
	./test_rtd

# 10.E7.2 S3 tail (VD-E7-2-6, VD-E7-2-3): rtd's boot/periodic drift DRIVERS
# through the REAL dispatch.c statics (#included, the test-netd-batch pattern)
# -- the one participant that landed at S3d with no Sec.7 equivalence test,
# because no target linked daemon/rtd/dispatch.c. Pins: boot == periodic rows
# on converged and drifted fixtures; the periodic driver is not gated by the
# boot record; a spent budget publishes `error`; the backfill cap is installed
# for the periodic walk (EXACTLY OGMA_ROUTE_BACKFILL_PERIODIC_MAX probes),
# honoured, and restored so boot probes at the ratified OGMA_ROUTE_GET_MAX;
# the Sec.2.8 dynamic-routing fence is periodic-only, needs BOTH origin and
# priority, and fails safe to `skip` on a truncated list; the confirm-window
# gate holds on both channels. Fake route(8) via OGMA_FAKE_ROUTE_DIR; every
# other exec macro the rtd closure could reach is poisoned. The walk budget is
# stubbed (daemon_base.c's closure is the whole daemon loop; VD-E7-2-2).
test-rtd-drift:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_rtd_drift \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_IFCONFIG='"daemon/test/fake_poison.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_SYSCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_RTD_DRIFT_TEST_ROOT='"/tmp/ogma-rtd-drift-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_RTD_DRIFT_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_RTD_DRIFT_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_RTD_DRIFT_TEST_ROOT "/staging"' \
		-DOGMA_GW_STATE_DIR='OGMA_RTD_DRIFT_TEST_ROOT "/run"' \
		-DOGMA_LOG_RTD='OGMA_RTD_DRIFT_TEST_ROOT "/audit.log"' \
		-DOGMA_MYgate_PATH='OGMA_RTD_DRIFT_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_RTD_DRIFT_TEST_ROOT "/mygate.tmp"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/proto.c daemon/common/json_pf.c \
		daemon/common/pf_structured.c \
		daemon/common/json_routes.c daemon/common/ops.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/revision.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/apply_lock.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/gateways_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/common/lateral.c daemon/common/frame.c \
		daemon/common/boot_drift.c daemon/common/text_diff.c \
		daemon/common/confirm_txn.c daemon/common/cap_hmac.c \
		daemon/common/backup_snap.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/drift.c \
		daemon/rtd/peer.c daemon/rtd/dhcp_reconcile.c \
		daemon/rtd/route.c daemon/rtd/apply.c daemon/rtd/backup_apply.c \
		daemon/rtd/backup_apply_gateways.c \
		daemon/rtd/route_pending.c daemon/rtd/gateways_pending.c \
		daemon/rtd/pending_mux.c daemon/rtd/audit.c \
		daemon/test/test_rtd_drift.c ${LDLIBS}
	chmod +x daemon/test/fake_route.sh daemon/test/fake_poison.sh
	rm -rf /tmp/ogma-rtd-drift-`id -u`
	./test_rtd_drift
	rm -rf /tmp/ogma-rtd-drift-`id -u`

test-yaml-lite:
	${CC} ${CFLAGS} -o test_yaml_lite \
		daemon/common/yaml_lite.c \
		daemon/test/test_yaml_lite.c ${LDLIBS}
	./test_yaml_lite

# 10.E2 S1: the config-fragment schema-version seam, end to end in ONE binary
# — the shared helpers, the v99 differentiated reject for every fragment, the
# MECH-6 unknown-key invariant, MECH-5 serialize-at-ceiling, and the D2
# retrofit (net/pf/routes/arp/gateways accept an optional `version:` and emit
# nothing at v1, which is the zero-on-disk-byte-change property). Links the
# whole canonical closure, so -DOGMA_HAVE_IFCONFIG is MANDATORY: canonical.c
# pulls in netd/hostname.c -> netd/ifconfig.c, whose four symbols net_l2.c also
# defines unless one of OGMA_HAVE_IFCONFIG/OGMA_NETD_BUILD is set (a duplicate
# definition, not a missing one). Sanitized leg — the same closure as
# test-backup-coordinator and test-routing-status, both already there.
# hasync.yaml is deliberately absent: its parser needs the scratch-dir -D
# overrides test-hasync carries, so its version cases live in that target.
test-schema-version:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_schema_version \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/canonical.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/arp_canonical.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/dhcpd_canonical.c \
		daemon/common/routing_canonical.c daemon/common/time_canonical.c \
		daemon/common/ipsec_canonical.c daemon/common/cert_canonical.c \
		daemon/common/auth_canonical.c daemon/common/gateways_canonical.c \
		daemon/common/remotelog_canonical.c daemon/common/alerts_canonical.c \
		daemon/common/sysctl_canonical.c daemon/common/identity_canonical.c \
		daemon/common/system_canonical.c \
		daemon/common/egress_url.c daemon/common/yaml_lite.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/pppoe_drift.c daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/test/test_schema_version.c ${LDLIBS}
	./test_schema_version

test-pf:
	${CC} ${CFLAGS} -o test_pf \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_pf.c ${LDLIBS}
	./test_pf

# Phase 5.1 canonical dns.yaml fragment: parse/validate/serialize
# round-trip, render golden, extra: allowlist, config-dir manifest.
test-dns:
	${CC} ${CFLAGS} -o test_dns \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_dns.c ${LDLIBS}
	./test_dns

# Phase NTP canonical time.yaml: parse/validate/serialize round-trip,
# validation rejects (incl. tz traversal + wildcard listen), ntpd.conf render
# goldens + allowlist scan, the public-listen amplification classifier, the
# get_time JSON shape, and the ntpctl status parser. Pure (no exec executed).
test-time:
	${CC} ${CFLAGS} -o test_time \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/time_canonical.c daemon/common/json_time.c \
		daemon/common/drift.c \
		daemon/timed/probe.c daemon/timed/ntpd.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_time.c ${LDLIBS}
	./test_time

# 10.C8 D1 timed machine-path no-op apply: drives the REAL timed apply core
# (render -> staged fake `ntpd -n` -> snapshot -> D1 predicate -> fake rcctl
# reconcile -> localtime swap -> canonical + revision) in a per-uid scratch
# tree. Arms: byte-identical machine no-op (zero mutating execs, canonical
# still saved), zone-mismatch (the timed AND term) => full actuation +
# syslogd zone restart, operator identical re-apply always actuates.
test-timed-apply:
	${CC} ${CFLAGS} -I daemon/timed -o test_timed_apply \
		-DOGMA_TEST_ROOT='"/tmp/ogma-timed-apply-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_TIME_STAGING_VALIDATE='OGMA_TEST_ROOT "/staging-time-validate"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/gw-state"' \
		-DOGMA_NTPD_CONF='OGMA_TEST_ROOT "/etc/ntpd.conf"' \
		-DOGMA_LOCALTIME='OGMA_TEST_ROOT "/etc/localtime"' \
		-DOGMA_LOCALTIME_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_ZONEINFO_DIR='OGMA_TEST_ROOT "/zoneinfo"' \
		-DOGMA_LOG_TIMED='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_timed.sh"' \
		-DOGMA_NTPD='"daemon/test/fake_ntpd_check.sh"' \
		-DOGMA_NTPCTL='"daemon/test/fake_ntpd_check.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/time_canonical.c daemon/common/json_time.c \
		daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/apply_lock.c daemon/common/apply_noop.c \
		daemon/common/boot_drift.c \
		daemon/timed/apply.c daemon/timed/ntpd.c daemon/timed/probe.c \
		daemon/timed/audit.c \
		daemon/test/test_timed_apply.c ${LDLIBS}
	chmod +x daemon/test/fake_rcctl_timed.sh daemon/test/fake_ntpd_check.sh
	-chmod -R u+rwx /tmp/ogma-timed-apply-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-timed-apply-`id -u` /tmp/ogma-timed-fake-`id -u`
	OGMA_FAKE_TIMED_DIR=/tmp/ogma-timed-fake-`id -u` ./test_timed_apply

# Phase 9.O5 canonical remotelog.yaml + alerts.yaml: parse/serialize/validate
# round-trip + byte-stability, the validate accept/reject matrix (proto/facilities/
# email charset, supported_v1 rejects failover+neighbor + webhook), the syslogd.conf
# splice (base lines preserved, block replace vs append), the public-target
# classifier, and the size caps. Pure (no exec/FS) — links COMMON + the two new
# canonical fragments.
# Phase 9.R2.c cross-peer config sync: HMAC-SHA256 RFC 4231 KAT (incl. key>block),
# the canonical string + constant-time compare, every pure validator, hasync.yaml
# round-trip, and the filtered-bundle export->validate gate (kind/whitelist/net-
# exclusion/sha). Links COMMON's hasync.c + cap_hmac.c (the HMAC core) + the
# backup stack (for export/validate). Host-portable — NO exec/apply, so it runs in
# the STRICT Linux CI c-tests tier (LDLIBS=-lmd for sha2). The libtls helper +
# egress child are NOT linked (network/OpenBSD-only).
test-hasync:
	-mkdir -p /tmp/ogma-hasync-`id -u`/auth /tmp/ogma-hasync-`id -u`/staging-web
	${CC} ${CFLAGS} -DOGMA_TEST_APPLY_LOCK -DOGMA_HAVE_IFCONFIG \
		-DOGMA_DB_DIR='"/tmp/ogma-hasync-'`id -u`'"' \
		-DOGMA_STAGING_WEB_DIR='"/tmp/ogma-hasync-'`id -u`'/staging-web"' -o test_hasync \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/revision.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/routing_canonical.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/gateways_canonical.c \
		daemon/common/backup_apply.c daemon/common/apply_lock.c \
		daemon/common/backup_snap.c \
		daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/hasync.c daemon/common/cap_hmac.c \
		daemon/common/egress_url.c \
		daemon/test/test_hasync.c ${LDLIBS}
	./test_hasync

test-alertd:
	${CC} ${CFLAGS} -o test_alertd \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/time_canonical.c \
		daemon/common/remotelog_canonical.c daemon/common/alerts_canonical.c \
		daemon/common/egress_url.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_alertd.c ${LDLIBS}
	./test_alertd
	@# VD-E3-16 (the R24 form, anchored on the call so prose can never
	@# satisfy it): alertd's webhook bearers + export_audit body must never
	@# reach a core file.
	grep -q 'setrlimit(RLIMIT_CORE' daemon/alertd/main.c
	@# ...and the zeroing-daemon census the support bundle's collection.json
	@# note states stays true at the NAME level, both directions: the set of
	@# callers is pinned literally (glob expansion is sorted on both legs),
	@# and the note string itself is pinned so a revert of either side reds.
	[ "`grep -l 'setrlimit(RLIMIT_CORE' daemon/*/main.c | tr '\n' ' '`" = 'daemon/alertd/main.c daemon/authd/main.c daemon/ipsecd/main.c daemon/netd/main.c daemon/routed/main.c ' ]
	grep -q 'authd, netd, ipsecd, alertd, routed' daemon/authd/support_bundle.c

# 10.C12 exec-helper convergence: the single-sourced egress primitives + reserved
# exit-code enum (common/egress_url.c + egress_exit.h). Host-portable — the libtls
# helpers are OpenBSD-only, but the EXTRACTED pure-C gates are tested here. Links
# the alertd canonical closure (for ogma_webhook_url_ok / ogma_webhook_tls_path_ok,
# which now route through egress_url.c) + the SUT.
test-egress:
	${CC} ${CFLAGS} -DOGMA_EGRESS_STREAM -o test_egress \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/time_canonical.c \
		daemon/common/remotelog_canonical.c daemon/common/alerts_canonical.c \
		daemon/common/egress_url.c daemon/common/egress_http.c \
		daemon/common/egress_gunzip.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_egress.c ${LDLIBS} -lz
	./test_egress

# 10.C8 D1 alertd remotelog machine-path no-op apply: drives the REAL
# remotelog apply core (splice -> snapshot -> D1 predicate -> flags pin ->
# live write -> fake rcctl restart) in a per-uid scratch tree. Arms:
# byte-identical machine no-op (zero mutating execs, canonical still saved),
# not-running => restart survives, syslogd_flags mismatch => full actuation,
# operator identical re-apply always actuates.
test-alertd-apply:
	${CC} ${CFLAGS} -I daemon/alertd -o test_alertd_apply \
		-DOGMA_TEST_ROOT='"/tmp/ogma-alertd-apply-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_SYSLOGD_CONF='OGMA_TEST_ROOT "/etc/syslog.conf"' \
		-DOGMA_SYSLOGD_CONF_LEGACY='OGMA_TEST_ROOT "/etc/syslogd.conf"' \
		-DOGMA_SYSLOGD_CONF_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_LOG_ALERTD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/run"' \
		-DOGMA_LOG_ROUTED='OGMA_TEST_ROOT "/routed.log"' \
		-DOGMA_LOG_DIAGD='OGMA_TEST_ROOT "/diagd.log"' \
		-DOGMA_LOGSRC_AUTHLOG='OGMA_TEST_ROOT "/authlog"' \
		-DOGMA_LOG_AUTHD='OGMA_TEST_ROOT "/authd.log"' \
		-DOGMA_LOG_TIMED='OGMA_TEST_ROOT "/timed.log"' \
		-DOGMA_LOG_NETD='OGMA_TEST_ROOT "/netd.log"' \
		-DOGMA_LOG_RTD='OGMA_TEST_ROOT "/rtd.log"' \
		-DOGMA_LOG_PFD='OGMA_TEST_ROOT "/pfd.log"' \
		-DOGMA_LOG_DHCPD='OGMA_TEST_ROOT "/dhcpd.log"' \
		-DOGMA_LOG_DNSD='OGMA_TEST_ROOT "/dnsd.log"' \
		-DOGMA_LOG_HEALTHD='OGMA_TEST_ROOT "/healthd.log"' \
		-DOGMA_LOG_ARPD='OGMA_TEST_ROOT "/arpd.log"' \
		-DOGMA_LOG_LOGD='OGMA_TEST_ROOT "/logd.log"' \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/sysd.log"' \
		-DOGMA_LOG_IPSECD='OGMA_TEST_ROOT "/ipsecd.log"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_alertd.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/time_canonical.c \
		daemon/common/remotelog_canonical.c daemon/common/alerts_canonical.c \
		daemon/common/egress_url.c \
		daemon/common/log_lines.c daemon/common/yaml_lite.c \
		daemon/common/apply_lock.c daemon/common/apply_noop.c \
		daemon/common/boot_drift.c \
		daemon/common/drift.c \
		daemon/logd/logread.c daemon/logd/audit.c \
		daemon/alertd/apply.c daemon/alertd/syslogd.c \
		daemon/alertd/notify.c daemon/alertd/audit.c \
		daemon/test/test_alertd_apply.c ${LDLIBS}
	chmod +x daemon/test/fake_rcctl_alertd.sh
	-chmod -R u+rwx /tmp/ogma-alertd-apply-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-alertd-apply-`id -u` /tmp/ogma-alertd-fake-`id -u`
	OGMA_FAKE_ALERTD_DIR=/tmp/ogma-alertd-fake-`id -u` ./test_alertd_apply

# Phase 10.A1 (L8-01): the pure `df -ki` worst-of-block/inode parser, in
# isolation (no forked df, no alertd deps) — the disk_full inode-axis arithmetic.
test-alert-df:
	${CC} ${CFLAGS} -I daemon/alertd -o test_alert_df \
		daemon/alertd/df_parse.c \
		daemon/test/test_alert_df.c ${LDLIBS}
	./test_alert_df

# 10.E7.2 S3 tail (VD-E7-2-4/-6/-1): the config_drift collector's
# K-consecutive-walks hysteresis (daemon/alertd/cfgdrift_hyst.c) -- the
# per-domain cross-tick state contract Sec.2.8 requires. Pure C, libc only: the
# collector itself (metrics.c) is linked by the alertd build alone and has no
# harness, so the state machine lives in its own module precisely so this
# target can pin it: walks-not-ticks counting, the K boundary, reset on an
# evaluated walk, keep across a stale spell, prune on absence, and every
# fail-safe bound (pool/walk-table exhaustion surfaces, never silences).
test-alertd-cfgdrift:
	${CC} ${CFLAGS} -I daemon/alertd -o test_alertd_cfgdrift \
		daemon/alertd/cfgdrift_hyst.c \
		daemon/test/test_alertd_cfgdrift.c ${LDLIBS}
	./test_alertd_cfgdrift

# 10.C2 S6 (L1-15): the alertd exec-capture watchdog in isolation (capture.c
# links alone — the df_parse.c lean-TU precedent). Pins the FAIL-CLOSED
# contract: clean EOF -> 0; buffer-full / deadline cut / missing binary /
# empty output -> -1; stderr still folded into a successful capture.
test-alertd-capture:
	${CC} ${CFLAGS} -I daemon/alertd -o test_alertd_capture \
		daemon/alertd/capture.c \
		daemon/test/test_alertd_capture.c ${LDLIBS}
	./test_alertd_capture

# Phase 9.O3: canonical cert.yaml parse/serialize/validate + byte-stable
# round-trip + omit-when-unset, the ACME-domain charset gate, and the openssl
# x509 output parser (multi-SAN, EC, expired, SAN de-dup). Pure (no exec).
# 10.C13 (L3-06) sysctl.yaml + identity.yaml canonicals: parse/serialize
# round-trip + byte-stability, group optionality/completeness, range REJECTS,
# fqdn charset rejects, the strip-block fail-safe contract, and render->strip
# convergence. Pure (no exec/FS).
test-sysctl-canonical:
	${CC} ${CFLAGS} -o test_sysctl_canonical \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/identity_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_sysctl_canonical.c ${LDLIBS}
	./test_sysctl_canonical

test-cert-canonical:
	${CC} ${CFLAGS} -o test_cert_canonical \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/cert_canonical.c \
		daemon/common/cert_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_cert_canonical.c ${LDLIBS}
	./test_cert_canonical

# Phase 7b.3.1 canonical ipsec.yaml fragment: parse/serialize/validate
# round-trip + byte-stability, the validate accept/reject matrix, the
# id/secret-value/proposal/cidr-overlap primitives, and the iked.conf render
# (redacted vs keyed, fail-closed, whole-line scan). Pure (no exec/FS).
# 10.G1.8 (VD-D2-2): the render size-bound NEGATIVE. Clones the
# test-ipsec-canonical link set and adds the build-time under-count injection,
# so both ipsec size bounds return a deliberate short value and the renders MUST
# refuse (10.D2 S2 measured that they silently realloc'd instead — the whole
# point of the row). -DOGMA_IPSEC_RENDER_BOUND_TEST is the fence: without it,
# ipsec_canonical.c #errors on the SHRINK macro, so a stray -D in CFLAGS cannot
# reach a production build. This is the ONLY recipe that may define either.
# The CONTROL — the same fixtures rendering 0 un-shrunk — lives in
# test-ipsec-canonical, sharing daemon/test/ipsec_render_bound_fixture.h so the
# two cannot drift apart.
test-ipsec-render-bound:
	${CC} ${CFLAGS} -DOGMA_IPSEC_RENDER_BOUND_TEST \
		-DOGMA_IPSEC_RENDER_BOUND_SHRINK=8 \
		-I daemon/test -o test_ipsec_render_bound \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_ipsec_render_bound.c ${LDLIBS}
	./test_ipsec_render_bound

test-ipsec-canonical:
	${CC} ${CFLAGS} -I daemon/test -o test_ipsec_canonical \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_ipsec_canonical.c ${LDLIBS}
	./test_ipsec_canonical

# Phase 7b.4.1 pure canonical for the ipsecctl plane (IKEv1 isakmpd + manual
# keying): parse/serialize/validate round-trip + byte-stability, the D1 plane
# exclusion + validate reject matrix, the hex-key / ikev1-id validators, the
# /etc/ipsec.conf render (redacted vs keyed, length-correct hex sentinel,
# fail-closed, single-line scan), and the D11 selector enumerator. Clones the
# test-ipsec-canonical link set (COMMON + ipsec_canonical.c + validate.c).
test-ipsec-ikev1:
	${CC} ${CFLAGS} -o test_ipsec_ikev1 \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_ipsec_ikev1.c ${LDLIBS}
	./test_ipsec_ikev1

# 10.F4 S4 (D8): the C leg of the YAML round-trip fixture bridge. Reads the
# committed goldens under ci/yaml-roundtrip/ from the checkout (repo-root
# relative, the test_rtd.c pattern) and proves the real canonical parser and
# serializer agree with the same bytes the web composer is proven to emit by
# t/yaml_roundtrip.t. Test-only C: contract Sec.5 pins zero production C in F4.
# Registered in ci/sanitized-targets.txt -- it execs nothing and reads only
# committed text, so it runs strict under ASan/UBSan on the blocking leg.
test-yaml-roundtrip:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_yaml_roundtrip \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/canonical.c \
		daemon/common/pppoe_drift.c daemon/common/carp_drift.c \
		daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/yaml_lite.c \
		daemon/common/dhcpd_canonical.c \
		daemon/common/gateways_canonical.c \
		daemon/common/routing_canonical.c \
		daemon/common/alerts_canonical.c daemon/common/egress_url.c \
		daemon/common/remotelog_canonical.c \
		daemon/common/time_canonical.c \
		daemon/common/cert_canonical.c \
		daemon/test/test_yaml_roundtrip.c ${LDLIBS}
	./test_yaml_roundtrip

# Phase 7b.3.4 PF orchestration (ogma_ipsec_pf_suggestion): the suggested pf.conf
# computation (IKE/ESP/enc0 + road-warrior nat-to keyed off the iked tag), the
# tag-byte-identity invariant between the iked render and the suggestion (R28/D9),
# and forwarding_required. Pure (no exec/FS) — clones the test-ipsec-canonical
# link set, swapping the test file.
test-ipsec-pf:
	${CC} ${CFLAGS} -o test_ipsec_pf \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_ipsec_pf.c ${LDLIBS}
	./test_ipsec_pf

# Phase 7b.3.2d PURE D6 management-path gate (ipsec_gate.c): the false-negative
# matrix (both flow sides, reverse containment, default gateway, transport/AH +
# passive hard-rejects, config_address overlap, v6, mgmt-unknown conservative).
# No I/O — builds configs in-struct and runs the verdict.
test-ipsec-gate:
	${CC} ${CFLAGS} -o test_ipsec_gate \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/ipsec_gate.c \
		daemon/test/test_ipsec_gate.c ${LDLIBS}
	./test_ipsec_gate

# Phase 7b.3.2c durable refs-only commit marker (ipsec_txn.c). Runs against a
# per-uid /tmp scratch tree (OGMA_STAGING_DIR -D'd so OGMA_IKED_PENDING_DIR
# follows): create/find/read/candidate/flows/snapshot/commit/one-marker/escape.
test-ipsec-txn:
	${CC} ${CFLAGS} -o test_ipsec_txn \
		-DOGMA_IPSEC_TXN_TEST_ROOT='"/tmp/ogma-ipsec-txn-'`id -u`'"' \
		-DOGMA_STAGING_DIR='OGMA_IPSEC_TXN_TEST_ROOT "/staging"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/ipsec_txn.c daemon/common/yaml_lite.c \
		daemon/test/test_ipsec_txn.c ${LDLIBS}
	./test_ipsec_txn

# Phase 7b.3.2c ipsecd commit-confirmed window (ipsec_pending.c): marker/timer/
# recovery/confirm-cancel + the revert (restore prior conf -> stop+ipsecctl -F
# kernel flush -> restart/stop -> rc boot-intent -> route reassert). Runs the REAL
# ipsec_txn marker + ipsec_pending state machine against fake iked/ipsecctl/rcctl/
# route and a per-uid /tmp scratch tree (no root, no /etc). The fakes record their
# calls so the test proves the revert flushed the kernel (R1) and that no key/
# payload byte ever reaches the audit log. -DOGMA_IPSECD_TEST_HOOKS exposes the
# deadline-expiry hook; the fakes' paths override the exec binaries.
test-ipsec-pending:
	${CC} ${CFLAGS} -o test_ipsec_pending \
		-DOGMA_IPSECD_TEST_HOOKS \
		-DOGMA_IKED='"daemon/test/fake_iked.sh"' \
		-DOGMA_IPSECCTL='"daemon/test/fake_ipsecctl.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_ipsec.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_IPSEC_PEND_ROOT='"/tmp/ogma-ipsec-pending-'`id -u`'"' \
		-DOGMA_STAGING_DIR='OGMA_IPSEC_PEND_ROOT "/staging"' \
		-DOGMA_CONFIG_DIR='OGMA_IPSEC_PEND_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_IPSEC_PEND_ROOT "/revisions"' \
		-DOGMA_IKED_CONF='OGMA_IPSEC_PEND_ROOT "/iked.conf"' \
		-DOGMA_IKED_PREADOPT='OGMA_IPSEC_PEND_ROOT "/iked.conf.preadopt"' \
		-DOGMA_IPSEC_CONF='OGMA_IPSEC_PEND_ROOT "/ipsec.conf"' \
		-DOGMA_ISAKMPD_FIFO='OGMA_IPSEC_PEND_ROOT "/isakmpd.fifo"' \
		-DOGMA_IKED_STAGING_VALIDATE='OGMA_IPSEC_PEND_ROOT "/validate"' \
		-DOGMA_LOG_IPSECD='OGMA_IPSEC_PEND_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/ipsec_txn.c daemon/common/apply_lock.c \
		daemon/common/yaml_lite.c \
		daemon/ipsecd/iked.c daemon/ipsecd/audit.c \
		daemon/ipsecd/ipsec_pending.c daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/test/test_ipsec_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_iked.sh daemon/test/fake_ipsecctl.sh \
		daemon/test/fake_rcctl_ipsec.sh daemon/test/fake_route.sh
	./test_ipsec_pending
	@# R24: the keyed in-RAM prior_conf must never reach a core file.
	grep -q 'RLIMIT_CORE' daemon/ipsecd/main.c

# Phase 7b.3.2d gated live apply (apply_set.c): drives ogma_ipsecd_set_ipsec
# against fake iked/ipsecctl/rcctl/route + a seeded canonical net.yaml (mgmt
# subnet) and fake default route (gateway). Asserts non-gated commit, gated
# window+confirm, confirm_wan_ipsec enforcement, the transport hard-reject, the
# carrier fail-closed, and no PSK in the audit. OGMA_HAVE_IFCONFIG so canonical.c
# links.
# 10.E7.2 S3b: OGMA_GW_STATE_DIR is redirected into the scratch tree so the two
# published drift records (OGMA_BOOT_DRIFT_DIR derives from it) land there
# instead of the real /var/run — the S3a test-timed-apply precedent.
test-ipsec-apply:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_ipsec_apply \
		-DOGMA_IPSECD_TEST_HOOKS \
		-DOGMA_IKED='"daemon/test/fake_iked.sh"' \
		-DOGMA_IKECTL='"daemon/test/fake_ikectl.sh"' \
		-DOGMA_IPSECCTL='"daemon/test/fake_ipsecctl.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_ipsec.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_IPSEC_APPLY_ROOT='"/tmp/ogma-ipsec-apply-'`id -u`'"' \
		-DOGMA_STAGING_DIR='OGMA_IPSEC_APPLY_ROOT "/staging"' \
		-DOGMA_CONFIG_DIR='OGMA_IPSEC_APPLY_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_IPSEC_APPLY_ROOT "/revisions"' \
		-DOGMA_IKED_CONF='OGMA_IPSEC_APPLY_ROOT "/iked.conf"' \
		-DOGMA_IKED_PREADOPT='OGMA_IPSEC_APPLY_ROOT "/iked.conf.preadopt"' \
		-DOGMA_IPSEC_CONF='OGMA_IPSEC_APPLY_ROOT "/ipsec.conf"' \
		-DOGMA_ISAKMPD_FIFO='OGMA_IPSEC_APPLY_ROOT "/isakmpd.fifo"' \
		-DOGMA_IKED_STAGING_VALIDATE='OGMA_IPSEC_APPLY_ROOT "/validate"' \
		-DOGMA_IKED_PRIVATE_DIR='OGMA_IPSEC_APPLY_ROOT "/iked-private"' \
		-DOGMA_SSL_DIR='OGMA_IPSEC_APPLY_ROOT "/ssl"' \
		-DOGMA_LOG_IPSECD='OGMA_IPSEC_APPLY_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_IPSEC_APPLY_ROOT "/gw-state"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/drift.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/ipsec_gate.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/ipsec_txn.c daemon/common/apply_lock.c \
		daemon/common/apply_noop.c \
		daemon/common/yaml_lite.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/ipsecd/iked.c daemon/ipsecd/audit.c daemon/ipsecd/reload_additions.c \
		daemon/ipsecd/ipsec_pending.c daemon/ipsecd/gate_inputs.c \
		daemon/ipsecd/apply_set.c daemon/ipsecd/apply.c daemon/ipsecd/pki.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/common/boot_drift.c \
		daemon/test/test_ipsec_apply.c ${LDLIBS}
	chmod +x daemon/test/fake_iked.sh daemon/test/fake_ipsecctl.sh \
		daemon/test/fake_rcctl_ipsec.sh daemon/test/fake_route.sh \
		daemon/test/fake_ikectl.sh
	./test_ipsec_apply

# Phase 7b.3.3 IPsec PKI driver (ipsecd/pki.c): the ikectl exec drivers against
# a fake ikectl + per-uid scratch tree. Asserts R19 export-exclusion, no CA
# passphrase on argv (stdin only, twice), the fixed driver argv shapes, the
# stat-based key-mode scan, the certs_preprovisioned validate matrix (R18),
# ca_name path-safety, and the carrier egress-gate fix (§6.3.1).
test-ipsec-pki:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_ipsec_pki \
		-DOGMA_IPSECD_TEST_HOOKS \
		-DOGMA_IKED='"daemon/test/fake_iked.sh"' \
		-DOGMA_IKECTL='"daemon/test/fake_ikectl.sh"' \
		-DOGMA_IPSECCTL='"daemon/test/fake_ipsecctl.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_ipsec.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_IPSEC_PKI_ROOT='"/tmp/ogma-ipsec-pki-'`id -u`'"' \
		-DOGMA_STAGING_DIR='OGMA_IPSEC_PKI_ROOT "/staging"' \
		-DOGMA_CONFIG_DIR='OGMA_IPSEC_PKI_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_IPSEC_PKI_ROOT "/revisions"' \
		-DOGMA_IKED_CONF='OGMA_IPSEC_PKI_ROOT "/iked.conf"' \
		-DOGMA_IKED_PREADOPT='OGMA_IPSEC_PKI_ROOT "/iked.conf.preadopt"' \
		-DOGMA_IKED_STAGING_VALIDATE='OGMA_IPSEC_PKI_ROOT "/validate"' \
		-DOGMA_IKED_PRIVATE_DIR='OGMA_IPSEC_PKI_ROOT "/iked-private"' \
		-DOGMA_SSL_DIR='OGMA_IPSEC_PKI_ROOT "/ssl"' \
		-DOGMA_LOG_IPSECD='OGMA_IPSEC_PKI_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c daemon/common/ipsec_canonical.c \
		daemon/common/ipsec_gate.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/ipsec_txn.c daemon/common/apply_lock.c \
		daemon/common/apply_noop.c \
		daemon/common/yaml_lite.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/ipsecd/iked.c daemon/ipsecd/audit.c daemon/ipsecd/reload_additions.c \
		daemon/ipsecd/ipsec_pending.c daemon/ipsecd/gate_inputs.c \
		daemon/ipsecd/apply_set.c daemon/ipsecd/pki.c daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/test/test_ipsec_pki.c ${LDLIBS}
	chmod +x daemon/test/fake_ikectl.sh daemon/test/fake_iked.sh \
		daemon/test/fake_ipsecctl.sh daemon/test/fake_rcctl_ipsec.sh \
		daemon/test/fake_route.sh
	./test_ipsec_pki

# Phase 7b.3.3 authd PKI handler (authd/ipsec_pki.c): the create_ca crash-safety
# (rollback deletes the generated passphrase + restores canonical => no phantom
# CA), the idempotence guard, and the dual-perm/host gates. The proxy/canonical/
# rbac/rate/audit layers are stubbed (the test_authd_wg model); the real secret
# store + value policy run.
test-authd-ipsec-pki:
	${CC} ${CFLAGS} -o test_authd_ipsec_pki \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-ipsec-pki-'`id -u`'/secrets"' \
		-Wl,--wrap=ogma_secret_read -Wl,--wrap=ogma_secret_exists \
		daemon/authd/ipsec_pki.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c \
		daemon/test/test_authd_ipsec_pki.c ${LDLIBS}
	./test_authd_ipsec_pki

# Phase 6.1 canonical dhcp.yaml fragment: parse/validate/serialize
# round-trip, validation rejects, dhcpd.conf render golden.
test-dhcp-server:
	${CC} ${CFLAGS} -o test_dhcp_server \
		-DOGMA_DHCPD_LEASES_TEST_DIR='"/tmp/ogma-dhcpd-leases-test-'`id -u`'"' \
		-DOGMA_DHCPD_LEASES='OGMA_DHCPD_LEASES_TEST_DIR "/dhcpd.leases"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/dhcpd_canonical.c \
		daemon/common/json_dhcpd.c daemon/common/dhcpd_leases.c \
		daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_dhcp_server.c ${LDLIBS}
	./test_dhcp_server

# Phase 7b.0 / Gate-0a secrets store: secret_ref slug validation (accept +
# reject traversal/charset/high-byte/leading-dot/too-long), base64 vectors, the
# no-value audit-token scan, and the generate->read->rotate->set->list->prune->
# delete round-trip (arc4random_buf, 0600 files, explicit_bzero). Per-uid scratch
# store (the test-auth-db precedent) so it never touches the real /var/db tree.
test-secrets:
	${CC} ${CFLAGS} -o test_secrets \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-secrets-'`id -u`'/secrets"' \
		daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c \
		daemon/test/test_secrets.c ${LDLIBS}
	./test_secrets

# Phase 7b.0 authd handler layer (ogma_authd_secret_op): drives the six secret
# ops directly with audit + rate-limit stubbed, pinning the A3 invariants — the
# value never appears in a response or audit detail, reject paths are generic,
# the rate-limit denial is audited + skips the success record, and responses
# carry only the ref. Per-uid scratch store (the test-secrets precedent).
test-authd-secrets:
	${CC} ${CFLAGS} -o test_authd_secrets \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-secrets-'`id -u`'/secrets"' \
		-DOGMA_MAX_SECRETS=8 \
		daemon/authd/secrets.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c \
		daemon/test/test_authd_secrets.c ${LDLIBS}
	./test_authd_secrets

# 10.B1 D1/D2/S1 front-door primitives (daemon/authd/front_door.c): the drain-
# framing matrix (fragmented assemble / '\n' terminate / EOF / OVERFLOW /
# cumulative TIMEOUT) over a socketpair, the per-uid token bucket + in-flight +
# root-exemption + audit-window on an INJECTED clock (now_ms is a parameter, so
# the caps are deterministic), and the S1 bounded write. Links front_door.c +
# util.c (ogma_mono_ms) only — NO sqlite — so it runs on the Linux c-tests leg.
# Phase 10.B1 D4 fold: the shared daemon loop's request path (daemon_base.c),
# driven through the file-static handle_client over a socketpair with EVERY
# external stubbed (no parser/cap/db link — libc only). Pins split-before-parse
# (the R2-1 BLOCKER canary), the authd skip_cap_verify branch + peer_uid
# threading, the fragment ping/cap/idempotency paths, the parse-fail emit_reply,
# and the read_frame/write_reply hooks. daemon_base.c is #included by the test.
# 10.E7.2 S1: OGMA_GW_STATE_DIR is redirected into a per-uid /tmp root so the
# config-drift KILL SWITCH can be asserted BOTH ways (present and absent)
# against a real file without touching /var/run — the same redirect the
# test-boot-drift target uses, and the OGMA_LOCALTIME precedent.
test-daemon-base:
	${CC} ${CFLAGS} -o test_daemon_base \
		-DOGMA_GW_STATE_DIR='"/tmp/ogma-daemon-base-'`id -u`'"' \
		daemon/common/frame.c \
		daemon/test/test_daemon_base.c ${LDLIBS}
	rm -rf /tmp/ogma-daemon-base-`id -u`
	./test_daemon_base
	rm -rf /tmp/ogma-daemon-base-`id -u`
	@# VD-E3-22, two-sided: every reply free in the shared loop goes through
	@# the scrub helper — exactly 5 call sites (parse-fail json, out +
	@# json_data on the emit-fail arm, out + json_data on the normal arm),
	@# anchored on the statement shape so comments can't satisfy or inflate
	@# it — and no plain free of a reply pointer survives anywhere in the
	@# file (statement-anchored for the same reason).
	[ "`grep -c '^[[:space:]]*ogma_free_scrubbed(' daemon/common/daemon_base.c`" -eq 5 ]
	! grep -E '^[[:space:]]*(\(void\))?free\((out|json|resp\.json_data)\)' daemon/common/daemon_base.c

# 10.C2: the framed transport primitive (ogma_frame_read_auto / format_header) —
# framed round-trip, multi-read reassembly, TRUNCATED/OVERFLOW/BAD_MAGIC/
# BAD_VERSION fail-closed, the strict header lexer, and legacy auto-detect. Pure
# libc over a socketpair (host-portable, on the CI c-tests leg).
test-frame:
	${CC} ${CFLAGS} -o test_frame \
		daemon/common/frame.c \
		daemon/test/test_frame.c ${LDLIBS}
	./test_frame

# 10.C2 S7: the shared framed lateral client (ogma_lateral_call) — framed
# round-trip against a real forked AF_UNIX server (coalesced/split/dribbled),
# legacy-reply auto-detect + its fail-closed arms (no-'\n' budget-full, EOF
# mid-line), framed fail-closed (torn body, oversize pre-read, bad magic with a
# valid header tail, bad version), NO_LISTENER vs TIMEOUT classification, and
# the cumulative-deadline cut a per-read timer cannot deliver. util.c's link
# cohort mirrors test-authd-frontdoor (secret/validate/net_l2).
test-lateral:
	${CC} ${CFLAGS} -o test_lateral \
		daemon/common/lateral.c daemon/common/frame.c \
		daemon/common/secret.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c \
		daemon/test/test_lateral.c ${LDLIBS}
	./test_lateral

test-authd-frontdoor:
	${CC} ${CFLAGS} -I daemon/authd -o test_authd_frontdoor \
		daemon/authd/front_door.c daemon/common/frame.c \
		daemon/common/secret.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c \
		daemon/test/test_authd_frontdoor.c ${LDLIBS}
	./test_authd_frontdoor

# 10.C4 S0: the authd background-job engine end-to-end with REAL processes —
# the test forks the real job-runner supervisor, which forks real synthetic
# sleep-and-echo workers: submit/poll/cancel(+group-kill)/watchdog/grace/
# global+per-actor caps/owner-scope/id-never-in-path/restart-loses-handle/
# secret-never-spooled (M13 golden)/supervisor-death-degraded (M18) against
# the production jobs.c + job_runner.c. -DOGMA_AUTHD_JOB_TEST enables the
# synthetic class + the reset/peek hooks (a production build submits NO
# class); MAX_JOBS=12 exceeds the per-actor cap (8) so the two rejections
# are separately provable; grace/ack are shrunk for test pace. Links the
# frontdoor util cohort + netd/audit.c (authd's audit writer) — NO sqlite,
# NO parser — so it runs on the Linux c-tests leg too (pledge/unveil are
# OpenBSD-gated; under OGMA_AUTHD_JOB_TEST the supervisor unveils only the
# redirected spool dir). The scratch tree is wiped first: a stale spool from
# a previous run would trip the workers' O_EXCL create.
test-authd-jobs:
	rm -rf /tmp/ogma-authd-jobs-test-`id -u` /tmp/ogma-authd-jobs-audit-`id -u`
	${CC} ${CFLAGS} -I daemon/authd -o test_authd_jobs \
		-DOGMA_AUTHD_JOB_TEST \
		-DOGMA_AUTHD_JOB_DIR='"/tmp/ogma-authd-jobs-test-'`id -u`'"' \
		-DOGMA_LOG_AUTHD='"/tmp/ogma-authd-jobs-audit-'`id -u`'"' \
		-DOGMA_AUTHD_MAX_JOBS=12 \
		-DOGMA_AUTHD_JOB_GRACE_SEC=2 \
		-DOGMA_AUTHD_JOB_ACK_MS=3000 \
		-DOGMA_AUTHD_JOB_RESULT_MAX=8192 \
		-DOGMA_STAGING_DIR='"/tmp/ogma-authd-jobs-test-'`id -u`'/staging"' \
		-DOGMA_STAGING_WEB_DIR='"/tmp/ogma-authd-jobs-test-'`id -u`'/web"' \
		daemon/authd/jobs.c daemon/authd/job_runner.c \
		daemon/netd/audit.c \
		daemon/common/secret.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c \
		daemon/test/test_authd_jobs.c ${LDLIBS}
	./test_authd_jobs
	./test_authd_jobs wedge
	@# VD-E3-18: the capture-mirror sweep needs a FRESH process (the 60 s
	@# sweep gate is a function-local static no hook resets, and any engine
	@# call arms it), so it is its own invocation like `wedge`.
	./test_authd_jobs sweep

# Phase 10.A5.5 fork-migration + reference-counted prune (daemon/authd/migrate.c).
# Prune-completeness (every ref field of every canonical, incl. disabled objects),
# the migration provenance gate + additive fork + idempotency, and the prune
# report/destructive + ipsec.exp./ha-sync-peer retention + fail-closed. The
# daemon-only externals (materialize/proxy/rbac/rate/audit/net loader) are stubbed
# in the test; the routing helpers (ospf/bgp ref composers, iface walk) are the
# REAL routing_canonical.c. Per-uid scratch store + an empty per-uid config dir
# (so no routing/ipsec/alerts canonical is present -> the prune live-set is exactly
# ha-sync-peer + the seeded ipsec.exp.*).
test-authd-migrate:
	${CC} ${CFLAGS} -o test_authd_migrate \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-migrate-'`id -u`'/secrets"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-authd-migrate-'`id -u`'/config"' \
		daemon/authd/migrate.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/config_manifest.c \
		daemon/common/routing_canonical.c daemon/common/yaml_lite.c \
		daemon/test/test_authd_migrate.c ${LDLIBS}
	./test_authd_migrate

# Phase 7b.1.2a authd WireGuard key-op handler (ogma_authd_wg_keyop): drives the
# generate/set ops with the proxy/audit/rate layer stubbed, pinning the A3
# invariants — a private key never reaches the response or the audit detail, the
# pubkey is response-only (never audited), reject paths are generic + no-echo,
# and the rate limit denies + skips the success record. Per-uid scratch store.
test-authd-wg:
	${CC} ${CFLAGS} -o test_authd_wg \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-wg-'`id -u`'/secrets"' \
		daemon/authd/wg.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c \
		daemon/test/test_authd_wg.c ${LDLIBS}
	./test_authd_wg

# Phase 10.A5.1 authd pppoe apply handler (daemon/authd/pppoe.c): the authkey_ref
# namespace binding ("pppoe.<iface>.authkey") is enforced fail-closed before any
# store read/materialize, the materialized PAP password never reaches the
# response or audit (A3), an empty req->interface binds on the matched iface, and
# authproto none touches no secret. Real net_l2.c linked; proxy/rbac/rate/audit
# stubbed (the test_authd_wg model). Per-uid scratch store.
test-authd-pppoe:
	${CC} ${CFLAGS} -o test_authd_pppoe \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-pppoe-'`id -u`'/secrets"' \
		daemon/authd/pppoe.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c \
		daemon/test/test_authd_pppoe.c ${LDLIBS}
	./test_authd_pppoe

# Phase 10 VD-A5-1: host-independent proof of the OSPF+BGP authd materialize
# chokepoint namespace binding (daemon/authd/{ospf,bgp}.c). Links the REAL
# ospf.c+bgp.c+routing_canonical.c parser/validator/composers and secret store;
# stubs only the RBAC gate + backup archive loader. `--wrap` on the store read/
# exists seam lets a reject case assert ZERO reads occurred (the L4-01 "no-read"
# property) and a positive case assert the wrapper is live. Per-uid scratch store
# + config dir (the toggle case writes/reads a bgp.yaml there).
test-authd-routing-binding:
	${CC} ${CFLAGS} -o test_authd_routing_binding \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-rbind-'`id -u`'/secrets"' \
		-DOGMA_CONFIG_DIR='"/tmp/ogma-authd-rbind-'`id -u`'/config"' \
		-Wl,--wrap=ogma_secret_read -Wl,--wrap=ogma_secret_exists \
		daemon/authd/ospf.c daemon/authd/bgp.c \
		daemon/common/routing_canonical.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_authd_routing_binding.c ${LDLIBS}
	./test_authd_routing_binding

# Phase 10 VD-A5-1: host-independent proof of the IPsec authd materialize
# chokepoint binding (daemon/authd/ipsec.c authd_ipsec_forward -> materialize_ref)
# + the restore-path archive-binding wrapper. Links the REAL ipsec.c + secret
# store + value policy + ogma_ipsec_config_binding_ok (secret.c); stubs the ipsec
# canonical parser/validator/gate, the net loader, the proxy, and the backup
# archive loaders so a test-built config drives the chokepoint. Per-uid scratch store.
test-authd-ipsec-binding:
	${CC} ${CFLAGS} -o test_authd_ipsec_binding \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-ibind-'`id -u`'/secrets"' \
		-Wl,--wrap=ogma_secret_read -Wl,--wrap=ogma_secret_exists \
		daemon/authd/ipsec.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c \
		daemon/test/test_authd_ipsec_binding.c ${LDLIBS}
	./test_authd_ipsec_binding

# 10.C11 S5a: the extracted authd dispatch route type + the three pre-dispatch
# gates (arp-publish / forwarding-disable / power-confirm) + the M17 ingress
# sanitize + the R-1 rate prelude + the M18 route census. LEAN link (M3): ONLY
# authd_route.c — every external dep (strlcpy/rbac/canonical/power/rate/audit/op
# registry) is a controllable stub IN the test, so the byte-verbatim gate/sanitize
# motion is pinned without the proxy/auth-db/util closure.
test-authd-route:
	${CC} ${CFLAGS} -I daemon/authd -o test_authd_route \
		daemon/authd/authd_route.c \
		daemon/test/test_authd_route.c ${LDLIBS}
	./test_authd_route

# Phase 10.A5.2 authd WireGuard apply handler (daemon/authd/wg.c:
# ogma_authd_set_wireguard): the private_key_ref ("wg.<iface>.privkey") and
# per-peer preshared_key_ref ("wg.<iface>.psk.<pubkey>") namespace bindings are
# enforced fail-closed before any store read/materialize, the materialized key
# values never reach the response or audit (A3), an empty req->interface binds on
# the matched iface, and a disabled peer's PSK is never resolved (empty-slot
# invariant). Real net_l2.c linked; proxy/rbac/rate/audit stubbed (the
# test_authd_wg model). Per-uid scratch store.
test-authd-wg-apply:
	${CC} ${CFLAGS} -o test_authd_wg_apply \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-authd-wg-apply-'`id -u`'/secrets"' \
		daemon/authd/wg.c daemon/common/secret.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c \
		daemon/test/test_authd_wg_apply.c ${LDLIBS}
	./test_authd_wg_apply

# Phase 7a.1 canonical routing.yaml fragment (ospfd): parse/validate/serialize
# round-trip, validation rejects, ospfd.conf render golden.
# 10.C3 S8 (VD-C3-1 close): -DOGMA_ROUTING_RENDER_TEST_HOOKS compiles the
# test-only cap-tightening seam (ogma_bgpd_render_cap_test_set) into
# routing_canonical.c so the OVERSIZE arms stay exercised by a REAL render now
# that the cap is priced above every validate-accepted fanout.
test-routing:
	${CC} ${CFLAGS} -DOGMA_ROUTING_RENDER_TEST_HOOKS -o test_routing \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/config_manifest.c \
		daemon/common/routing_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_routing.c ${LDLIBS}
	./test_routing
	@# VD-E3-16 (the R24 form, anchored on the call so prose can never
	@# satisfy it): routed's keyed rendered ospfd.conf/bgpd.conf must never
	@# reach a core file.
	grep -q 'setrlimit(RLIMIT_CORE' daemon/routed/main.c

# Phase 7c live status: the pure ospfctl/bgpctl parsers + JSON builders
# (routing_status.c) against captured fixtures + hostile inputs. The exec/capture
# is in routed/status.c and covered by the lab smoke; this pins the brittle
# header-driven / right-anchored parsing. Links json.c (ogma_json_escape) +
# routing_canonical.c (ogma_bgp_eff_max_prefix for the config join) — the
# test-iface-stats link set with routing_status.c swapped in.
test-routing-status:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_routing_status \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/routing_status.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_routing_status.c ${LDLIBS}
	./test_routing_status

# LS.3(c): status_run_capture_ex exact-fit-vs-overflow sentinel. test_routing_capture
# #includes daemon/routed/status.c to reach the file-static capture fn and stubs the
# 3 routed-only externs; fake_ctl emits an EXACT byte count (nothing on stderr) so the
# 65535-byte boundary — un-producible from a live ctl — is exercised deterministically.
# Link set mirrors test-routing-status (status.c is #included, NOT listed separately).
test-routing-capture:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o fake_ctl daemon/test/fake_ctl.c
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_routing_capture \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/routing_status.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_routing_capture.c ${LDLIBS}
	./test_routing_capture ./fake_ctl

# Phase 9.O6: pure ndp_list parser + JSON builder (daemon/common/diag_view.c).
# Closure mirrors test-routing-status (json.c pulls the common deps); diag_view.c
# swapped in for routing_status.c. No fork/exec — runs on every CI tier.
test-diag-view:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_diag_view \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/diag_view.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_diag_view.c ${LDLIBS}
	./test_diag_view

# PF state-table parser + JSON builders (pure; no exec, no fake_pfctl). Feeds
# captured `pfctl -vvs states` / `-s Sources` fixtures + hostile inputs and
# asserts fail-closed parsing, id/creatorid round-trip, counters-as-strings, the
# over-cap truncation flag, and the "available"/"killed" first-key proxy anchors.
# Mirrors the test-routing-status link set (pf_states.c swapped in); the live
# capture/kill is in pfd/pfctl.c and covered by the lab smoke.
test-pf-states:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_pf_states \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/pf_states.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_pf_states.c ${LDLIBS}
	./test_pf_states

# Phase 9.D2.3 live PF table-content viewer pure parser + JSON builder + the
# table-name/entry/host-addr validators. Fixtures model the LAB-PINNED
# `pfctl -t T -T show -v` grammar (plain vs counters table); asserts the
# last-record flush, the 0..4 counter fold, the cap+truncated flag, the
# "available" first-key proxy anchor, and the leading-'-' getopt-smuggle +
# host-only-no-CIDR validator rejects. Mirrors the test-pf-states link set.
test-pf-table:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_pf_table \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/pf_table.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_pf_table.c ${LDLIBS}
	./test_pf_table

# Phase 9.D2.4 brute-force watcher pure logic: the sshd-authlog source-IP
# extraction (incl. the H1 host-only-no-CIDR + H2 last-`from` log-injection
# defenses) and the bounded sliding-window counter. The privileged authlog read
# + pfctl ban exec live in pfd/bruteforce.c & pfd/pfctl.c (lab-smoke covered).
test-pf-bruteforce:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_pf_bruteforce \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/pf_bruteforce.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_pf_bruteforce.c ${LDLIBS}
	./test_pf_bruteforce

# Router log viewer pure parsers/builders (no exec, no file IO). test-log-lines
# feeds captured syslog fixtures + hostile inputs and asserts fail-closed parse,
# the program/severity/grep filter, redaction (and non-over-redaction), the
# control-byte sanitizer, the per-line + fragment caps, and the "available"
# first-key proxy anchor. test-pflog does the same for the decoded pflog text.
# The privileged tail read / tcpdump capture live in logd/logread.c & pfd/pfctl.c
# and are covered by the lab smoke. Mirrors the test-pf-states link set.
test-log-lines:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_log_lines \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/log_lines.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_log_lines.c ${LDLIBS}
	./test_log_lines

test-pflog:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_pflog \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/routing_canonical.c \
		daemon/common/pflog.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_pflog.c ${LDLIBS}
	./test_pflog

# Phase 5.2A dnsd read path: expansion builder + listen/static-IPv4
# cross-check + get_dns detail JSON. Pure (no exec); links the net/lease
# helpers the expansion uses. 5.3B adds the health-probe wire builder/
# parser and the adaptive classifier (probed via injected query fns —
# no socket I/O in the test).
test-dnsd:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_dnsd \
		-DOGMA_DHCPLEASectl='"daemon/test/fake_dhcpleasectl.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/json_dns.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dns_canonical.c daemon/common/yaml_lite.c \
		daemon/dnsd/expand.c daemon/dnsd/unbound.c \
		daemon/dnsd/probe.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/test/test_dnsd.c ${LDLIBS}
	chmod +x daemon/test/fake_dhcpleasectl.sh
	rm -rf /tmp/ogma-dnsd-fakedl-`id -u`
	OGMA_FAKE_DHCPLEASECTL_DIR=/tmp/ogma-dnsd-fakedl-`id -u` ./test_dnsd

# 10.A3.1a dnsd boot render-reconcile + confirm-window boot artifact: runs the
# REAL dnsd apply/pending pipeline (windowed set_dns arm, recover directions,
# the boot-reconcile decision table, the staging reaper) against fake rcctl/
# unbound-checkconf shims + a per-uid scratch tree (the test-pf-pending
# pattern; no root, no /var/unbound, no real unbound). The REAL dnsd audit.c
# is linked against a -D'd scratch OGMA_LOG_DNSD so CRITICAL boot_reconcile
# lines are asserted from the log. OGMA_DNS_STAGING_VALIDATE is -D'd to a
# SINGLE level under the test root (staging_mkdir creates one level only; in
# production the parent comes from ogma_dnsd_pre_sandbox_dirs). The dhcplease
# dir + hostname dir are -D'd to empty scratch dirs so load_expansion can
# never read the build host's real leases or /etc/hostname.* (which would
# make the render-reject case nondeterministic). The chmod pre-clean handles
# revision.c's sealed 0550 dirs (the test-routing-pending precedent).
test-dns-pending:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_dns_pending \
		-DOGMA_CONFIRM_TXN_TEST_HOOKS \
		-DOGMA_TEST_ROOT='"/tmp/ogma-dns-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_DNS_STAGING_VALIDATE='OGMA_TEST_ROOT "/staging-dns-validate"' \
		-DOGMA_UNBOUND_CONF='OGMA_TEST_ROOT "/unbound/unbound.conf"' \
		-DOGMA_DNS_PREADOPT='OGMA_TEST_ROOT "/config/unbound.conf.preadopt"' \
		-DOGMA_UNBOUND_ROOT_KEY='OGMA_TEST_ROOT "/unbound/root.key"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/hostname-none"' \
		-DOGMA_DHCPLEASE_DIR='OGMA_TEST_ROOT "/dhcpleased"' \
		-DOGMA_LOG_DNSD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/run"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_dns.sh"' \
		-DOGMA_UNBOUND_CHECKCONF='"daemon/test/fake_unbound_checkconf.sh"' \
		-DOGMA_UNBOUND_ANCHOR='"daemon/test/fake_unbound_checkconf.sh"' \
		-DOGMA_DHCPLEASectl='"daemon/test/fake_dhcpleasectl.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/json_dns.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dns_canonical.c daemon/common/yaml_lite.c \
		daemon/common/confirm_txn.c daemon/common/apply_lock.c \
		daemon/common/apply_noop.c \
		daemon/dnsd/expand.c daemon/dnsd/unbound.c \
		daemon/dnsd/probe.c daemon/dnsd/apply.c \
		daemon/dnsd/dns_pending.c daemon/dnsd/audit.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/boot_drift.c \
		daemon/test/test_dns_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_rcctl_dns.sh daemon/test/fake_unbound_checkconf.sh daemon/test/fake_dhcpleasectl.sh
	-chmod -R u+rwx /tmp/ogma-dns-pending-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-dns-pending-`id -u` /tmp/ogma-dns-fake-`id -u`
	OGMA_FAKE_DNS_DIR=/tmp/ogma-dns-fake-`id -u` ./test_dns_pending

# Phase 6.2 dhcpd daemon: render-time expansion builder (network/netmask from
# net.yaml, advertise_self/override resolution, auto-pool slice, client/server
# exclusion, subnet overlap) + adoption hash + drift. Pure (no exec); links the
# net helpers the expansion uses.
test-dhcpd-daemon:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_dhcpd_daemon \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcpd_canonical.c daemon/common/yaml_lite.c \
		daemon/dhcpd/expand.c daemon/dhcpd/dhcpd_exec.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/test/test_dhcpd_daemon.c ${LDLIBS}
	./test_dhcpd_daemon

# 10.C8 D1 dhcpd machine-path no-op: runs the REAL dhcpd apply core
# (apply.c/dhcpd_exec.c/expand.c + the shared no-op predicate) against fake
# rcctl/dhcpd shims + a per-uid scratch tree (the test-dns-pending pattern; no
# root, no /etc/dhcpd.conf, no real dhcpd). The REAL dhcpd audit.c is linked
# against a -D'd scratch OGMA_LOG_DHCPD so the actuation-verb tokens are
# asserted from the log. OGMA_DHCPD_STAGING_VALIDATE is -D'd to a SINGLE level
# under the test root (staging_mkdir creates one level only).
test-dhcpd-apply:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_dhcpd_apply \
		-DOGMA_TEST_ROOT='"/tmp/ogma-dhcpd-apply-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_DHCPD_STAGING_VALIDATE='OGMA_TEST_ROOT "/staging-dhcp-validate"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/gw-state"' \
		-DOGMA_DHCPD_CONF='OGMA_TEST_ROOT "/etc/dhcpd.conf"' \
		-DOGMA_DHCPD_PREADOPT='OGMA_TEST_ROOT "/config/dhcpd.conf.preadopt"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/hostname-none"' \
		-DOGMA_DHCPLEASE_DIR='OGMA_TEST_ROOT "/dhcpleased"' \
		-DOGMA_LOG_DHCPD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_dhcpd.sh"' \
		-DOGMA_DHCPD='"daemon/test/fake_dhcpd_check.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/json_dhcpd.c daemon/common/dhcpd_leases.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcpd_canonical.c daemon/common/yaml_lite.c \
		daemon/common/apply_lock.c daemon/common/apply_noop.c \
		daemon/dhcpd/expand.c daemon/dhcpd/dhcpd_exec.c \
		daemon/dhcpd/apply.c daemon/dhcpd/audit.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/boot_drift.c \
		daemon/test/test_dhcpd_apply.c ${LDLIBS}
	chmod +x daemon/test/fake_rcctl_dhcpd.sh daemon/test/fake_dhcpd_check.sh
	-chmod -R u+rwx /tmp/ogma-dhcpd-apply-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-dhcpd-apply-`id -u` /tmp/ogma-dhcpd-fake-`id -u`
	OGMA_FAKE_DHCPD_DIR=/tmp/ogma-dhcpd-fake-`id -u` ./test_dhcpd_apply

# Phase 4.3 commit-confirmed apply: runs the real pfd apply pipeline
# against a fake pfctl and a scratch tree under /tmp (no root, no /etc).
# The scratch root is per-uid: the lab smoke runs this as root while
# developers/CI run it unprivileged, and a fixed path would leave a tree
# the other user cannot clean.
# 10.E7.2 S3b: OGMA_GW_STATE_DIR is redirected into the scratch tree so the two
# published drift records (OGMA_BOOT_DRIFT_DIR derives from it) land there
# instead of the real /var/run — the S3a test-timed-apply precedent.
# 10.E9 VD-E9-8: OGMA_SYSCTL is redirected to a committed fake too — the
# fail-closed pf hold (pfd/pfctl.c) forces both IP-forwarding sysctls off, and
# `sysctl -w` on the CI runner would MUTATE the host kernel. The same redirect
# is what lets the unit assert the VALUE written and drive the sysctl-refused
# (partial-hold) arm. OGMA_FSCK_BOOT_SENTINEL derives from OGMA_GW_STATE_DIR
# above, so the hold's boot-scoped sentinel lands in the scratch tree as well.
test-pf-pending:
	${CC} ${CFLAGS} -o test_pf_pending \
		-Wl,--wrap=write -Wl,--wrap=fsync -Wl,--wrap=rename \
		-DOGMA_PFD_TEST_HOOKS -DOGMA_CONFIRM_TXN_TEST_HOOKS \
		-DOGMA_PFCTL='"daemon/test/fake_pfctl.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl.sh"' \
		-DOGMA_SYSCTL='"daemon/test/fake_sysctl.sh"' \
		-DOGMA_PF_TEST_ROOT='"/tmp/ogma-pf-pending-'`id -u`'"' \
		-DOGMA_PF_CONF='OGMA_PF_TEST_ROOT "/etc/pf.conf"' \
		-DOGMA_PF_MANAGED='OGMA_PF_TEST_ROOT "/etc/pf.conf.ogmaprotect"' \
		-DOGMA_PF_MANAGED_TMP='OGMA_PF_TEST_ROOT "/etc/pf.conf.ogmaprotect.tmp"' \
		-DOGMA_PF_CONF_TMP='OGMA_PF_TEST_ROOT "/etc/pf.conf.ogma.tmp"' \
		-DOGMA_PF_STAGING_VALIDATE='OGMA_PF_TEST_ROOT "/staging/pf/validate"' \
		-DOGMA_CONFIG_DIR='OGMA_PF_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_PF_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_PF_TEST_ROOT "/staging"' \
		-DOGMA_LOG_PFD='OGMA_PF_TEST_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_PF_TEST_ROOT "/gw-state"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/gateways_canonical.c \
		daemon/common/confirm_txn.c daemon/common/apply_lock.c \
		daemon/common/pf_table.c daemon/common/pf_bruteforce.c \
		daemon/common/yaml_lite.c \
		daemon/pfd/pfctl.c daemon/pfd/apply.c daemon/pfd/audit.c \
		daemon/pfd/bruteforce.c \
		daemon/common/boot_drift.c daemon/common/drift.c \
		daemon/test/fault_wrap.c \
		daemon/test/state_reclaim.c \
		daemon/test/test_pf_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_pfctl.sh daemon/test/fake_rcctl.sh \
		daemon/test/fake_sysctl.sh
	./test_pf_pending

# Phase 9.O1 Part B generalized commit-confirmed core: drives the real
# daemon/common/confirm_txn.c marker + timer through a stub domain (no daemon,
# no exec, no /etc) against a per-uid scratch tree under /tmp. OGMA_CONFIRM_TXN_
# TEST_HOOKS exposes the deadline-expiry/retry-gate hook. Same json/parse/util
# closure as test-revision (ogma_parse pulls routes_canonical/validate) + the
# core + apply_lock; the lock lives under OGMA_CONFIG_DIR (created at start).
test-confirm-txn:
	${CC} ${CFLAGS} -o test_confirm_txn \
		-Wl,--wrap=write -Wl,--wrap=fsync -Wl,--wrap=rename \
		-DOGMA_CONFIRM_TXN_TEST_HOOKS \
		-DOGMA_TEST_ROOT='"/tmp/ogma-confirm-txn-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/test/fault_wrap.c \
		daemon/test/test_confirm_txn.c ${LDLIBS}
	./test_confirm_txn

# 10.A3.2 boot self-check record helper: the versioned record builder + verdict
# counters, the CRITICAL-audit-per-drift sink, record-row injection sanitization,
# and the confirm-window gate. Closure: boot_drift.c calls ogma_confirm_txn_find
# (confirm_txn.c) for the window gate and ogma_write_atomic_mode/ogma_valid_txn_id
# (util.c); confirm_txn.c pulls the same common set as test-confirm-txn.
# 10.A3.2b: OGMA_GW_STATE_DIR is redirected into a per-uid /tmp root so
# ogma_boot_drift_commit (and its overflow marker row) can be exercised against a
# real file without touching /var/run — OGMA_BOOT_DRIFT_DIR derives from it.
test-boot-drift:
	${CC} ${CFLAGS} -o test_boot_drift \
		-DOGMA_GW_STATE_DIR='"/tmp/ogma-boot-drift-'`id -u`'"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/common/boot_drift.c \
		daemon/test/test_boot_drift.c ${LDLIBS}
	rm -rf /tmp/ogma-boot-drift-`id -u`
	./test_boot_drift

# 10.E8 VD-E8-52: the mfa_disable event record (daemon/common/mfa_record.c) —
# the authd writer + the alertd-side pure reader (parse / digest / detail) in
# a per-uid scratch OGMA_GW_STATE_DIR, plus source-scan pins for the two seams
# no host harness can link: dispatch.c's five emit sites (and NO emit outside
# the mfa_disable handler) and metrics.c's collector wiring. Same link closure
# as test-boot-drift (util.c's dup/free externs), no boot_drift.c.
test-mfa-record:
	${CC} ${CFLAGS} -o test_mfa_record \
		-DOGMA_GW_STATE_DIR='"/tmp/ogma-mfa-record-'`id -u`'"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/common/mfa_record.c \
		daemon/test/test_mfa_record.c ${LDLIBS}
	rm -rf /tmp/ogma-mfa-record-`id -u`
	./test_mfa_record
	rm -rf /tmp/ogma-mfa-record-`id -u`

# 10.C6.S1: the unified drift-verdict schema (drift.c). Pins the fail-safe
# roll-up (M7), the DISPLAY-vs-DECISION has_drift/has_error split (M1), and the
# canonical JSON fragment. Pure/in-memory; drift.c's only closure is
# ogma_json_escape (json.c) + ogma_buf_append (yaml_lite.c), both standalone.
test-drift:
	${CC} ${CFLAGS} -o test_drift \
		daemon/common/drift.c daemon/common/json.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_drift.c ${LDLIBS}
	./test_drift

# 10.A4 crash-safe-restore primitives: the authd journal (create/read/commit-
# point/atomic-delete/reap), the write-once txn-keyed snapshot store (publish/
# discard/reap class allowlist), and restore.lock — all against a per-uid
# scratch tree. Host-portable (no exec, no /etc): runs in the Linux CI strict
# tier too. Closure: restore_journal validates upload paths via backup.c,
# which pulls the archive reader; the txn write/rmtree bodies live in util.c.
test-restore-journal:
	${CC} ${CFLAGS} -o test_restore_journal \
		-DOGMA_TEST_ROOT='"/tmp/ogma-restore-journal-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_snap.c daemon/common/restore_lock.c \
		daemon/authd/restore_journal.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_restore_journal.c ${LDLIBS}
	./test_restore_journal

# 10.A4 per-daemon restore contract: drives a fragment daemon's REAL apply/
# rollback/commit ops (the coordinator suite fakes every proxy, so it never runs
# a daemon's own apply code). cert (sysd) is the representative INTENT-only
# fragment — ogma_canonical_save_cert writes canonical only, no exec — so its
# full write-once / fail-closed / no-op-rollback contract runs against a per-uid
# scratch tree with no tool fake. Host-portable: runs in the Linux CI strict tier.
test-restore-daemons:
	${CC} ${CFLAGS} -DOGMA_TEST_APPLY_LOCK -o test_restore_daemons \
		-DOGMA_TEST_ROOT='"/tmp/ogma-restore-daemons-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_SYSCTL_CONF='OGMA_TEST_ROOT "/etc/sysctl.conf"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c \
		daemon/common/cert_canonical.c daemon/common/backup_validate_cert.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/backup_validate_sysctl.c \
		daemon/common/identity_canonical.c \
		daemon/common/backup_validate_identity.c \
		daemon/common/apply_lock.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_snap.c \
		daemon/sysd/backup_apply.c \
		daemon/netd/backup_apply_sysctl.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_restore_daemons.c ${LDLIBS}
	./test_restore_daemons

# Phase 9.O1 Part B rtd commit-confirmed adopter: drives the real route_pending.c
# resolve body (commit persists routes.yaml + mygate + one revision and re-asserts
# the forward plan; revert touches no canonical; corrupt/empty payload contract;
# recover direction) against a fake route(8) + a per-uid scratch tree. The route
# exec is the fake (empty FIB → live_status reads absent), so the resolve control
# flow runs without root/FIB; the live FIB behavior is covered by the smoke. The
# audit sink is stubbed in the test, so no rtd log/audit.c is linked.
test-route-pending:
	${CC} ${CFLAGS} -o test_route_pending \
		-DOGMA_TEST_ROOT='"/tmp/ogma-route-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_MYgate_PATH='OGMA_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/json_routes.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/config_manifest.c daemon/common/yaml_lite.c \
		daemon/common/drift.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/rtd/route.c daemon/rtd/route_pending.c \
		daemon/test/test_route_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_route.sh
	rm -rf /tmp/ogma-rp-fake-`id -u`
	OGMA_FAKE_ROUTE_DIR=/tmp/ogma-rp-fake-`id -u` ./test_route_pending

# Phase 9.R1 rtd gateways commit-confirmed window: drives the real
# gateways_pending.c resolve body (revert restores the PRIOR gateways.yaml +
# refreshes the manifest; confirm keeps the NEW canonical; first-apply revert
# empties the managed config; reject while open) against a per-uid scratch tree.
# The pfd re-render + the rtd audit sink are stubbed in the test, so neither pfd
# nor rtd dispatch is linked; the live PF re-home is covered by the smoke.
# config_manifest.c is REQUIRED (ogma_canonical_save_gateways refreshes the
# fragment manifest), like test-route-pending.
# The chmod before the pre-clean is load-bearing: the test mints real revisions,
# and revision.c SEALS them (dirs 0550, files 0440), so a plain rm -rf of the
# prior run's residue fails "Permission denied" as non-root — every successful
# run would poison the next one on a persistent host (CI's fresh VM never sees
# it). The other pre-cleaned roots (ogma-rp-fake / ogma-ap-fake) hold only fake
# tool logs, never sealed revisions, so they do not need this.
test-gateways-pending:
	${CC} ${CFLAGS} -o test_gateways_pending \
		-DOGMA_TEST_ROOT='"/tmp/ogma-gw-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/config_manifest.c daemon/common/yaml_lite.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/common/routes_canonical.c \
		daemon/common/gateways_canonical.c daemon/rtd/gateways_pending.c \
		daemon/test/test_gateways_pending.c ${LDLIBS}
	-chmod -R u+rwx /tmp/ogma-gw-pending-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-gw-pending-`id -u`
	./test_gateways_pending

# Phase 7b.1.2b WireGuard commit-confirmed window: drives the real netd
# wg_pending machinery (marker/timer/recovery/confirm-cancel + revert) against
# a fake ifconfig/route and a scratch tree under /tmp (no root, no /etc). Same
# per-uid scratch-root discipline as test-pf-pending. OGMA_HAVE_IFCONFIG so the
# revert actually execs the (fake) ifconfig/route; OGMA_NETD_TEST_HOOKS exposes
# the deadline-expiry hook.
test-wg-pending:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_wg_pending \
		-DOGMA_NETD_TEST_HOOKS \
		-DOGMA_CONFIRM_TXN_TEST_HOOKS \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_WG_TEST_ROOT='"/tmp/ogma-wg-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_WG_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_WG_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_WG_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_WG_TEST_ROOT "/etc"' \
		-DOGMA_MYgate_PATH='OGMA_WG_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_WG_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_WG_TEST_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/confirm_txn.c daemon/common/apply_lock.c \
		daemon/common/yaml_lite.c \
		daemon/rtd/route.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/netd/hostname.c daemon/common/drift.c \
		daemon/netd/audit.c daemon/netd/wg_pending.c \
		daemon/test/test_wg_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh
	./test_wg_pending

# Phase B tunnel-endpoint commit-confirmed window: drives the real netd
# tunnel_pending machinery (marker/timer/recovery/confirm-cancel + the IN-PLACE
# endpoint revert) against the same fake ifconfig/route as test-wg-pending and a
# scratch tree under /tmp (no root, no /etc). Same per-uid scratch-root
# discipline. OGMA_HAVE_IFCONFIG so the arm/revert actually exec the (fake)
# ifconfig (logging `tunnel <src> <dst>` so the test proves the live dst moved);
# OGMA_NETD_TEST_HOOKS exposes the deadline-expiry hook.
test-tunnel-pending:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_tunnel_pending \
		-DOGMA_NETD_TEST_HOOKS \
		-DOGMA_CONFIRM_TXN_TEST_HOOKS \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_TUNNEL_TEST_ROOT='"/tmp/ogma-tunnel-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TUNNEL_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TUNNEL_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TUNNEL_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TUNNEL_TEST_ROOT "/etc"' \
		-DOGMA_MYgate_PATH='OGMA_TUNNEL_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_TUNNEL_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_TUNNEL_TEST_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/confirm_txn.c daemon/common/apply_lock.c \
		daemon/common/yaml_lite.c \
		daemon/rtd/route.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/netd/hostname.c daemon/common/drift.c \
		daemon/netd/audit.c daemon/netd/tunnel_pending.c \
		daemon/test/test_tunnel_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh
	./test_tunnel_pending

# Phase 9.O1 Part B interface-ADDRESS commit-confirmed window: drives the real
# netd address resolve body (daemon/netd/address_pending.c on the generalized
# confirm_txn core) against the real canonical/ifconfig/revision machinery + a
# fake ifconfig/route and a per-uid scratch tree under /tmp (no root, no /etc).
# Proves: deferred save (confirm persists net.yaml + hostname.if + one revision;
# revert touches neither + restores the captured live set), the corrupt/empty
# live_before + corrupt candidate fail-closed contract, recover direction, the D1
# VLAN-candidate UNCHECKED-parse fix, the kernel-only live_before filter, and the
# D7 iface-to-marker binding. OGMA_HAVE_IFCONFIG so revert execs the fake ifconfig;
# OGMA_HOSTNAME_DIR redirects the hostname render into the scratch tree.
# VD-F7-19 adds the AUTOCONF arm: the snapshot/restore filter widened to the
# drift predicate (a SLAAC GUA / RFC 4941 temporary / DHCPv6-PD /64 is never
# recorded nor re-pinned as a static), the gated `slaacctl send solicitation`
# with its negative and its failure-injection case, and the untagged-list
# INERTNESS pin that keeps the other fifteen restore_addresses callers honest.
# OGMA_SLAACCTL points at a committed logging fake — netd's only WRITE use of
# that tool — so the suite never execs the system binary (10.D4 S1b).
test-address-pending:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_address_pending \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_SLAACCTL='"daemon/test/fake_slaacctl.sh"' \
		-DOGMA_TEST_ROOT='"/tmp/ogma-address-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_MYgate_PATH='OGMA_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_TEST_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/common/yaml_lite.c \
		daemon/rtd/route.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/netd/hostname.c daemon/common/drift.c \
		daemon/netd/audit.c daemon/netd/address_pending.c \
		daemon/test/test_address_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh \
		daemon/test/fake_slaacctl.sh
	rm -rf /tmp/ogma-ap-fake-`id -u`
	OGMA_FAKE_IFCONFIG_DIR=/tmp/ogma-ap-fake-`id -u` \
	OGMA_FAKE_ROUTE_DIR=/tmp/ogma-ap-fake-`id -u` ./test_address_pending

# Phase 9.R2.b CARP commit-confirmed window: drives the real netd carp resolve
# body (daemon/netd/carp_pending.c on the generalized confirm_txn core) against
# the real canonical/ifconfig/revision machinery + a fake ifconfig/route and a
# per-uid scratch tree under /tmp (no root, no /etc, no carp(4)). Proves:
# deferred save (confirm persists net.yaml + hostname.carpN + one revision;
# revert touches neither + reprograms the prior canonical state), the windowed
# CREATE destroy-on-revert, the absent-snapshot + corrupt-candidate fail-closed
# contract, the D6 pass-bearing-candidate backstop, recover direction, the D7
# iface-to-marker binding, and the pending fragment / reject gate.
test-carp-pending:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_carp_pending \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_TEST_ROOT='"/tmp/ogma-carp-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_MYgate_PATH='OGMA_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_TEST_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/common/yaml_lite.c \
		daemon/rtd/route.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/netd/hostname.c daemon/common/drift.c \
		daemon/netd/audit.c daemon/netd/carp_pending.c \
		daemon/test/test_carp_pending.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh
	rm -rf /tmp/ogma-cp-fake-`id -u`
	OGMA_FAKE_IFCONFIG_DIR=/tmp/ogma-cp-fake-`id -u` \
	OGMA_FAKE_ROUTE_DIR=/tmp/ogma-cp-fake-`id -u` ./test_carp_pending

# 9.R3.6 v6 commit-confirmed window: full auto-revert restoring all three managed
# files present-aware (M3 absent-vs-empty sentinel), + the bidirectional v4<->v6
# serialization gate (M1/M2). Drives the real v6_pending revert BODY against fake
# rcctl/ifconfig/route + a per-uid scratch tree (no root, no /etc). The three conf
# paths are redirected into the scratch tree via the #ifndef-guarded defines.
test-v6-confirm:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_v6_confirm \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_SLAACCTL='"daemon/test/fake_slaacctl.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl.sh"' \
		-DOGMA_TEST_ROOT='"/tmp/ogma-v6-confirm-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_TEST_ROOT "/etc"' \
		-DOGMA_DHCP6LEASED_CONF='OGMA_TEST_ROOT "/etc/dhcp6leased.conf"' \
		-DOGMA_DHCP6LEASED_TMP='OGMA_TEST_ROOT "/etc/dhcp6leased.conf.ogma.tmp"' \
		-DOGMA_RAD_CONF='OGMA_TEST_ROOT "/etc/rad.conf"' \
		-DOGMA_RAD_TMP='OGMA_TEST_ROOT "/etc/rad.conf.ogma.tmp"' \
		-DOGMA_MYgate_PATH='OGMA_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_TEST_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp6.c daemon/common/dhcp_txn.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/apply_lock.c daemon/common/confirm_txn.c \
		daemon/common/yaml_lite.c \
		daemon/rtd/route.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/netd/hostname.c daemon/common/drift.c \
		daemon/netd/audit.c daemon/netd/v6_pending.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_v6_confirm.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh daemon/test/fake_rcctl.sh daemon/test/fake_slaacctl.sh
	rm -rf /tmp/ogma-v6-fake-`id -u`
	OGMA_FAKE_RC_DIR=/tmp/ogma-v6-fake-`id -u` \
	OGMA_FAKE_IFCONFIG_DIR=/tmp/ogma-v6-fake-`id -u` \
	OGMA_FAKE_ROUTE_DIR=/tmp/ogma-v6-fake-`id -u` ./test_v6_confirm

# Phase 9.O1 Part B slice 4 routed commit-confirmed window: drives the REAL
# routed pipeline THROUGH ogma_routing_dispatch (windowed set_ospf arm /
# confirm / cancel / timeout-tick, the reject chokepoint + confirm_timeout
# floor, the DR-10 rollback_backup_routing contract, DR-12 no-op-skip revert,
# recover directions) against fake ospfd/bgpd/rcctl shims + a per-uid scratch
# tree under /tmp (no root, no /etc, no real daemons). dispatch.c needs
# status.c (its route table binds the status handlers), which needs
# common/routing_status.c; backup_apply.c needs backup.c + backup_archive.c +
# backup_validate_routing.c + ops.c. 10.A3.1b links the REAL routed/audit.c
# against a -D'd scratch OGMA_LOG_ROUTED (the define is #ifndef-guarded; the
# old in-test stub is gone) so CRITICAL boot_reconcile lines are asserted
# from the log, plus boot_reconcile.c and the last-good -D overrides. OGMA_ROUTING_STAGING_VALIDATE hardcodes /var/db and is NOT derived
# from OGMA_STAGING_DIR — it MUST be -D'd separately or the staged `-n`
# candidates escape the scratch tree. The chmod before the pre-clean is
# load-bearing: the test mints real revisions and revision.c SEALS them
# (dirs 0550, files 0440), so a plain rm -rf of a prior run's residue fails
# Permission denied as non-root on a persistent host (the
# test-gateways-pending precedent).
# 10.E7.2 S3b: OGMA_GW_STATE_DIR is redirected into the scratch tree so the two
# published drift records (OGMA_BOOT_DRIFT_DIR derives from it) land there
# instead of the real /var/run — the S3a test-timed-apply precedent.
test-routing-pending:
	${CC} ${CFLAGS} -o test_routing_pending \
		-DOGMA_CONFIRM_TXN_TEST_HOOKS \
		-DOGMA_ROUTING_RENDER_TEST_HOOKS \
		-DOGMA_TEST_ROOT='"/tmp/ogma-routing-pending-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_ROUTING_STAGING_VALIDATE='OGMA_TEST_ROOT "/staging/routing/validate"' \
		-DOGMA_OSPFD='"daemon/test/fake_ospfd.sh"' \
		-DOGMA_BGPD='"daemon/test/fake_bgpd.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_routing.sh"' \
		-DOGMA_OSPFCTL='OGMA_TEST_ROOT "/missing/ospfctl"' \
		-DOGMA_BGPCTL='OGMA_TEST_ROOT "/missing/bgpctl"' \
		-DOGMA_OSPFD_CONF='OGMA_TEST_ROOT "/etc/ospfd.conf"' \
		-DOGMA_BGPD_CONF='OGMA_TEST_ROOT "/etc/bgpd.conf"' \
		-DOGMA_OSPFD_PREADOPT='OGMA_TEST_ROOT "/config/ospfd.conf.preadopt"' \
		-DOGMA_BGPD_PREADOPT='OGMA_TEST_ROOT "/config/bgpd.conf.preadopt"' \
		-DOGMA_LASTGOOD_DIR='OGMA_TEST_ROOT "/last-good"' \
		-DOGMA_LOG_ROUTED='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/gw-state"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c daemon/common/ops.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/apply_lock.c daemon/common/apply_noop.c \
		daemon/common/confirm_txn.c \
		daemon/common/routing_canonical.c daemon/common/json_routing.c \
		daemon/common/routing_status.c \
		daemon/routed/apply.c daemon/routed/svc_apply.c \
		daemon/routed/svc_exec.c daemon/routed/ospfd_exec.c \
		daemon/routed/bgpd_exec.c daemon/routed/reload_classify.c \
		daemon/routed/state.c \
		daemon/routed/routing_pending.c daemon/routed/dispatch.c \
		daemon/routed/backup_apply.c daemon/routed/status.c \
		daemon/common/backup_snap.c \
		daemon/routed/boot_reconcile.c daemon/routed/audit.c \
		daemon/common/boot_drift.c daemon/common/drift.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_routing_pending.c ${LDLIBS}
	@# 10.C8: the OGMA_OSPFCTL/OGMA_BGPCTL -D's above are deliberately MISSING
	@# paths and the suite setup creates the OGMA_RELOAD_DISABLE kill-switch —
	@# this suite is the DR-12 migration gate and pins PRE-C8 restart counts;
	@# the reload arm must never actuate here (test_routing_reload owns reload).
	chmod +x daemon/test/fake_ospfd.sh daemon/test/fake_bgpd.sh \
		daemon/test/fake_rcctl_routing.sh
	-chmod -R u+rwx /tmp/ogma-routing-pending-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-routing-pending-`id -u` /tmp/ogma-routing-fake-`id -u`
	OGMA_FAKE_ROUTING_DIR=/tmp/ogma-routing-fake-`id -u` ./test_routing_pending

# 10.C8 routed reload-apply: D2 classifier table (pure) + apply-level behavior
# through ogma_routing_dispatch against fake ospfd/bgpd/rcctl + the new fake
# ospfctl/bgpctl shims (MUTATE-tagged shared calls log; fail_reload env knob).
# Same link set as test-routing-pending plus apply_noop.c + reload_classify.c.
test-routing-reload:
	${CC} ${CFLAGS} -o test_routing_reload \
		-DOGMA_CONFIRM_TXN_TEST_HOOKS \
		-DOGMA_TEST_ROOT='"/tmp/ogma-routing-reload-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_ROUTING_STAGING_VALIDATE='OGMA_TEST_ROOT "/staging/routing/validate"' \
		-DOGMA_OSPFD='"daemon/test/fake_ospfd.sh"' \
		-DOGMA_BGPD='"daemon/test/fake_bgpd.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_routing.sh"' \
		-DOGMA_OSPFCTL='"daemon/test/fake_ospfctl.sh"' \
		-DOGMA_BGPCTL='"daemon/test/fake_bgpctl.sh"' \
		-DOGMA_RELOAD_DISABLE='OGMA_TEST_ROOT "/config/reload-disable"' \
		-DOGMA_OSPFD_CONF='OGMA_TEST_ROOT "/etc/ospfd.conf"' \
		-DOGMA_BGPD_CONF='OGMA_TEST_ROOT "/etc/bgpd.conf"' \
		-DOGMA_OSPFD_PREADOPT='OGMA_TEST_ROOT "/config/ospfd.conf.preadopt"' \
		-DOGMA_BGPD_PREADOPT='OGMA_TEST_ROOT "/config/bgpd.conf.preadopt"' \
		-DOGMA_LASTGOOD_DIR='OGMA_TEST_ROOT "/last-good"' \
		-DOGMA_LOG_ROUTED='OGMA_TEST_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/yaml_lite.c daemon/common/ops.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/apply_lock.c daemon/common/apply_noop.c \
		daemon/common/confirm_txn.c \
		daemon/common/routing_canonical.c daemon/common/json_routing.c \
		daemon/common/routing_status.c \
		daemon/routed/apply.c daemon/routed/svc_apply.c \
		daemon/routed/svc_exec.c daemon/routed/ospfd_exec.c \
		daemon/routed/bgpd_exec.c daemon/routed/reload_classify.c \
		daemon/routed/state.c \
		daemon/routed/routing_pending.c daemon/routed/dispatch.c \
		daemon/routed/backup_apply.c daemon/routed/status.c \
		daemon/common/backup_snap.c \
		daemon/routed/boot_reconcile.c daemon/routed/audit.c \
		daemon/common/boot_drift.c daemon/common/drift.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_routing_reload.c ${LDLIBS}
	chmod +x daemon/test/fake_ospfd.sh daemon/test/fake_bgpd.sh \
		daemon/test/fake_rcctl_routing.sh daemon/test/fake_ospfctl.sh \
		daemon/test/fake_bgpctl.sh
	-chmod -R u+rwx /tmp/ogma-routing-reload-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-routing-reload-`id -u` /tmp/ogma-routing-reload-fake-`id -u`
	OGMA_FAKE_ROUTING_DIR=/tmp/ogma-routing-reload-fake-`id -u` ./test_routing_reload

# Phase 7b.1.2c WireGuard live apply: drives the real netd set_wireguard
# pipeline (validate/dry-probe/render/apply/marker+arm or commit/rollback)
# against a STATEFUL fake ifconfig (tracks create/destroy) + a fake route + a
# per-uid scratch tree under /tmp. No real wg(4), no root, no /etc.
test-wg-apply:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_wg_apply \
		-DOGMA_NETD_TEST_HOOKS \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig_apply.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_WG_TEST_ROOT='"/tmp/ogma-wg-apply-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_WG_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_WG_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_WG_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_WG_TEST_ROOT "/etc"' \
		-DOGMA_MYgate_PATH='OGMA_WG_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_WG_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_WG_TEST_ROOT "/audit.log"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/canonical.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/apply_lock.c \
		daemon/common/wg_drift.c daemon/common/drift.c \
		daemon/netd/tunnel_pending.c \
		daemon/common/confirm_txn.c daemon/netd/address_pending.c \
		daemon/netd/carp_pending.c \
		daemon/common/yaml_lite.c \
		daemon/rtd/route.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/netd/hostname.c \
		daemon/netd/audit.c daemon/netd/wg_pending.c daemon/netd/wg_apply.c \
		daemon/common/boot_drift.c \
		daemon/test/test_wg_apply.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig_apply.sh daemon/test/fake_route.sh
	./test_wg_apply

# Review-F6 WireGuard delete_interface safety net. Pins four invariants of the
# netd delete path — the file-static netd_op_delete_interface / netd_wg_delete_teardown
# / netd_wg_delete_routes in daemon/netd/dispatch.c: pending-window reject (wg AND
# tunnel), allowed_ips route withdrawal before destroy, kind=wireguard restore by
# name, and orphan 0600 hostname.wgN key unlink on canonical-remove failure.
# test_netd_delete.c #includes ../netd/dispatch.c to reach the statics, then links
# the PROG_NETD object closure (COMMON + NETD_SRC minus main/peer/daemon_base, and
# minus dispatch.c itself since it is #included). Same per-uid /tmp scratch + fake
# ifconfig/route discipline as test-wg-pending; OGMA_HAVE_IFCONFIG so the teardown
# actually execs the fakes (without it the apply calls compile to no-ops).
test-netd-batch:
	${CC} ${CFLAGS} -DOGMA_NETD_BUILD -DOGMA_HAVE_IFCONFIG -o test_netd_batch \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_SYSCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_NETSTAT='"daemon/test/fake_poison.sh"' \
		-DOGMA_SLAACCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_DHCPLEASectl='"daemon/test/fake_dhcpleasectl.sh"' \
		-DOGMA_DHCPLEASED_BIN='"daemon/test/fake_poison.sh"' \
		-DOGMA_DHCP6LEASectl='"daemon/test/fake_poison.sh"' \
		-DOGMA_DHCP6LEASED_BIN='"daemon/test/fake_poison.sh"' \
		-DOGMA_RAD_BIN='"daemon/test/fake_poison.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_NETD_BATCH_TEST_ROOT='"/tmp/ogma-netd-batch-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_NETD_BATCH_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_NETD_BATCH_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_NETD_BATCH_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_NETD_BATCH_TEST_ROOT "/etc"' \
		-DOGMA_MYgate_PATH='OGMA_NETD_BATCH_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_NETD_BATCH_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_NETD_BATCH_TEST_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_NETD_BATCH_TEST_ROOT "/run"' \
		-DOGMA_DHCPLEASE_DIR='OGMA_NETD_BATCH_TEST_ROOT "/dhcpleased"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/json_dns.c daemon/common/pf_structured.c \
		daemon/common/yaml_lite.c daemon/common/ops.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/revision.c daemon/common/backup.c \
		daemon/common/backup_archive.c daemon/common/apply_lock.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/config_manifest.c \
		daemon/common/wg_drift.c daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/pppoe_drift.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/iface_stats.c \
		daemon/common/bridge_stats.c \
		daemon/common/json_routes.c daemon/common/routes_compose.c \
		daemon/netd/hostname.c daemon/netd/dhcp_apply.c \
		daemon/common/dhcp.c daemon/common/dhcp_txn.c \
		daemon/common/dhcp6.c \
		daemon/common/canonical.c daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_apply.c daemon/common/routes_canonical.c \
		daemon/common/backup_snap.c \
		daemon/netd/backup_apply_sysctl.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/backup_validate_sysctl.c \
		daemon/rtd/route.c \
		daemon/netd/wg_pending.c \
		daemon/netd/wg_apply.c \
		daemon/netd/tunnel_pending.c \
		daemon/netd/pending_mux.c \
		daemon/common/confirm_txn.c daemon/netd/address_pending.c \
		daemon/netd/carp_pending.c daemon/netd/v6_pending.c \
		daemon/netd/ifconfig.c daemon/netd/audit.c \
		daemon/common/boot_drift.c \
		daemon/common/cap_hmac.c \
		daemon/common/text_diff.c \
		daemon/test/test_netd_batch.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh \
		daemon/test/fake_poison.sh daemon/test/fake_dhcpleasectl.sh
	rm -rf /tmp/ogma-batch-fake-`id -u`
	OGMA_FAKE_IFCONFIG_DIR=/tmp/ogma-batch-fake-`id -u` \
	OGMA_FAKE_ROUTE_DIR=/tmp/ogma-batch-fake-`id -u` ./test_netd_batch

test-netd-delete:
	${CC} ${CFLAGS} -DOGMA_NETD_BUILD -DOGMA_HAVE_IFCONFIG -o test_netd_delete \
		-DOGMA_IFCONFIG='"daemon/test/fake_ifconfig.sh"' \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_SYSCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_NETSTAT='"daemon/test/fake_poison.sh"' \
		-DOGMA_SLAACCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_DHCPLEASectl='"daemon/test/fake_dhcpleasectl.sh"' \
		-DOGMA_DHCPLEASED_BIN='"daemon/test/fake_poison.sh"' \
		-DOGMA_DHCP6LEASectl='"daemon/test/fake_poison.sh"' \
		-DOGMA_DHCP6LEASED_BIN='"daemon/test/fake_poison.sh"' \
		-DOGMA_RAD_BIN='"daemon/test/fake_poison.sh"' \
		-DOGMA_RCCTL='"daemon/test/fake_poison.sh"' \
		-DOGMA_NETD_DELETE_TEST_ROOT='"/tmp/ogma-netd-delete-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_NETD_DELETE_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_NETD_DELETE_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_NETD_DELETE_TEST_ROOT "/staging"' \
		-DOGMA_HOSTNAME_DIR='OGMA_NETD_DELETE_TEST_ROOT "/etc"' \
		-DOGMA_MYgate_PATH='OGMA_NETD_DELETE_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_NETD_DELETE_TEST_ROOT "/mygate.tmp"' \
		-DOGMA_LOG_NETD='OGMA_NETD_DELETE_TEST_ROOT "/audit.log"' \
		-DOGMA_GW_STATE_DIR='OGMA_NETD_DELETE_TEST_ROOT "/run"' \
		-DOGMA_DHCPLEASE_DIR='OGMA_NETD_DELETE_TEST_ROOT "/dhcpleased"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/json_dns.c daemon/common/pf_structured.c \
		daemon/common/yaml_lite.c daemon/common/ops.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/revision.c daemon/common/backup.c \
		daemon/common/backup_archive.c daemon/common/apply_lock.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/config_manifest.c \
		daemon/common/wg_drift.c daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/pppoe_drift.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/iface_stats.c \
		daemon/common/bridge_stats.c \
		daemon/common/json_routes.c daemon/common/routes_compose.c \
		daemon/netd/hostname.c daemon/netd/dhcp_apply.c \
		daemon/common/dhcp.c daemon/common/dhcp_txn.c \
		daemon/common/dhcp6.c \
		daemon/common/canonical.c daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_apply.c daemon/common/routes_canonical.c \
		daemon/common/backup_snap.c \
		daemon/netd/backup_apply_sysctl.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/backup_validate_sysctl.c \
		daemon/rtd/route.c \
		daemon/netd/wg_pending.c \
		daemon/netd/wg_apply.c \
		daemon/netd/tunnel_pending.c \
		daemon/netd/pending_mux.c \
		daemon/common/confirm_txn.c daemon/netd/address_pending.c \
		daemon/netd/carp_pending.c daemon/netd/v6_pending.c \
		daemon/netd/ifconfig.c daemon/netd/audit.c \
		daemon/common/boot_drift.c \
		daemon/common/cap_hmac.c \
		daemon/common/text_diff.c \
		daemon/test/test_netd_delete.c ${LDLIBS}
	chmod +x daemon/test/fake_ifconfig.sh daemon/test/fake_route.sh \
		daemon/test/fake_poison.sh daemon/test/fake_dhcpleasectl.sh
	./test_netd_delete

test-rtd-backup:
	${CC} ${CFLAGS} -DOGMA_TEST_APPLY_LOCK -DOGMA_HAVE_IFCONFIG -o test_rtd_backup \
		-DOGMA_ROUTE='"daemon/test/fake_route.sh"' \
		-DOGMA_ROUTE_TEST_ROOT='"/tmp/ogma-rtd-backup-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_ROUTE_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_ROUTE_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_ROUTE_TEST_ROOT "/staging"' \
		-DOGMA_LOG_RTD='OGMA_ROUTE_TEST_ROOT "/audit.log"' \
		-DOGMA_MYgate_PATH='OGMA_ROUTE_TEST_ROOT "/mygate"' \
		-DOGMA_MYgate_TMP='OGMA_ROUTE_TEST_ROOT "/mygate.tmp"' \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/dhcp_preflight.c \
		daemon/common/revision.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/apply_lock.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/yaml_lite.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/rtd/route.c daemon/rtd/apply.c daemon/rtd/backup_apply.c \
		daemon/rtd/backup_apply_gateways.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/backup_snap.c \
		daemon/rtd/route_pending.c daemon/common/confirm_txn.c \
		daemon/rtd/pending_mux.c daemon/rtd/gateways_pending.c \
		daemon/common/gateways_canonical.c \
		daemon/rtd/audit.c \
		daemon/common/cap_hmac.c \
		daemon/common/ops.c \
		daemon/test/test_rtd_backup.c ${LDLIBS}
	chmod +x daemon/test/fake_route.sh
	./test_rtd_backup

# Deterministic mutation fuzzing of the net/routes/pf YAML parsers.
# Tunables: OGMA_FUZZ_ITERS, OGMA_FUZZ_SEED, OGMA_FUZZ_STRICT (see
# daemon/test/fuzz_yaml.c). For coverage-guided fuzzing build the same
# sources with clang -DOGMA_FUZZ_USE_LIBFUZZER -fsanitize=fuzzer,address.
test-ops:
	${CC} ${CFLAGS} -o test_ops \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/test_ops.c ${LDLIBS}
	./test_ops

# 10.B3 D5(d): the exclusion-arrow table test — pins every op's confirm-window
# class (the graph the daemon_base chokepoint enforces), the fail-closed
# completeness guard, and the read-family ALLOW invariant. Links only the op
# registry (host-portable, no live daemon).
test-window-gate:
	${CC} ${CFLAGS} -o test_window_gate \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/test_window_gate.c ${LDLIBS}
	./test_window_gate

# 10.C1.S6: the byte-level wire-neutrality proof for the table-driven proxy
# response slicer (daemon/authd/proxy_slice.c) — canned daemon replies -> the
# CLIENT wire (ogma_json_build_response) asserted byte-for-byte against pre-S6
# behavior. Same json/proto link closure as test-proto (json.c pulls the parse
# tree) plus ops.c (the resp_shape/slice_key registry) + proxy_slice.c. Sqlite-
# free, so it runs on the Linux c-tests leg under -Wswitch-enum.
test-proxy-slice:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_proxy_slice \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c \
		daemon/common/ops.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/dhcp.c daemon/common/revision.c \
		daemon/common/config_manifest.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/authd/proxy_slice.c \
		daemon/common/daemon_inventory.c \
		daemon/test/test_proxy_slice.c ${LDLIBS}
	./test_proxy_slice

# 10.C9: the single daemon inventory (daemon/common/daemon_inventory.c) is the
# structural source of truth (enum<->table bijection, fail-closed accessors, the
# 16 _Static_asserts). test_daemon_inventory pins that structure at runtime.
# Sqlite-free, minimal link (the daemon_inventory.c object only).
test-daemon-inventory:
	${CC} ${CFLAGS} -o test_daemon_inventory \
		daemon/common/daemon_inventory.c \
		daemon/test/test_daemon_inventory.c ${LDLIBS}
	./test_daemon_inventory

# 10.C11 S4 (M13): the blind double-entry equivalence matrix for the unified
# authd->daemon proxy transport — per-daemon socket, deadline (recovery on/off),
# and the five client-visible error strings incl. the two shipped deviations
# (ipsecd prefix "ipsec"; netd parse-fail "netd error"). Lean link: proxy.c +
# the inventory + trivial stubs (the transport's heavy deps are unexercised).
test-proxy-transport:
	${CC} ${CFLAGS} -o test_proxy_transport \
		daemon/authd/proxy.c daemon/common/daemon_inventory.c \
		daemon/test/test_proxy_transport.c ${LDLIBS}
	./test_proxy_transport
	@# VD-E3-22, two-sided: the proxied body leaves authd's 64 KiB stack
	@# frame scrubbed on BOTH post-read arms (parsed-ok after the slice,
	@# and parse-not-ok) — frame.c covers only the non-OK read result.
	[ "`grep -c '^[[:space:]]*explicit_bzero(respbuf, sizeof(respbuf))' daemon/authd/proxy.c`" -eq 2 ]

# 10.C9: freeze the neutral inventory projection scripts/daemon_inventory.gen from
# the compiled C table (the gen_rate_golden idiom). The .gen carries a hand-authored
# committed DO-NOT-EDIT header (first 7 lines); check-daemon-inventory verifies the
# frozen data lines still match the C table and reconciles every surface against it.
gen-inventory:
	${CC} ${CFLAGS} -o gen_daemon_inventory \
		daemon/common/daemon_inventory.c \
		daemon/test/gen_daemon_inventory.c ${LDLIBS}
	head -n 7 scripts/daemon_inventory.gen > scripts/daemon_inventory.gen.new
	./gen_daemon_inventory >> scripts/daemon_inventory.gen.new
	mv scripts/daemon_inventory.gen.new scripts/daemon_inventory.gen
	@echo "gen-inventory: froze scripts/daemon_inventory.gen from the C table"

# 10.C9: the agreement gate. Rebuild the projection from the live C table, then
# check-daemon-inventory.sh reconciles the committed .gen (staleness) + every
# packaging/rc.d/Makefile/deploy/ctl daemon-registration surface against it.
check-daemon-inventory:
	${CC} ${CFLAGS} -o gen_daemon_inventory \
		daemon/common/daemon_inventory.c \
		daemon/test/gen_daemon_inventory.c ${LDLIBS}
	./gen_daemon_inventory > gen_daemon_inventory.out
	sh scripts/check-daemon-inventory.sh gen_daemon_inventory.out

# 10.C12: the exec-helper egress single-source gate — a pure source-scan (no
# compiled artifact) that runs on BOTH CI legs, asserting the injection charset
# literal lives in one production file AND every egress site calls the shared
# entry point. See scripts/check-egress-single-source.sh.
check-egress-single-source:
	sh scripts/check-egress-single-source.sh

# 10.C4 S0: the no-new-parent-promise gate — a pure source-scan on BOTH CI
# legs pinning (1) authd's parent pledge string byte-exact (the load-bearing
# "10.C4 adds nothing to the serving process" property), (2) the job-runner
# supervisor's pledge byte-exact + exec-free (§11 M9), and (3) the M11
# dumb-relay invariant (job_runner.c references no request parser). See
# scripts/check-authd-parent-pledge.sh.
check-authd-parent-pledge:
	sh scripts/check-authd-parent-pledge.sh

# 10.C3 S1: the capacity-matrix agreement gate — a pure source-scan (no compiled
# artifact; runs on both CI legs AND the docs-lint leg) asserting every Limit in
# docs/CAPACITY-MATRIX.md equals its bare-integer #define in
# daemon/common/ogmaprotect.h. The parity net that stops an S3/S4 cap lift (or a
# doc fat-finger) from silently diverging the published matrix. See
# scripts/check-capacity-matrix.sh.
check-capacity-matrix:
	sh scripts/check-capacity-matrix.sh

# 10.D1 S1: the CI coverage sync guard (L5-04 C-side). A pure perl source-scan
# (no compiled artifact) asserting every test-*/check-* target runs in a proven
# leg — the Makefile `test:` aggregate, its .PHONY twin, the sanitized set
# (ci/sanitized-targets.txt), and the best-effort set must stay provably
# consistent, so no test silently defaults to un-sanitized-VM-only or nowhere.
# Deliberately NOT a `test:` prerequisite (it is a meta-guard, not a suite test)
# and NOT on .PHONY (like test-fixture-parity) — it is allow-listed as
# %NOT_AGGREGATED inside the guard, and run as a blocking c-tests step. Its own
# fail-closed proof is t/check_ci_coverage.t. See scripts/check_ci_coverage.pl.
check-ci-coverage:
	perl scripts/check_ci_coverage.pl

# 10.D2 S3 (contract M8): the scheduled fuzz lane's liveness attestation. The
# coverage-guided lane (.github/workflows/fuzz-libfuzzer.yml) runs on a daily
# cron, and EVERY way a cron can stop running is silent — dropped under load,
# Actions-minute quota exhausted with no payment method, or auto-disabled — so a
# dead lane looks exactly like a lane finding nothing. This guard cannot see
# GitHub: it validates ci/fuzz-lane-liveness.txt, a dated HUMAN attestation that
# somebody confirmed the lane ran, and reddens this blocking leg on `expires`,
# forcing someone to look. The `# expires:` + `--today` idiom is
# scripts/check_lsan_suppressions.pl's. Like check-ci-coverage: a meta-guard,
# NOT a `test:` prerequisite and NOT on .PHONY (allow-listed %NOT_AGGREGATED in
# check_ci_coverage.pl), run as a blocking c-tests step. Fail-closed proof:
# t/check_fuzz_lane_liveness.t. See docs/PHASE10-D2-FUZZ-REACH-CONTRACT.md §14.
check-fuzz-lane-liveness:
	perl scripts/check_fuzz_lane_liveness.pl

# 10.D3 (L5-05): the leak-detection guard. Validates ci/lsan-suppressions.txt
# (sanctioned reasons, narrow frames, dated+capped pending-D3) AND — the
# recurrence teeth — that no workflow re-disables LeakSanitizer (detect_leaks=0
# / LSAN exitcode=0) and the sanitized leg runs detect_leaks=1. Like
# check-ci-coverage: a meta-guard, NOT a `test:` prerequisite and NOT on .PHONY
# (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a blocking
# c-tests step. Fail-closed proof: t/check_lsan_suppressions.t. See
# scripts/check_lsan_suppressions.pl and docs/PHASE10-D3-LEAK-DETECTION.md.
check-lsan-suppressions:
	perl scripts/check_lsan_suppressions.pl

# 10.D4 S1b (VD-D1-1): the exec-macro hermeticity guard. Verifies the dangerous
# exec macros (OGMA_SYSCTL/OGMA_RCCTL) carry a compile-time POISON default under
# OGMA_SANITIZED_BUILD, that the sanitized CI leg actually sets that sentinel,
# and that test-netd-delete keeps its poison-shim overrides — so a sanitized unit
# test can never silently exec (or, for `sysctl -w`, mutate via) a real host
# tool. Like check-ci-coverage/check-lsan-suppressions: a meta-guard, NOT a
# `test:` prerequisite and NOT on .PHONY (allow-listed %NOT_AGGREGATED in
# check_ci_coverage.pl), run as a blocking c-tests step. Fail-closed proof:
# t/check_exec_hermeticity.t. See docs/PHASE10-D4-BOUNDARY-ASSURANCE.md.
check-exec-hermeticity:
	perl scripts/check_exec_hermeticity.pl

# 10.D5 S1a (L5-08): the smoke-registry guard. Keeps scripts/smoke-registry.txt
# bijective with the tree (every scripts/ file classified exactly once; a
# deploy-coupled or *_smoke.sh smoke cannot hide behind a rename), the tag/domain
# sane, the regression import-closure consistent, and -- the interlock -- no
# workflow reaches the lab (deploy.env / OGMA_SSH_PASS/SU_PASS / self-hosted /
# run_smokes.py). Like check-ci-coverage: a meta-guard, NOT a `test:` prerequisite
# and NOT on .PHONY (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as
# a blocking c-tests step. Fail-closed proof: t/check_smoke_registry.t. See
# scripts/check_smoke_registry.pl and docs/PHASE10-D5-REPRODUCIBLE-VERIFICATION.md.
check-smoke-registry:
	perl scripts/check_smoke_registry.pl

# 10.F1 S4a: the cookie-name gate + the scanner-emitted census (contract Sec.3.5
# D5's first-named deliverable). Two teeth: BARE-LITERAL, a pre-S4a cookie name
# left unswept under web/ t/ scripts/ -- which matters because the smokes' read
# -side extractions are SUBSTRING matches that keep working inside the prefixed
# name, so an unswept read site is invisible to every test; and NAME-UNKNOWN, a
# `__Host-` name that is not one of the two constants read out of Auth.pm, which
# is the wrongly-swept case no bare-literal scan can see. ci/cookie-name-census.txt
# must regenerate equal. docs/ is deliberately out of scope (history). Like
# check-ci-coverage: a meta-guard, NOT a `test:` prerequisite and NOT on .PHONY
# (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a blocking
# c-tests step. Fail-closed proof: t/check_cookie_names.t.
check-cookie-names:
	perl scripts/check_cookie_names.pl --check

# Regenerate the cookie-name census after a legitimate change under web/ t/
# scripts/ (the artifact pins per-tree counts, so adding a cookie-bearing file
# is a reviewed diff rather than a silent drift).
gen-cookie-census:
	perl scripts/check_cookie_names.pl --write

# 10.F2 S2: the section-driver ordering invariant. No value may be spliced into
# the template buffer WITHOUT brace-neutralization before a section pass runs
# over it -- since S2 every driver is repeat-capable, so a SPLIT marker pair
# across two fields of an escape_html-only fragment deletes everything between
# them (measured: 537 bytes and 13 closing tags off /system/tls-cert). The
# damage direction is content REMOVAL, so every marker-absence gate scores it as
# an improvement; only a source-ordering gate sees it. Fail-closed proof lives
# in t/check_section_driver.t, which also regresses check_section_conversion.pl
# (a two-image tool, so not runnable as a standing target).
check-section-ordering:
	perl scripts/check_section_ordering.pl

# 10.F3 VD-F3-19, G-ESC-COVERAGE. check_render_equivalence proves a move changed
# no rendered byte, and its whole power is ci/f3-render-payloads.pl -- a payload
# that REACHES a sub does not DETECT a dropped _tpl_value inside it, because a
# benign value is byte-identical escaped or not. This asks the other question:
# for every governed escape site, neutering it must change at least one payload
# render. S5 wrote that rule down as prose binding on S6-S10; this is the rule,
# mechanised, on the ground VD-F3-17 records for G-COLOCATE.
#
# ~2 minutes: it re-renders every payload once per governed site.
check-render-coverage:
	perl scripts/check_render_coverage.pl

# 10.G1 S2d (L5-13): the truncation-warning ratchet. Compiles the sanitized-
# target union source set once, compile-only, under WARNSCAN_CFLAGS (its own
# env var -- never $$PORTABLE_CFLAGS; ci.yml defines it) and pins the unique
# -Wformat-truncation / -Wstringop-truncation site counts against
# ci/truncation-baseline.txt, two-sided, plus the zero-diagnostic-pragma
# tripwire. GCC-only by construction (clang implements neither diagnostic),
# so this runs on its own ci.yml step in the GCC c-tests job -- never in the
# `test:` aggregate, never on the OpenBSD/lab legs.
check-truncation-baseline:
	perl scripts/check_truncation_baseline.pl

# 10.G1 S2e (L5-12): LOCAL-ONLY code-coverage baselines -- illuminate, not
# gate. DISAMBIGUATION: this is CODE coverage (gcov / Devel::Cover);
# check-ci-coverage and check-render-coverage are CI-WIRING guards, and the
# fuzz lane's "coverage-guided" is fuzzer feedback -- neither is this.
# Deliberately NOT named test-* or check-* so check_ci_coverage.pl Teeth B
# never claims these as suite tests (a `check-coverage` name would red that
# guard as an unwired recipe).
#
# `coverage` instruments a NAMED REPRESENTATIVE target set, not the full
# sanitized set: this tree has no .o files (every target recompiles $${COMMON}
# into itself), so instrumenting the whole set under -j means dozens of racing
# writers to the same util.gcno -- garbage counts, not a number. Isolation is
# SERIAL per-target builds + a per-target artifact sweep (direct-to-binary
# compiles prefix every .gcno/.gcda with the binary name, so pairs stay
# distinct; the contract's -fprofile-dir form was tried and DEFEATS gcov's
# gcno/gcda pairing -- its mangled-absolute-path .gcda basenames never match
# the .gcno, measured at S2e landing). The merged figure is a BASELINE, not a
# suite figure. --coverage never appears in an OpenBSD or shipped target (a .gcda
# write needs a wpath the pledged daemons do not hold). Artifacts
# (*.gcno/*.gcda/coverage-out/, cover_db/ -- the latter embeds source text)
# are gitignored, which also keeps them out of the dist tarball (git
# archive); the support-bundle walk and backup snap/archive touch only fixed
# appliance paths (verified at S2e) so neither can sweep them; the
# verification-debt guard's gitless tree-walk prunes them explicitly.
COVERAGE_TARGETS=	test-yaml-lite test-proto test-hostname test-net-l2 \
		test-routes test-rbac test-confirm-txn test-pf
coverage:
	@command -v gcov >/dev/null 2>&1 || { \
		echo "coverage: gcov (GCC) not found -- this target is local-only,"; \
		echo "  GCC/Linux (the lab clang has llvm-cov, but the baseline is"; \
		echo "  documented against gcov; see docs/PHASE10-G1-QUICKWINS.md 3.5)"; \
		exit 1; }
	@rm -rf coverage-out; mkdir -p coverage-out
	@set -e; for t in ${COVERAGE_TARGETS}; do \
		mkdir -p coverage-out/$$t; \
		${MAKE} $$t CFLAGS="${CFLAGS} --coverage"; \
		mv *.gcno coverage-out/$$t/ 2>/dev/null || true; \
		mv *.gcda coverage-out/$$t/ 2>/dev/null || true; \
	done
	@echo "== coverage: per-target profile data under coverage-out/<target>/ =="
	@if command -v gcovr >/dev/null 2>&1; then \
		gcovr --root . coverage-out --print-summary || true; \
	else \
		echo "gcovr not installed -- raw .gcno/.gcda kept per target;"; \
		echo "  \`pip install gcovr\` then \`gcovr --root . coverage-out\`"; \
		echo "  for the merged report."; \
	fi

coverage-perl:
	@perl -MDevel::Cover -e1 2>/dev/null || { \
		echo "coverage-perl: Devel::Cover not installed (local-only target;"; \
		echo "  cpan Devel::Cover). Deliberately unwired from CI -- package"; \
		echo "  availability and the t/lib typeglob-mock interplay are"; \
		echo "  unproven there (docs/PHASE10-G1-QUICKWINS.md 3.5)."; \
		exit 1; }
	@rm -rf cover_db
	HARNESS_PERL_SWITCHES=-MDevel::Cover ${MAKE} test-perl || true
	cover -summary cover_db 2>/dev/null || cover cover_db

# 10.F2 S2: section POLARITY against the merge base. A keep/drop inversion, and
# a symmetric section-name swap, leak no marker and change no rendered byte at
# any site the suite does not exercise in a distinguishing configuration -- an
# adversarial review planted both and every other tier stayed green. Compares
# HEAD's Template.pm with the merge-base image; a no-op on main, meaningful on
# every branch. Skips (0) when git or the base image is unavailable.
check-section-polarity:
	@base="$${OGMA_POLARITY_BASE}"; \
	if [ -z "$$base" ]; then \
		base=`git merge-base origin/main HEAD 2>/dev/null` || base=''; \
	fi; \
	if [ -z "$$base" ]; then \
		echo "check-section-polarity: no base commit."; \
		echo "  Pass OGMA_POLARITY_BASE=<sha> (CI does; a shallow checkout cannot"; \
		echo "  compute a merge-base), or run from a full checkout."; \
		if [ -n "$$OGMA_REQUIRE_POLARITY" ]; then \
			echo "  OGMA_REQUIRE_POLARITY is set, so this is a FAILURE: a gate"; \
			echo "  that silently skips is the defect this slice's review closed."; \
			exit 1; \
		fi; \
		echo "  skipping (CI sets OGMA_REQUIRE_POLARITY=1 to make this fatal)"; \
		exit 0; \
	fi; \
	tmp=`mktemp -d` || exit 1; \
	if ! git archive $$base web/lib 2>/dev/null | tar -xf - -C $$tmp; then \
		echo "check-section-polarity: base image $$base unavailable"; \
		rm -rf $$tmp; \
		if [ -n "$$OGMA_REQUIRE_POLARITY" ]; then exit 1; fi; \
		exit 0; \
	fi; \
	man=''; mman=''; \
	for m in ci/f3-move-manifest-*.txt; do \
		[ -f "$$m" ] || continue; \
		perl scripts/check_template_resolve.pl --spent-check "$$m" \
			--base-dir $$tmp; \
		src=$$?; \
		if [ $$src -eq 3 ]; then continue; fi; \
		if [ $$src -ne 0 ]; then rm -rf $$tmp; exit 1; fi; \
		perl scripts/check_template_resolve.pl --manifest "$$m" \
			|| { rm -rf $$tmp; exit 1; }; \
		man="$$man --manifest $$m"; \
		mman="$$mman --move-manifest $$m"; \
	done; \
	perl scripts/check_polarity_set.pl --base-dir $$tmp --head-dir . $$man \
		--base-ref `git rev-parse $$base` --head-ref `git rev-parse HEAD`; \
	rc=$$?; \
	if [ $$rc -eq 0 ] && [ -n "$$man" ]; then \
		if ! git show $$base:ci/raw-splice-ledger.txt > $$tmp/base-ledger.txt 2>/dev/null; then \
			echo "check-section-polarity: base $$base has no ci/raw-splice-ledger.txt,"; \
			echo "  so --move-check cannot run. Refusing to pass silently."; \
			rm -rf $$tmp; exit 1; \
		fi; \
		perl scripts/check_raw_splices.pl --move-check \
				--base-ledger $$tmp/base-ledger.txt $$mman; \
		rc=$$?; \
	fi; \
	rm -rf $$tmp; exit $$rc
# 10.D5 S1b (L5-09): the verification-debt ledger guard. Keeps docs/VERIFICATION-DEBT.md
# honest -- every VD-<slice>-<n> token in the tracked tree resolves to a ledger row
# (the doc-scan catches a Gate-0 that cites an unseeded VD row), every row id
# unique/well-formed with a sanctioned State, each closed/lab-only row's discharging
# smoke is a REGISTERED regression smoke (the S1a teeth-6 linkage, now that the registry
# exists), and the consolidated ROADMAP lab-gate residuals (9.R2/vxlan/dhcp-prio) keep a
# dedicated row. Like check-smoke-registry: a meta-guard, NOT a `test:` prerequisite and
# NOT on .PHONY (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a blocking
# c-tests step. Fail-closed proof: t/check_verification_debt.t. See
# scripts/check_verification_debt.pl and docs/PHASE10-D5-REPRODUCIBLE-VERIFICATION.md.
check-verification-debt:
	perl scripts/check_verification_debt.pl

# 10.F2 S3 (G3+G4): the escape-or-declare ratchet. Every {{TOKEN}} substitution
# in web/lib is either escaped through the engine (whole-expression match --
# `_tpl_value($$x) . $$raw` is RAW, the concat evasion an S2 review executed)
# or declared in ci/raw-splice-ledger.txt under a closed reason enum, with the
# CAP scanner-emitted, never hand-typed. Engine::raw() call sites count toward
# the same CAP so F3 cannot relocate debt into raw() invisibly, and raw()'s
# argument must be a bare lexical. Like check-section-ordering: a meta-guard,
# NOT a `test:` prerequisite and NOT on .PHONY (allow-listed %NOT_AGGREGATED in
# check_ci_coverage.pl), run as a blocking c-tests step. Fail-closed proof:
# t/check_raw_splices.t (a planted defect per tooth, each asserting its NAMED
# diagnostic). See scripts/check_raw_splices.pl and
# docs/PHASE10-F2-ONE-TEMPLATE-ENGINE.md Sec.3.5.
check-raw-splices:
	perl scripts/check_raw_splices.pl

# 10.F3 S1a (G-COMPLETE): page-dispatch completeness. render() dispatches on
# $$name through a flat if-chain with a silent fall-through tail, so a template
# with no matching arm is NOT an error -- it renders with every other marker
# intact and ships its unrendered ones. That is SECURITY-REVIEW W-11: 10.E3 S3
# served a support tab with value="{{CSRF}}" and both POST routes 403 for every
# operator, through the real httpd/slowcgi chain, whole suite green.
#
# The comparison is THREE-way -- templates/*.html <-> %PAGES <-> what render()
# actually branches on. A two-way gate (templates <-> %PAGES) is green on the
# same defect in a two-line edit; UNDISPATCHED is the direction that closes it.
# T1 requires each declared non-render() template to be positively demonstrated
# by a literal open IN CODE (a comment naming the file is not a demonstration),
# T2 forbids an exclusion-list edit sharing a branch with a template addition,
# T3 pins every dispatch-$$name literal to a %PAGES key. Every list is capped
# two-sided, so each escape route is a censused edit rather than a one-line door.
#
# Like check-raw-splices: a meta-guard, NOT a `test:` prerequisite and NOT on
# .PHONY (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a
# blocking c-tests step. T2 additionally needs a base commit, so CI runs it a
# second time on pull_request with OGMA_F3_BASE + OGMA_REQUIRE_PAGE_DISPATCH.
# Fail-closed proof: t/check_page_dispatch.t. See
# docs/PHASE10-F3-TEMPLATE-DECOMPOSITION.md Sec.3.3.
check-page-dispatch:
	perl scripts/check_page_dispatch.pl

# 10.F3 S1a (G-RESOLVE): callable resolution + the Sec.0.8 partition.
# `perl -c` is PROVABLY blind to the dominant decomposition slip -- a call to a
# sub that does not exist reports "syntax OK", with and without parens -- and
# once Template.pm splits into per-domain packages that slip becomes a
# cross-package failure: the call compiles, the page 500s. Three teeth: (i) every
# _\w+( call site resolves and no defined sub is uncalled; (ii) every %PAGES
# value is '' or a fully-qualified render_* that RESOLVES and lives in the module
# ci/f3-partition.txt assigns to that page, with the unpopulated count held
# exactly; (iii) the partition is total and unambiguous over the live sub census.
# --manifest also validates a Sec.3.5 move manifest, including that a row may not
# legitimise a wrong-module landing.
#
# Meta-guard, same treatment as above. Fail-closed proof:
# t/check_template_resolve.t. See docs/PHASE10-F3-TEMPLATE-DECOMPOSITION.md
# Sec.3.3 / Sec.3.5 / Sec.0.8.
check-template-resolve:
	perl scripts/check_template_resolve.pl

# 10.F3 S1b (G-SWEEP-SET): the section-sweep derivation, as a script rather than
# a block inside t/template_escaping.t. It used to derive %driven by scanning ONE
# hardcoded path, so at F3's terminal state -- Template.pm a thin router with no
# section calls -- %driven and %carries empty and its two-sided ratchet asserts
# 0 <= 0 <= 0 while claiming nothing about any template (VD-F3-4). That is also
# why it is a script: a .t cannot drive a planted defect against itself, so the
# gate could never be SHOWN to redden. Holds four EXACT two-sided censuses (213
# driven names / 393 literal-name sites / 34 templates / 4 engine consumers) and
# names any module that calls an engine primitive without importing the engine.
# Meta-guard, same treatment as check-raw-splices. Fail-closed proof:
# t/check_sweep_set.t, whose decisive case relocates the section calls into a
# domain module and requires the driven set to be unchanged.
check-sweep-set:
	perl scripts/check_sweep_set.pl

# 10.F3 S-final (G-LOC-BUDGET, contract Sec.3.3 row 7 / Sec.7 D8): the
# per-module LOC budget over the Template file set. Sec.0.1 measured regrowth
# as the default -- Template.pm went 7,056 -> 7,475 -> 8,098 in 24 days while
# F2 was explicitly not adding pages -- and NOTHING prevented the twelve
# domain modules from re-running that curve. One MOD row per module in
# ci/f3-loc-budget.txt (measured <= budget <= ceiling); the budget MAY ONLY
# FALL (the PR leg passes OGMA_F3_BASE + OGMA_REQUIRE_LOC_BUDGET and compares
# the committed file against the base's copy), and the remedy for OVER is
# shrink-or-split, never a bigger number. The file set is WALKED and compared
# two-sided against the rows, so a new module cannot arrive ungoverned.
#
# Like check-raw-splices: a meta-guard, NOT a `test:` prerequisite and NOT on
# .PHONY (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a
# blocking c-tests step. Fail-closed proof: t/check_loc_budget.t. See
# docs/PHASE10-F3-TEMPLATE-DECOMPOSITION.md Sec.7 D8 / Sec.26.
check-loc-budget:
	perl scripts/check_loc_budget.pl

# 10.F3 S-final (VD-F2-2 / VD-F3-5): the constructed-token tripwire. A token
# BUILT at run time (`'{{BRIDGE_' . uc($bk) . '}}'` + `s/\Q$ph\E/...`)
# carries no literal `\{\{` in any pattern, so it is invisible to
# check-raw-splices, to t/template_escaping.t's shape scans and to
# check-section-ordering -- the one splice class with NO other instrument.
# Flags EVERY contiguous `{{` on a non-comment line (the escaped `\{\{`
# spelling is the one sanctioned splice form; a first draft exempted
# complete tokens and the slice's own review showed that exemption was the
# hoisted-literal door); held two-sided against a declared census (today:
# the VD-F3-5 bridge-loop site in Template/Interface.pm, with a mechanical
# escaper-adjacency tooth, and fill()'s unknown-token passthrough in
# Template/Engine.pm). A syntactic tripwire, not taint analysis -- the same
# honest bound as check-raw-splices' `^\$\w+$` (contract Sec.8.4).
#
# Like check-raw-splices: a meta-guard, NOT a `test:` prerequisite and NOT on
# .PHONY (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a
# blocking c-tests step. Fail-closed proof: t/check_constructed_tokens.t.
check-constructed-tokens:
	perl scripts/check_constructed_tokens.pl
# 10.F3 S4: G-RENDER-EQUIV. Renders ci/f3-render-payloads.pl through the
# merge-base web/lib and through HEAD's, and compares the output BYTE FOR BYTE.
# Same base-vs-head shape as check-section-polarity above, and for the same
# reason: a move slice's claim is "nothing changed", and only two trees can
# settle that. An adversarial review measured five defects -- a byte inside a
# multi-line qq{, a byte in a single-line qq{}, an argument transposition at a
# call site into a relocated sub, and a wholly corrupted _human_bytes -- that
# every page suite AND all six gates passed. All five red here.
# Fail-closed proof: t/check_render_equivalence.t, which plants exactly those.
check-render-equivalence:
	@base="$${OGMA_POLARITY_BASE}"; \
	if [ -z "$$base" ]; then \
		base=`git merge-base origin/main HEAD 2>/dev/null` || base=''; \
	fi; \
	if [ -z "$$base" ]; then \
		echo "check-render-equivalence: no base commit (see check-section-polarity)"; \
		if [ -n "$$OGMA_REQUIRE_RENDER_EQUIV" ]; then exit 1; fi; \
		exit 0; \
	fi; \
	tmp=`mktemp -d` || exit 1; mkdir -p $$tmp/x; \
	if ! git archive $$base web/lib 2>/dev/null | tar -xf - -C $$tmp/x; then \
		echo "check-render-equivalence: base image $$base unavailable"; \
		rm -rf $$tmp; \
		if [ -n "$$OGMA_REQUIRE_RENDER_EQUIV" ]; then exit 1; fi; \
		exit 0; \
	fi; \
	perl scripts/check_render_equivalence.pl --base-dir $$tmp/x --head-dir .; \
	rc=$$?; rm -rf $$tmp; exit $$rc
# 10.F5 S1: the JS sink ledger (audit L7-04, L7-12(c)). web/static/ogmaprotect.js
# is 11k lines with no linter and no test, and its DOM-output discipline is
# asserted in THIRTEEN prose comments and enforced by nothing. The richer half
# of the safety net (ESLint + jsdom) is a CI-only Node lane -- the OpenBSD lab
# has no node and the port must never need one -- so the security-relevant
# invariants live HERE, in a pure-perl guard that runs wherever the product
# builds. Class 2 (eval / new Function / string timers / javascript:) is
# forbidden outright with NO ledger escape, because the CSP already blocks it at
# the browser; classes 1/3/4 are ledgered under a closed reason enum. Arm 4
# emits the JS-injected ogma- class literals for t/css_drift.t, which scopes JS
# out by name and has left that gap owned by nobody (its out-of-scope block).
#
# Like check-raw-splices: a meta-guard, NOT a `test:` prerequisite and NOT on
# .PHONY (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a
# blocking c-tests step. Fail-closed proof: t/check_js_sinks.t (a planted defect
# per tooth, each asserting its NAMED diagnostic, plus an ACCEPTS case so an
# always-red guard cannot pass). See docs/PHASE10-F5-JS-SAFETY-NET.md.
check-js-sinks:
	perl scripts/check_js_sinks.pl

# 10.F1: the route-guard census (audit L7-08). The route table used to be 83
# metadata-less `path => \&sub` rows with every cross-cutting guard -- method,
# CSRF, permission -- hand-threaded per handler, so "forgot the guard" shipped a
# CSRF-open endpoint that still compiled. S1 made the guards DATA in
# web/lib/OgmaProtect/Dispatch.pm and S2b deleted the handler copies the central
# dispatcher subsumes; this instrument is the blind double-entry that proved each
# step reproduced the last.
#
# Emits SIX committed artifacts under a closed NINE-class enum (the comment here
# said "three ... 7-class" from S0 through S2a, which was already false at S1 --
# corrected 10.F1 S2b):
#   ci/route-guard-matrix.txt      per route: kind/methods/csrf/perm from the
#                                  Dispatch.pm metadata, plus ops, delegates and
#                                  the `residual` per-action-refinement tokens
#   ci/route-guard-ledger.txt      keyed guard sites, closed 9-class enum
#                                  (`dispatcher-subsumed` is EMPTY from S2b)
#   ci/route-guard-census.txt      the FROZEN old-code column scan, taken at the
#                                  last commit before the handler guards went
#   ci/route-column-deltas.txt     every census-vs-metadata difference, with a
#                                  reason from a closed enum, both directions
#   ci/route-dispatch-order.txt    the front-controller prelude ORDER -- because
#                                  every behaviour golden is captured below the
#                                  centralized chain, so nothing else would notice
#                                  the force-change gate moving after dispatch or
#                                  /ha/sync after CGI->new (re-opening L4-05)
#   ci/route-op-partition.txt      per-op mutating verdict (A12)
# Like check-raw-splices: a meta-guard, NOT a `test:` prerequisite and NOT on
# .PHONY (allow-listed %NOT_AGGREGATED in check_ci_coverage.pl), run as a blocking
# c-tests step. Fail-closed proof: t/check_route_guards.t (a planted defect per
# tooth, each asserting its NAMED diagnostic). Pure perl, no build deps.
# See scripts/check_route_guards.pl and docs/PHASE10-F1-ROUTE-GUARDS-CONTRACT.md.
check-route-guards:
	perl scripts/check_route_guards.pl

# 10.F6 S0: the request-surface census gate (contract PHASE10-F6-CGI-FACADE.md
# D6). The facade's one-module-swap claim (D10) is true only while the surface
# stays the enumerated D2 list; this gate makes "a handler reached past the
# facade" a CI red at the call site -- surface breach, response emission
# outside the UI funnels (ratcheted for the S1 migration), $ENV reads under
# Handlers/ (ratcheted for S2), engine reach outside Request.pm, and the D7
# exactly-once construction. Like check-route-guards: a meta-guard, NOT a
# `test:` prerequisite and NOT on .PHONY (allow-listed %NOT_AGGREGATED in
# check_ci_coverage.pl), run as a blocking c-tests step. Fail-closed proof:
# t/check_request_surface.t (a planted defect per tooth, each asserting its
# NAMED diagnostic). Pure perl, no build deps.
check-request-surface:
	perl scripts/check_request_surface.pl

# 10.E2 S1: the config-fragment schema-version gate. Keeps the per-fragment
# ceiling table in ogmaprotect.h the SINGLE source of truth for every version
# seam: each fragment reads through ogma_fragment_version_parse() with its own
# ceiling macro and its own on-disk name, validates through the matching
# helper, stamps the ceiling on save (MECH-5) with `version:` as the first key
# (D7 placement), no hand-rolled `!= 1` pin survives in production daemon/
# code, and the hand-written Perl builders that mirror the C serializers emit
# an integer DERIVED from the same table (the gateways case: one fragment whose
# two halves live in different languages). Green on arrival at ceiling 1; the
# compat-branch/fixture/docs teeth ARM at ceiling > 1, so the first real bump
# cannot half-ship. Like check-ci-coverage: a meta-guard, NOT a `test:`
# prerequisite and NOT on .PHONY (allow-listed %NOT_AGGREGATED in
# check_ci_coverage.pl), run as a blocking c-tests step. Fail-closed proof:
# t/check_schema_versions.t. See scripts/check_schema_versions.pl and
# docs/PHASE10-E2-CONFIG-SCHEMA-MIGRATION.md.
check-schema-versions:
	perl scripts/check_schema_versions.pl

# 10.C3 S1: fail-closed proof for the gate above — drives it against broken
# fixtures (value drift, prefix-collision, malformed row, missing/dup anchor,
# non-literal #define, CRLF, missing files) and asserts it REJECTS each while the
# real inputs pass. A sizing-trust guard that passed on disagreement is worse than
# none. See scripts/test-capacity-matrix.sh.
test-capacity-matrix:
	sh scripts/test-capacity-matrix.sh

# VD-E3-29: the support-bundle WALK under a lowered (2 MiB) archive ceiling —
# continue-after-oversize, the failed-not-absent MANIFEST rows, the SUMMARY
# NOTE coupling, and a reserve-count discriminator. The staging override is the
# GUARDED OGMA_STAGING_DIR (the exports macro derives from it; a direct -D on
# OGMA_SUPPORT_MAX_ARCHIVE's unguarded exports sibling is a -Werror
# redefinition error), and the ceiling must stay ABOVE the un-overridable
# 256 KiB metadata reserve — section_fits' size_t budget underflows below it
# and everything "fits". See daemon/test/test_authd_support_walk.c's header.
test-authd-support-walk:
	${CC} ${CFLAGS} -I daemon/authd \
		-DOGMA_STAGING_DIR='"/tmp/ogma-support-walk-'`id -u`'"' \
		-DOGMA_SUPPORT_MAX_ARCHIVE=2097152 \
		-o test_authd_support_walk \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c daemon/common/json.c \
		daemon/common/yaml_lite.c daemon/common/proto.c \
		daemon/authd/authorize.c daemon/authd/rbac.c \
		daemon/authd/support_sources.c daemon/authd/support_bundle.c \
		daemon/test/test_authd_support_walk.c ${LDLIBS}
	./test_authd_support_walk

# VD-E3-8: fail-closed proof for check-daemon-inventory.sh tooth #6b, from
# FIXTURES rather than the in-band self-check — drives the guard against a
# fixture with a dropped read-only unveil grant and one with an extractor-blind
# row name, asserting the per-tooth message each time (fifteen teeth share one
# exit code, so the exit code alone cannot attribute a fire). The generator is
# compiled and run once here exactly as check-daemon-inventory does — tooth #6b
# never reads it, but the other fourteen teeth run against the real tree on
# every invocation and must stay green for the positive control to mean
# anything. See scripts/test-inventory-fixtures.sh.
test-inventory-fixtures:
	${CC} ${CFLAGS} -o gen_daemon_inventory \
		daemon/common/daemon_inventory.c \
		daemon/test/gen_daemon_inventory.c ${LDLIBS}
	./gen_daemon_inventory > gen_daemon_inventory.out
	sh scripts/test-inventory-fixtures.sh gen_daemon_inventory.out

# 10.E9 S2a: the durable-paths manifest gate (docs/PHASE-BOOT-FSCK-RESILIENCE.md
# sec 6.13). scripts/ogma-savelist.conf is the ONE classification of every path
# the product puts on a box (persistA/persistB = the appliance /cfg save-list,
# volatile, static); the guard walks every OGMA_* path constant and unveil(2)
# target in daemon/, this file's install: recipe, the pkg PLIST and every
# etc/ + scripts/ file the recipe ships, and reds on an unclassified path, a
# row nothing references (rot), a dropped sec 6.13 seed, or a grammar /
# class-rule breach. Pure perl, no build deps. A META-GUARD (%NOT_AGGREGATED
# in check_ci_coverage.pl): NOT a `test:` prerequisite and NOT on .PHONY.
# Fail-closed proof: t/check_durable_paths.t. `--list persist` is the
# save-list projection the image build (10.E9 S2b, contrib/resflash/) consumes.
check-durable-paths:
	perl scripts/check_durable_paths.pl

# 10.C1.S1/S5: gen_rate_golden RUNS ogma_op_rate_limit over every op + the synthetic
# keys; its sorted output must equal the committed daemon/test/rate_golden.inc. The
# original 77 rows are the frozen pre-S1 oracle (M4/M11/BA-2); 10.C1.S5's 11 appended
# rows are the intentional rate-drift FIX (see the .inc header) — for those this is a
# self-consistency snapshot, their independent oracle being rate_tier_golden.inc + the
# test_auth_db behavioral caps. Order-independent (both sides sorted). Sqlite-free,
# same link closure as test-ops.
check-rate-golden:
	${CC} ${CFLAGS} -o gen_rate_golden \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/gen_rate_golden.c ${LDLIBS}
	./gen_rate_golden | sort > gen_rate_golden.out
	grep '^{' daemon/test/rate_golden.inc | sort | diff -u - gen_rate_golden.out
	@echo "check-rate-golden: rate_tier column matches the committed golden"

# 10.C6 S7b: freeze scripts/drift_registry.json — the drift-domain manifest that
# DRIVES the ogmaprotectctl status walk (M13d) — from the compiled ops.c census
# {drift_role != NONE} JOINed to daemon_inventory sockets (D2-B). Manual +
# reviewed (the gen-inventory precedent); check-drift-registry gates it in `test`.
gen-drift-registry:
	${CC} ${CFLAGS} -o gen_drift_registry \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/daemon_inventory.c \
		daemon/test/gen_drift_registry.c ${LDLIBS}
	./gen_drift_registry > scripts/drift_registry.json
	./gen_drift_registry --required-set > scripts/drift_required.txt
	@echo "gen-drift-registry: froze scripts/drift_registry.json + drift_required.txt from ops.c + daemon_inventory"

# 10.C6 S7b: the agreement gate — regenerate the manifest from the live C tables
# and diff it against the committed artifact. A clean diff proves ops.c
# {drift_role != NONE} == the manifest; socket attribution is pinned against
# drift_role_golden.inc's owner column by the t/ successor, and the required-set
# the CLI compiles in is the same generator's --required-set. Same link closure
# as check-rate-golden; sqlite-free.
check-drift-registry:
	${CC} ${CFLAGS} -o gen_drift_registry \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/daemon_inventory.c \
		daemon/test/gen_drift_registry.c ${LDLIBS}
	./gen_drift_registry | diff -u scripts/drift_registry.json -
	./gen_drift_registry --required-set | diff -u scripts/drift_required.txt -
	@echo "check-drift-registry: manifest + required-set match ops.c + daemon_inventory"

# 10.C1.S1 strict-A: prove the fully-enumerated ogma_op_needs_cap + ogma_op_is_mutating
# switches reproduce origin/main for EVERY op. -Wswitch-enum proves totality, not
# sidedness, so this is the net that catches a mis-sided read-perm arm (a tokenless
# mutating op). gen_class_golden RUNS both classifiers; its sorted output must equal
# the committed daemon/test/class_golden.inc (frozen from the pre-enumeration `main`).
# Link closure mirrors test-cap (cap.c's dependency set).
check-class-golden:
	-mkdir -p /tmp/ogma-class-`id -u`/secrets
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG \
		-DOGMA_DB_DIR='"/tmp/ogma-class-'`id -u`'"' \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-class-'`id -u`'/secrets"' \
		-DOGMA_CAPKEYS_DIR='"/tmp/ogma-class-'`id -u`'/capkeys"' -o gen_class_golden \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/revision.c daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c daemon/common/backup_validate_routes.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routing.c daemon/common/routing_canonical.c \
		daemon/common/backup_validate_gateways.c daemon/common/gateways_canonical.c \
		daemon/common/backup_apply.c daemon/common/apply_lock.c \
		daemon/common/backup_snap.c daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c daemon/common/ops.c daemon/common/cap.c \
		daemon/common/cap_hmac.c daemon/common/frame.c \
		daemon/common/daemon_inventory.c \
		daemon/test/gen_class_golden.c ${LDLIBS}
	./gen_class_golden | sort > gen_class_golden.out
	sort daemon/test/class_golden.inc | diff -u - gen_class_golden.out
	@echo "check-class-golden: needs_cap + is_mutating reproduce the frozen pre-enum behavior"

# 10.C11 S2a: the request-emitter byte golden. gen_req_golden drives the OLD
# ogma_json_build_request over EVERY registry op x {defaults,max,flags,vals}
# (+ targeted alt rows + an unregistered-op probe) with a deterministic
# 312-member struct populator and prints the exact bytes as C-escaped rows.
# The committed daemon/test/req_wire_golden.inc is the S2b table-walk
# equivalence gate: any emitter byte change fails this ORDER-PRESERVING diff
# (no sort anywhere — row and key order are M1 linearization contract data)
# until the golden is consciously regenerated via gen-req-golden.
# Link closure: json.c needs only yaml_lite.c (the test-drift precedent);
# ops.c brings the check-rate-golden registry cohort.
check-req-golden:
	${CC} ${CFLAGS} -o gen_req_golden \
		daemon/common/json.c daemon/common/yaml_lite.c \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/gen_req_golden.c ${LDLIBS}
	./gen_req_golden > gen_req_golden.out
	grep '^{' daemon/test/req_wire_golden.inc | diff -u - gen_req_golden.out
	@echo "check-req-golden: request wire bytes match the committed golden"

# Freeze target (gen-inventory precedent): preserves the exactly-10-line hand
# header, replaces every generated row. Manual + reviewed only — never run by
# test/CI. Requires the committed golden to exist (header source).
gen-req-golden:
	${CC} ${CFLAGS} -o gen_req_golden \
		daemon/common/json.c daemon/common/yaml_lite.c \
		daemon/common/ops.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/gen_req_golden.c ${LDLIBS}
	test -f daemon/test/req_wire_golden.inc
	head -n 10 daemon/test/req_wire_golden.inc > daemon/test/req_wire_golden.inc.new
	./gen_req_golden >> daemon/test/req_wire_golden.inc.new
	mv daemon/test/req_wire_golden.inc.new daemon/test/req_wire_golden.inc
	@echo "gen-req-golden: froze daemon/test/req_wire_golden.inc from the live emitter"
	@echo "gen-req-golden: NOW run 'python3 scripts/check_req_linearization.py'"
	@echo "gen-req-golden: and commit BOTH the golden and req_wire_linear.gen"

# I-02 capability token: the mutating/exempt classifier (BLOCKER-1 sweep over the
# INTERNAL ops), mint -> RS-split -> verify round-trip, and replay/tamper/wrong-
# daemon/expired/epoch abuse. Per-uid /tmp scratch (the test-secrets precedent);
# ${LDLIBS} carries -lmd for the SHA-2 KAT on Linux CI.
test-cap:
	-mkdir -p /tmp/ogma-cap-`id -u`/secrets
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG \
		-DOGMA_DB_DIR='"/tmp/ogma-cap-'`id -u`'"' \
		-DOGMA_SECRETS_DIR='"/tmp/ogma-cap-'`id -u`'/secrets"' \
		-DOGMA_CAPKEYS_DIR='"/tmp/ogma-cap-'`id -u`'/capkeys"' -o test_cap \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/revision.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/common/backup_validate_routes.c \
		daemon/common/backup_validate_routing.c \
		daemon/common/routing_canonical.c \
		daemon/common/backup_validate_gateways.c \
		daemon/common/gateways_canonical.c \
		daemon/common/backup_apply.c daemon/common/apply_lock.c \
		daemon/common/backup_snap.c \
		daemon/common/tunnel_drift.c daemon/common/drift.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/rtd/route.c daemon/common/confirm_txn.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c daemon/common/cap.c daemon/common/cap_hmac.c \
		daemon/common/frame.c \
		daemon/common/daemon_inventory.c \
		daemon/test/test_cap.c ${LDLIBS}
	./test_cap

# Phase 8 idempotency: pure cache core (explicit cache + injected clock) —
# key hygiene, the bare-ack shape guard, hit/miss + actor/op/key scope
# isolation, the absolute-TTL boundary, LRU eviction, expired-slot reuse.
test-idempotency:
	${CC} ${CFLAGS} -o test_idempotency \
		daemon/common/idempotency.c daemon/common/util.c \
		daemon/common/validate.c daemon/common/net_l2.c daemon/common/secret.c \
		daemon/test/test_idempotency.c ${LDLIBS}
	./test_idempotency

# Phase 9.O4: the FQDN validator (ogma_valid_fqdn, validate.c) — the set_identity
# dry-run + execv-injection guard. 10.C6 S6: + the pure identity drift eval
# (identity_drift.c; drift.c's closure is json.c + yaml_lite.c).
# 10.E7.2 S3a: + the REAL sysd identity walk through BOTH drivers
# (ogma_sysd_boot_check / ogma_sysd_drift_check), which is what makes the
# contract's boot-equals-periodic invariant a test rather than a claim. That
# pulls daemon/sysd/identity.c and its closure (sysd_exec.c + audit.c, and
# boot_drift.c's record/revision-stall closure — the test-boot-drift set).
# HERMETIC, and every -D below is load-bearing:
#   -DOGMA_HOSTNAME_BIN : the walk forks hostname(1). Un-faked, the verdict
#     would depend on whatever box the suite runs on, and the unit would fork
#     the runner's own /bin/hostname.
#   -DOGMA_MYNAME       : the comparator's canonical side. The production path
#     is /etc/myname — a unit must never read (or, via identity.c's other
#     entry points, write) the real one.
#   -DOGMA_GW_STATE_DIR : OGMA_BOOT_DRIFT_DIR derives from it, so the two
#     published records land in the scratch tree instead of /var/run.
#   -DOGMA_LOG_SYSD     : the walk ends in ogma_audit_sysd(), which appends to
#     a seq/gen-chained production audit log.
#   -DOGMA_IMAGE_BOOTOK : 10.E9 S2 part 2 — the boot walk arms the image
#     boot-ok writer (image_boot.c, linked below for the symbol); under the
#     scratch OGMA_IMAGE_LAYOUT_MARKER it is the software edition and the
#     helper is never exec'd, but the exec-macro guard repoints it regardless.
test-sys-identity:
	chmod +x daemon/test/fake_hostname.sh daemon/test/fake_bootok.sh
	${CC} ${CFLAGS} -I daemon/sysd -o test_sys_identity \
		-DOGMA_IMAGE_BOOTOK='"daemon/test/fake_bootok.sh"' \
		-DOGMA_TEST_ROOT='"/tmp/ogma-sys-identity-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/gw-state"' \
		-DOGMA_MYNAME='OGMA_TEST_ROOT "/etc/myname"' \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_HOSTNAME_BIN='"daemon/test/fake_hostname.sh"' \
		-DOGMA_FSCK_MARKER='OGMA_TEST_ROOT "/etc/fsck-manual"' \
		-DOGMA_FSCK_STATE_DIR='OGMA_TEST_ROOT "/fsck"' \
		-DOGMA_FSCK_RC_PATH='OGMA_TEST_ROOT "/etc/rc"' \
		-DOGMA_FSCK_REBOOT_COUNTER='OGMA_TEST_ROOT "/etc/fsck-reboots"' \
		-DOGMA_FSCK_PENDING='OGMA_TEST_ROOT "/etc/fsck-pending"' \
		-DOGMA_IMAGE_LAYOUT_MARKER='OGMA_TEST_ROOT "/etc/image-layout"' \
		-DOGMA_FSCK_RETRY_S=0 \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/identity_drift.c daemon/common/drift.c \
		daemon/common/identity_canonical.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/apply_lock.c daemon/common/boot_drift.c \
		daemon/common/json.c daemon/common/yaml_lite.c \
		daemon/sysd/identity.c daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/sysd/fsck.c daemon/sysd/edition.c daemon/sysd/image_boot.c \
		daemon/sysd/cfg_persist.c \
		daemon/common/system_canonical.c daemon/common/fsck_state.c \
		daemon/test/test_sys_identity.c ${LDLIBS}
	-chmod -R u+rwx /tmp/ogma-sys-identity-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-sys-identity-`id -u`
	./test_sys_identity

# Phase 9.O4: the restart_service hard allowlist. The PREDICATE
# (ogma_sysd_service_allowed) is still sysd/services.c, but 10.F8 S2 moved the
# LIST it enforces to daemon/common/service_table.c, which is therefore a hard
# link dependency of this target. Also links the sysd exec/audit helpers it
# shares a TU with, plus the common closure util.c pulls in (net_l2.c defines the
# config-free helpers util.c references).
# 10.F8 Q5 made this target HERMETIC and non-polluting; it was neither.
#   -DOGMA_RCCTL : ogma_sysd_op_get_services forks rcctl twice per service, so
#     without a shim this unit forked the real /usr/sbin/rcctl 40x as root on
#     whatever box `make test` ran on. The poison default that would have caught
#     that is armed only under OGMA_SANITIZED_BUILD, which the lab's plain
#     `make test` does not pass.
#   -DOGMA_LOG_SYSD : the op is not pure -- it ends in ogma_audit_sysd(), which
#     appends to /var/log/ogmaprotect-sysd with a seq/gen chain. A unit driving
#     it wrote the PRODUCTION audit log.
test-sys-svc:
	chmod +x daemon/test/fake_rcctl_svc.sh
	${CC} ${CFLAGS} -I daemon/sysd -o test_sys_svc \
		-DOGMA_TEST_ROOT='"/tmp/ogma-sys-svc-'`id -u`'"' \
		-DOGMA_RCCTL='"daemon/test/fake_rcctl_svc.sh"' \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/audit.log"' \
		daemon/sysd/services.c daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/common/util.c daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c \
		daemon/common/service_table.c \
		daemon/test/test_sys_svc.c ${LDLIBS}
	rm -rf /tmp/ogma-sys-svc-`id -u` /tmp/ogma-fake-svc-`id -u`
	mkdir -p /tmp/ogma-sys-svc-`id -u`
	./test_sys_svc

# Phase 10.A6: the advisory OS-release classifier (ogma_release_qualified) + its
# total string map — pure exact-string membership, no deps beyond os_release.c.
test-os-release:
	${CC} ${CFLAGS} -o test_os_release \
		daemon/common/os_release.c \
		daemon/test/test_os_release.c ${LDLIBS}
	./test_os_release

# Phase 10.E6.2: the advisory projected-EOL classifier (ogma_release_eol) — the
# window/clock-floor boundary matrix with injected `now` values, the parity check
# (every qualified release has an EOL row), table hygiene + the strict date
# parser's reject arms, and the total string map. #includes os_release.c (the
# established harness pattern) to reach the static table, so nothing extra links.
test-os-release-eol:
	${CC} ${CFLAGS} -o test_os_release_eol \
		daemon/test/test_os_release_eol.c ${LDLIBS}
	./test_os_release_eol

# Phase 10.A6: the advisory guard regression. Overrides OGMA_UNAME with a fake
# (the ogmaprotect.h #ifndef guard makes that legal under -Werror) so the
# UNQUALIFIED and UNKNOWN (empty/failed-read) states can be injected; asserts the
# guard STARTS (never exits) and get_system_version still SERVES ok with the right
# verdict. Links the full version.c/json closure (test-sys-power stubs it away;
# here we drive the real surface). The 10.F8.1 cache-file -D pins the errata
# record off the live leaf, so a record a lab box's real sysd wrote can never
# leak into this binary's renders (pending stays -1 here; the cache's own
# battery is test-syspatch-cache).
test-os-guard:
	chmod +x daemon/test/fake_uname.sh
	${CC} ${CFLAGS} -DOGMA_UNAME='"daemon/test/fake_uname.sh"' \
		-DOGMA_SYSPATCH='"/usr/bin/false"' \
		-DOGMA_SYSPATCH_CACHE_FILE='"test_os_guard_syspatch_cache"' \
		-o test_os_guard \
		daemon/sysd/version.c daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/sysd/edition.c \
		daemon/common/os_release.c daemon/common/json.c \
		daemon/common/util.c daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c daemon/common/yaml_lite.c \
		daemon/test/test_os_guard.c ${LDLIBS}
	./test_os_guard

# Phase 10.E3.2: the sysd host-facts family. Drives the REAL get_host_facts /
# get_dmesg_boot handlers with every input path -D'd to a scratch dir the test
# populates per case (the test-os-guard fake-input idiom; the #ifndef guards in
# ogmaprotect.h / host_facts.c make the -Ds legal under -Werror). Exec-free:
# pure fixture-file reads, so it belongs to the sanitized leg.
test-host-facts:
	${CC} ${CFLAGS} \
		-DOGMA_RC_CONF_LOCAL='"test_hf_scratch/rc.conf.local"' \
		-DOGMA_DRIFT_MANIFEST='"test_hf_scratch/drift_registry.json"' \
		-DOGMA_HOSTFACTS_BOOT_DRIFT_DIR='"test_hf_scratch/boot-drift"' \
		-DOGMA_DMESG_BOOT='"test_hf_scratch/dmesg.boot"' \
		-o test_host_facts \
		daemon/sysd/host_facts.c daemon/sysd/audit.c \
		daemon/common/ops.c daemon/common/daemon_inventory.c \
		daemon/common/jsmn.c daemon/common/json.c \
		daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_host_facts.c ${LDLIBS}
	./test_host_facts

# Phase 10.E6 S3: the on-box update signal. Part A drives the PURE logic
# (daemon/common/update_check.c: the manifest parser, the dotted-version
# comparator, the D10c verdict matrix, the status-record round trip, the
# build-age arithmetic, the D4a trust-root names). Part B drives the REAL sysd
# handlers (get_update_status / check_update / enable_ / disable_update_check)
# with every path -D'd into a scratch dir and BOTH binaries repointed at
# committed shims (the test-os-guard idiom): fake_updfetch.sh plays the libtls
# GET helper (mode via OGMA_FAKE_UPDFETCH_*), fake_signify.sh plays
# `signify -V -e` over a FAKE signature line bound to the pubkey file — not
# crypto (the real signify is proven on the lab, VD-E6-25) but enough to prove
# the handler's orchestration: tamper, replay, expired, fetch-failed-after-good,
# parse-failed, no-trust-root, the flag toggles. Exec-free of real tools, so it
# belongs to the sanitized leg. build_rev.h is a prerequisite because update.c
# #includes it (the test-health shape).
test-update-check: ${BUILD_REV_H}
	chmod +x daemon/test/fake_updfetch.sh daemon/test/fake_signify.sh
	${CC} ${CFLAGS} \
		-DOGMA_DB_DIR='"test_upd_scratch"' \
		-DOGMA_UPDATE_DIR='"test_upd_scratch/update"' \
		-DOGMA_UPDATE_PUBKEY_DIR='"test_upd_scratch/keys"' \
		-DOGMA_LOG_SYSD='"test_upd_scratch/audit.log"' \
		-DOGMA_UPDFETCH_HELPER='"daemon/test/fake_updfetch.sh"' \
		-DOGMA_SIGNIFY='"daemon/test/fake_signify.sh"' \
		-o test_update_check \
		daemon/sysd/update.c daemon/sysd/stage_record.c \
		daemon/sysd/audit.c daemon/sysd/sysd_exec.c \
		daemon/sysd/edition.c \
		daemon/common/update_check.c daemon/common/egress_url.c \
		daemon/common/json.c daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_update_check.c ${LDLIBS}
	./test_update_check

# 10.E6 S3 (VD-E6-25): the three update-check NEGATIVES + one daily tick against
# a CONTROLLED update server, with the REAL signify(1) and a REAL loopback
# socket — the half test-update-check proves only over committed shims. The
# driver mints a real signify keypair under the D4a trust-root name, signs the
# manifests, forks ctrl_update_server.pl (base perl, an ephemeral loopback
# HTTP/1.0 server) and drives the REAL sysd check_update / tick handlers with
# ctrl_updfetch.pl (a REAL GET over the socket) as the fetch helper: a
# byte-flipped signature -> verify-failed, an older real-signed manifest ->
# replay-rejected, the server killed -> fetch-failed with the held verdict
# surviving, and one enabled tick firing a real check exactly once. OGMA_SIGNIFY
# is LEFT at the shipped default (the real tool runs); only the paths, the fetch
# helper and the URL are -D'd into a scratch tree. Real base signify + base perl
# + loopback => OpenBSD-only, %OPENBSD_ONLY real-userland-exec (the Ubuntu
# sanitized leg has neither). Needs ${BUILD_REV_H} like test-update-check.
test-e6-ctrl-server: ${BUILD_REV_H}
	chmod +x daemon/test/ctrl_update_server.pl daemon/test/ctrl_updfetch.pl
	${CC} ${CFLAGS} \
		-DOGMA_DB_DIR='"test_e6cs_scratch"' \
		-DOGMA_UPDATE_DIR='"test_e6cs_scratch/update"' \
		-DOGMA_UPDATE_PUBKEY_DIR='"test_e6cs_scratch/keys"' \
		-DOGMA_LOG_SYSD='"test_e6cs_scratch/audit.log"' \
		-DOGMA_UPDFETCH_HELPER='"daemon/test/ctrl_updfetch.pl"' \
		-DOGMA_UPDATE_MANIFEST_URL='"http://127.0.0.1/updates/ogmaprotect-update.sig"' \
		-o test_update_ctrl_server \
		daemon/sysd/update.c daemon/sysd/stage_record.c \
		daemon/sysd/audit.c daemon/sysd/sysd_exec.c \
		daemon/sysd/edition.c \
		daemon/common/update_check.c daemon/common/egress_url.c \
		daemon/common/json.c daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_update_ctrl_server.c ${LDLIBS}
	./test_update_ctrl_server

# 10.E6 AP2 (VD-E6-30): the P-A2 image-signature arms against the REAL
# scripts/sign-release.sh -- real signify(1) + pkg_sign(1) over a throwaway
# keypair and a minimal pkg_create package: the Lite ceremony unchanged (3
# manifest rows -- the 0.5.4 regression guard), a genuine .fs ': OK', a
# byte-flipped .fs ': FAIL' with the banner still printed, a foreign key
# 'checked against wrong key', the rebinding rename refused, and the script's
# own one-image / stale-version / wrong-osrel / malformed-name refusals.
# OpenBSD-only (%OPENBSD_ONLY real-userland-exec): pkg_sign exists nowhere
# else -- the same fact that keeps the signing ceremony off CI (E6 §7 D6).
# The on-box half of P-A2 (signify -C against the BAKED anchor on a flashed
# appliance) is NOT here and keeps VD-E6-30 partial.
test-release-sign:
	@if [ "`uname -s`" = "OpenBSD" ]; then \
		sh daemon/test/test_release_sign.sh; \
	else \
		echo "test-release-sign: skipped (OpenBSD-only: execs pkg_sign(1)/signify(1))"; \
	fi

# 10.F8.1: the errata-count cache battery (VD-F8-8, the deadline-chain
# inversion). Drives the REAL get_system_version / syspatch tick / apply
# handlers with syspatch -D'd at a committed shim (the test-os-guard idiom)
# and the state leaf under a scratch dir. Three timing -Ds (all #ifndef'd in
# version.c): CACHE_TTL_S pinned AT the shipped 3600 so the test TU shares the
# constant and the stale arm forges past-TTL stamps rather than sleeping;
# RETRY_S=3 so the backoff arms are provable without a 300 s sleep;
# WORKER_KILL_S=2 so the wedge arm (a worker outliving its own alarm — the
# Q5 fault class) reaps in seconds.
# Fork/exec of the fake only => sanitized-leg safe (the test-power-pending
# precedent).
test-syspatch-cache:
	chmod +x daemon/test/fake_syspatch.sh
	${CC} ${CFLAGS} \
		-DOGMA_UPDATE_DIR='"test_sp_scratch/update"' \
		-DOGMA_LOG_SYSD='"test_sp_scratch/audit.log"' \
		-DOGMA_SYSPATCH='"daemon/test/fake_syspatch.sh"' \
		-DSYSD_SYSPATCH_CACHE_TTL_S=3600 \
		-DSYSD_SYSPATCH_RETRY_S=3 \
		-DSYSD_SYSPATCH_WORKER_KILL_S=2 \
		-o test_syspatch_cache \
		daemon/sysd/version.c daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/sysd/edition.c \
		daemon/common/os_release.c daemon/common/json.c \
		daemon/common/util.c daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c daemon/common/yaml_lite.c \
		daemon/test/test_syspatch_cache.c ${LDLIBS}
	./test_syspatch_cache

# 10.E6 AP1 (D-A9 `SK-marker`, Gate-0 §9 P-A5): the SKU-discriminator battery.
# Drives the REAL apply_sysupgrade / cancel_sysupgrade / get_system_version /
# get_update_status handlers with OGMA_IMAGE_LAYOUT_MARKER, the /bsd.upgrade
# sentinel, the audit log and the update leaf -D'd into a scratch dir, and
# every exec macro on the driven paths repointed at a committed shim (the
# test-os-guard idiom): fake_sysupgrade.sh records the detached stage an
# appliance must NEVER receive; fake_pgrep.sh stands in for BOTH pgrep (the
# in-flight-stage probe) and pkill (the cancel), answering the `-f <abspath>`
# shape; fake_syspatch.sh is linked in for the errata read the version render
# never reaches (cold cache, no tick). Marker present => refused + audited +
# E_IMAGE_EDITION, no spawn; absent => today's stage, byte-identical.
# Fork/exec of committed fakes only => sanitized-leg safe (the
# test-syspatch-cache precedent). Needs ${BUILD_REV_H} like test-update-check
# (update.c includes build_rev.h when present).
test-image-marker: ${BUILD_REV_H}
	chmod +x daemon/test/fake_sysupgrade.sh daemon/test/fake_pgrep.sh \
		daemon/test/fake_syspatch.sh
	${CC} ${CFLAGS} \
		-DOGMA_IMAGE_LAYOUT_MARKER='"test_im_scratch/image-layout"' \
		-DOGMA_BSD_UPGRADE='"test_im_scratch/bsd.upgrade"' \
		-DOGMA_DB_DIR='"test_im_scratch"' \
		-DOGMA_UPDATE_DIR='"test_im_scratch/update"' \
		-DOGMA_UPDATE_PUBKEY_DIR='"test_im_scratch/keys"' \
		-DOGMA_LOG_SYSD='"test_im_scratch/audit.log"' \
		-DOGMA_SYSUPGRADE='"daemon/test/fake_sysupgrade.sh"' \
		-DOGMA_PGREP='"daemon/test/fake_pgrep.sh"' \
		-DOGMA_PKILL='"daemon/test/fake_pgrep.sh"' \
		-DOGMA_SYSPATCH='"daemon/test/fake_syspatch.sh"' \
		-DOGMA_UPDFETCH_HELPER='"daemon/test/fake_updfetch.sh"' \
		-DOGMA_SIGNIFY='"daemon/test/fake_signify.sh"' \
		-o test_image_marker \
		daemon/sysd/version.c daemon/sysd/update.c daemon/sysd/stage_record.c \
		daemon/sysd/edition.c \
		daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/common/os_release.c daemon/common/update_check.c \
		daemon/common/egress_url.c daemon/common/json.c \
		daemon/common/util.c daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c daemon/common/yaml_lite.c \
		daemon/test/test_image_marker.c ${LDLIBS}
	./test_image_marker

# 10.E10 S1 (contract §9 P2/P3/P4/P9): the two-step-update battery. Drives the
# REAL sysd handlers — stage_update / discard_update / apply_update, the tick
# (reap + ceiling + verify-in-sysd) and the `staged` block on get_update_status
# — in-process, with every path -D'd into test_us_scratch/ and EVERY exec on the
# driven paths repointed at a committed shim (the test-image-marker idiom):
# fake_updstage.sh plays the streaming helper (per-artifact modes: ok / restart /
# partial / http404 / timeout / usage), fake_signify.sh's -C arm verifies a
# cksum-bound list (content-sensitive, not crypto — the real signify -C is proven
# on the lab), fake_tar.sh emulates the snapshot + its member assertion,
# fake_pkg_add.sh / fake_pkg_info.sh drive §2 P5's "exit status is not a verdict"
# both ways, fake_uname.sh supplies the URL facts, and apply_system_power is a
# recording stub IN the test (P4: assert the reboot was NOT called). WATCHDOG_S
# and DISCARD_GRACE_S are pinned to seconds so the ceiling/grace arms are
# provable without a six-hour sleep. Fork/exec of committed fakes only =>
# sanitized-leg safe. Needs ${BUILD_REV_H} (update.c includes build_rev.h).
test-updstage: ${BUILD_REV_H}
	chmod +x daemon/test/fake_updstage.sh daemon/test/fake_signify.sh \
		daemon/test/fake_tar.sh daemon/test/fake_pkg_add.sh \
		daemon/test/fake_pkg_info.sh daemon/test/fake_uname.sh \
		daemon/test/fake_updfetch.sh daemon/test/fake_image_apply.sh \
		daemon/test/fake_sha256.sh daemon/test/fake_dd.sh
	${CC} ${CFLAGS} \
		-DOGMA_IMAGE_LAYOUT_MARKER='"test_us_scratch/image-layout"' \
		-DOGMA_DB_DIR='"test_us_scratch/db"' \
		-DOGMA_UPDATE_DIR='"'`pwd`'/test_us_scratch/db/update"' \
		-DOGMA_UPDATE_PUBKEY_DIR='"'`pwd`'/test_us_scratch/keys"' \
		-DOGMA_LOG_SYSD='"test_us_scratch/audit.log"' \
		-DOGMA_UPDSTAGE_ETC_ROOT='"test_us_scratch/root"' \
		-DOGMA_UPDATE_PKG_BASE_URL='"https://updates.invalid/pkg"' \
		-DOGMA_UPDATE_FS_BASE_URL='"https://updates.invalid/pkg"' \
		-DOGMA_UPDSTAGE_HELPER='"daemon/test/fake_updstage.sh"' \
		-DOGMA_UPDFETCH_HELPER='"daemon/test/fake_updfetch.sh"' \
		-DOGMA_SIGNIFY='"daemon/test/fake_signify.sh"' \
		-DOGMA_TAR='"daemon/test/fake_tar.sh"' \
		-DOGMA_PAX='"daemon/test/fake_tar.sh"' \
		-DOGMA_PKG_ADD='"daemon/test/fake_pkg_add.sh"' \
		-DOGMA_PKG_INFO='"daemon/test/fake_pkg_info.sh"' \
		-DOGMA_UNAME='"daemon/test/fake_uname.sh"' \
		-DOGMA_IMAGE_APPLY='"daemon/test/fake_image_apply.sh"' \
		-DOGMA_SHA256='"daemon/test/fake_sha256.sh"' \
		-DOGMA_DD='"daemon/test/fake_dd.sh"' \
		-DOGMA_UPDSTAGE_WATCHDOG_EFF_S=3 \
		-DOGMA_UPDSTAGE_DISCARD_GRACE_S=1 \
		-o test_updstage \
		daemon/sysd/stage.c daemon/sysd/stage_record.c \
		daemon/sysd/update.c daemon/sysd/edition.c \
		daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/common/update_check.c daemon/common/egress_url.c \
		daemon/common/json.c daemon/common/util.c daemon/common/validate.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_updstage.c ${LDLIBS}
	./test_updstage

# 10.E10 S1 (contract §9 P1): the REAL helper binary end to end against an
# in-test libtls server. Builds ogmaprotect-updstage from its shipped sources
# with the sink dir, CA file, stall alarm and package cap -D'd for the test
# (the same #ifndef seams the production build leaves at their defaults), then
# a driver that forks a libtls listener serving scripted responses (a full 200,
# an over-cap Content-Length, a stall, a 206 continuation, a 200 on a resume,
# a 206 whose Content-Range starts elsewhere, a 404) and asserts the sink
# bytes, the progress record, stdout and the exit code per arm — plus the
# allowlist negative (a sink outside the stage dir is USAGE and the server sees
# NO connection). The certificate is minted by openssl(1) at test time (never
# committed). OpenBSD-only by construction (libtls) and it execs real userland
# (openssl + the helper), so it lives in %OPENBSD_ONLY as real-userland-exec.
test-updstage-helper:
	${CC} ${CFLAGS} -DOGMA_EGRESS_STREAM \
		-DOGMA_UPDSTAGE_SINK_DIR='"test_ush_scratch/stage"' \
		-DOGMA_UPDSTAGE_CA_FILE='"test_ush_scratch/ca.pem"' \
		-DOGMA_UPDSTAGE_STALL_S=2 \
		-DOGMA_UPDSTAGE_MAX_PKG=65536 \
		-DOGMA_UPDSTAGE_PROGRESS_STEP=4096 \
		-o test_updstage_helper_bin ${UPDSTAGE_SRC} ${UPDSTAGE_LIBS}
	${CC} ${CFLAGS} -DOGMA_EGRESS_STREAM -o test_updstage_helper \
		daemon/common/egress_url.c daemon/common/egress_http.c \
		daemon/test/test_updstage_helper.c -ltls -lssl -lcrypto
	./test_updstage_helper

# 10.E9 S1 (PHASE-BOOT-FSCK-RESILIENCE.md §9 Tier 1) — the system.yaml canonical
# (fsck_autorepair): default-true, parse/validate/serialize round-trip, the
# unknown-key reject, the v99 reject, and the render mapping the marker
# follows (false => present, true => absent) as a pure decision. Same link
# closure as test-sysctl-canonical (the identity precedent), no I/O.
test-system-canonical:
	${CC} ${CFLAGS} -o test_system_canonical \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/system_canonical.c daemon/common/fsck_state.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_system_canonical.c ${LDLIBS}
	./test_system_canonical

# 10.E9 S1 (§9 Tier 1) — the sysd fsck battery, against the REAL handlers in a
# per-uid scratch tree (the test-sys-identity idiom): the record parser
# (strict) + detail; the two comparators (marker drift, the /etc/rc sentinel
# scan incl. unreadable); the boot scan — sentinel present => ONE record +
# CRITICAL audit + counter reset, absent => inert, restart => no re-fire, and
# the §6.2 STEPPED-CLOCK regression (the wall clock jumps between rc's flush
# and sysd's settle and the alert still fires, because scoping is the tmpfs
# sentinel and never a clock read); get_fsck_setting / set_fsck_setting with
# the disable floor, the render mapping and the canonical save; the two drift
# rows on both walk channels. Every path -D'd; no exec anywhere on the driven
# paths (fsck.c forks nothing) => sanitized-leg safe.
test-fsck:
	${CC} ${CFLAGS} -I daemon/sysd -o test_fsck \
		-DOGMA_TEST_ROOT='"/tmp/ogma-fsck-'`id -u`'"' \
		-DOGMA_CONFIG_DIR='OGMA_TEST_ROOT "/config"' \
		-DOGMA_REVISIONS_DIR='OGMA_TEST_ROOT "/revisions"' \
		-DOGMA_STAGING_DIR='OGMA_TEST_ROOT "/staging"' \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/run"' \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_FSCK_MARKER='OGMA_TEST_ROOT "/etc/fsck-manual"' \
		-DOGMA_FSCK_STATE_DIR='OGMA_TEST_ROOT "/fsck"' \
		-DOGMA_FSCK_RC_PATH='OGMA_TEST_ROOT "/etc/rc"' \
		-DOGMA_FSCK_REBOOT_COUNTER='OGMA_TEST_ROOT "/etc/fsck-reboots"' \
		-DOGMA_FSCK_PENDING='OGMA_TEST_ROOT "/etc/fsck-pending"' \
		-DOGMA_IMAGE_LAYOUT_MARKER='OGMA_TEST_ROOT "/etc/image-layout"' \
		-DOGMA_FSCK_RETRY_S=0 \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c \
		daemon/common/revision.c daemon/common/text_diff.c \
		daemon/common/routes_canonical.c daemon/common/config_manifest.c \
		daemon/common/apply_lock.c daemon/common/boot_drift.c \
		daemon/common/json.c daemon/common/yaml_lite.c \
		daemon/common/system_canonical.c daemon/common/fsck_state.c \
		daemon/sysd/fsck.c daemon/sysd/edition.c daemon/sysd/audit.c \
		daemon/test/test_fsck.c ${LDLIBS}
	-chmod -R u+rwx /tmp/ogma-fsck-`id -u` 2>/dev/null
	rm -rf /tmp/ogma-fsck-`id -u`
	./test_fsck
	rm -rf /tmp/ogma-fsck-`id -u`

# 10.E9 S1 (§9 Tier 1, the FAIL-CLOSED injection) — the rc patch's shell half,
# etc/rc.fsck.subr, driven under /bin/sh with fake fsck/pfctl/sysctl/mount/
# reboot on PATH in a per-uid scratch root (the fake_*.sh idiom, generated by
# the script itself): repair-this-boot + empty or `pfctl -nf`-dirty pf.conf
# => pf HELD + forwarding 0 (the §2.0-C ship-blocker); clean pf.conf => not
# held; no repair => never held; the preen arm (marker => stock halt, loop
# breaker => DEGRADED, exit 0/4/8), and the D-seam flush (record, sentinel,
# the exit-4 carry, the §6.6 /var-not-mounted fallback). Mutation: drop the
# hold branch in the subr and the first arm reds. Pure POSIX sh so both CI
# legs run it; no root, no real tool.
test-fsck-guard:
	sh daemon/test/test_fsck_guard.sh

# 10.E9 S2 (§9 Track B, Tier 1) — the appliance image edition's shell half:
# etc/rc.image.subr under /bin/sh with fake mount/umount/fsck/logger on PATH in
# a per-uid scratch root (the edition gate, the /cfg mount + fresh-/cfg
# skeleton, the seam symlink check, library_aslr=NO enforcement, the
# /cfg-saved fsck marker PROBE and its seam into the REAL rc.fsck.subr — the
# VD-E9-4 read), scripts/ogma-cfg-save.sh against the REAL save-list manifest
# (the list is derived, never hand-written), and contrib/resflash/check-label.sh
# against a captured ratified disklabel. Two mutants prove the arms can fail.
# Pure POSIX sh so both CI legs run it; no root, no real tool, no vnd.
test-image-guard:
	sh daemon/test/test_image_guard.sh

# 10.E9 S2 part 2 (Appliance D-A7 RB-boot-counter; contract §10.3) — sysd's
# boot-ok writer against the REAL image_boot.c in a per-uid scratch tree: the
# software-edition gate (never execs on Lite), the management-plane gate
# (authd's socket PATH, -D'd), the exec of the helper (-D'd at the committed
# fake_bootok.sh -- the exec-macro guard forces this), the retry window, the
# MAX_TRIES bound with its CRITICAL audit line, and the helper's own
# "software edition" answer. The test-only reset seam is compiled in HERE and
# nowhere else. Fork/exec of the committed fake only => sanitized-leg safe.
test-image-boot: ${BUILD_REV_H}
	chmod +x daemon/test/fake_bootok.sh
	${CC} ${CFLAGS} -I daemon/sysd -o test_image_boot \
		-DOGMA_TEST_ROOT='"/tmp/ogma-image-boot-'`id -u`'"' \
		-DOGMA_IMAGE_BOOT_TEST_HOOKS \
		-DOGMA_GW_STATE_DIR='OGMA_TEST_ROOT "/run"' \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_IMAGE_LAYOUT_MARKER='OGMA_TEST_ROOT "/etc/image-layout"' \
		-DOGMA_IMAGE_MGMT_SOCK='OGMA_TEST_ROOT "/www/ogmaprotect.sock"' \
		-DOGMA_IMAGE_BOOTOK='"daemon/test/fake_bootok.sh"' \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/yaml_lite.c \
		daemon/sysd/image_boot.c daemon/sysd/edition.c \
		daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/test/test_image_boot.c ${LDLIBS}
	rm -rf /tmp/ogma-image-boot-`id -u`
	./test_image_boot
	rm -rf /tmp/ogma-image-boot-`id -u`

# 10.E9 S2a follow-up (VD-E9-S2A-1 (a); contract sec 6.13) -- the durable-paths
# BOOT SELF-CHECK against the REAL cfg_persist.c in a per-uid scratch tree: the
# software-edition gate, the four seam teeth (ENOENT/RAM/wrong-mount/shared
# device), UNKNOWN never reading as clean, the installed save-list projection's
# placement rule, the MAX_NAMED bound and the "changed nothing on disk"
# assertion. The statfs(2) probe is the one half a non-OpenBSD leg cannot run
# for real, so the recipe compiles the test-only probe seam
# (-DOGMA_CFG_PERSIST_TEST_HOOKS) HERE and nowhere else and the decision core
# is driven directly. No fork, no exec => sanitized-leg safe.
test-cfg-persist:
	${CC} ${CFLAGS} -I daemon/sysd -o test_cfg_persist \
		-DOGMA_TEST_ROOT='"/tmp/ogma-cfg-persist-'`id -u`'"' \
		-DOGMA_CFG_PERSIST_TEST_HOOKS \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_IMAGE_LAYOUT_MARKER='OGMA_TEST_ROOT "/etc/image-layout"' \
		-DOGMA_CFG_SAVELIST='OGMA_TEST_ROOT "/etc/ogmaprotect/cfg-savelist"' \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/yaml_lite.c \
		daemon/sysd/cfg_persist.c daemon/sysd/edition.c daemon/sysd/audit.c \
		daemon/test/test_cfg_persist.c ${LDLIBS}
	rm -rf /tmp/ogma-cfg-persist-`id -u`
	./test_cfg_persist
	rm -rf /tmp/ogma-cfg-persist-`id -u`

# Phase 10.A6: print the compiled ogma_qualified_releases[] one per line, for the
# ci.yml "guard cannot out-run CI" self-check. Utility — NOT part of `make test`.
print-qualified-releases:
	@${CC} ${CFLAGS} -o print_qualified_releases \
		daemon/common/os_release.c \
		daemon/test/print_qualified_releases.c ${LDLIBS}
	@./print_qualified_releases

# 10.A6 VD-A6-2: compile-only half of test-fixture-parity, run as a BLOCKING
# c-tests step (ci.yml "Parity lane cannot bit-rot"). The openbsd-next lane is
# continue-on-error BY DESIGN, so the lane's own compile failures are swallowed
# with everything else — a bit-rotted test_fixture_parity.c reds a lane nobody
# is required to look at, and "lane broken" masquerades as "lane green". This
# target is the lane's compile, hoisted where a failure blocks. COMPILE ONLY:
# the binary asserts a live OpenBSD kernel's uname/sysctl/ifconfig formats,
# which no ubuntu runner satisfies — the RUN stays in the lane. (The
# print_qualified_releases twin needs no hoist: the blocking "Guard cannot
# out-run CI" step already compiles it every push.) Utility prefix like
# print-qualified-releases, deliberately not test-*/check-*: it is one half of
# a carve-out target, not a suite test the coverage guard should ledger twice.
build-fixture-parity:
	${CC} ${CFLAGS} -o test_fixture_parity \
		daemon/common/os_release.c \
		daemon/test/test_fixture_parity.c ${LDLIBS}

# Phase 10.A6: best-effort format-parity smoke for the non-blocking next-release CI
# lane (ci.yml openbsd-next). Runs on a LIVE kernel — captures uname/sysctl/ifconfig
# and asserts their FORMAT still parses (NOT the logic couplings). NOT part of `make
# test` (it needs a running OpenBSD kernel); the openbsd-next job runs it directly.
# Compiles via the build-fixture-parity prerequisite above (VD-A6-2): ONE compile
# recipe, shared, so the blocking bit-rot leg and this run cannot drift apart.
test-fixture-parity: build-fixture-parity
	./test_fixture_parity

# Phase 9.O4.1: the power-action classifier (ogma_power_action_*, validate.c) + the
# apply_system_power confirm-floor REJECT paths (sysd/power.c). Links power.c + the sysd
# exec/audit helpers; ogma_sysd_upgrade_staged() (version.c) is stubbed in the test so
# the unit links without the version closure. Only reject paths run — an accepted
# action would fork a real shutdown(8) (covered by the live smoke instead).
# 10.F8 S3: power.c now calls the intent-record writer/clearer (power_pending.c),
# which brings json.c (+ yaml_lite.c, its one dep) for the read op's emit — linked,
# not stubbed, so the TU the daemon ships is the TU under test; the record path
# is -D'd under the test root so a reject path that reaches it (none does today)
# could never touch /var/run. The 10.F8 S3 actor-charset validator
# (ogma_valid_actor_name, validate.c) is pinned here beside its power siblings.
test-sys-power:
	${CC} ${CFLAGS} -o test_sys_power \
		-DOGMA_TEST_ROOT='"/tmp/ogma-sys-power-'`id -u`'"' \
		-DOGMA_SYSD_POWER_RECORD='OGMA_TEST_ROOT "/sysd.power"' \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/audit.log"' \
		daemon/sysd/power.c daemon/sysd/power_pending.c \
		daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/common/util.c daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c daemon/common/json.c daemon/common/yaml_lite.c \
		daemon/test/test_sys_power.c ${LDLIBS}
	rm -rf /tmp/ogma-sys-power-`id -u`
	mkdir -p /tmp/ogma-sys-power-`id -u`
	./test_sys_power

# 10.F8 S3: the pending-power intent record + live probe + get_system_power_pending
# (sysd/power_pending.c), driven end to end with every input faked: the record
# path under the per-uid scratch root, OGMA_PGREP -> the committed fake_pgrep.sh shim
# (exit code + marker match driven by files under $OGMA_FAKE_PGREP_DIR, the
# fake_rcctl_svc.sh shape — never the real /usr/bin/pgrep), the sysd audit log
# under the root, the probe alarm cut to 1 s (so the signalled-child arm costs one
# second, not three) and the spawn-race grace pinned. Every cell of the contract
# §5.3 truth table runs, incl. the fault-injected "failing probe leaves the record
# intact" case and its flip-the-stub negative. Exec-free beyond the shim -> sanitized.
test-power-pending:
	chmod +x daemon/test/fake_pgrep.sh
	${CC} ${CFLAGS} -I daemon/sysd -o test_power_pending \
		-DOGMA_TEST_ROOT='"/tmp/ogma-power-pending-'`id -u`'"' \
		-DOGMA_SYSD_POWER_RECORD='OGMA_TEST_ROOT "/sysd.power"' \
		-DOGMA_PGREP='"daemon/test/fake_pgrep.sh"' \
		-DOGMA_LOG_SYSD='OGMA_TEST_ROOT "/audit.log"' \
		-DOGMA_SYSD_POWER_PROBE_TIMEOUT_S=1 \
		-DOGMA_SYSD_POWER_GRACE_S=3 \
		daemon/sysd/power_pending.c daemon/sysd/sysd_exec.c daemon/sysd/audit.c \
		daemon/common/util.c daemon/common/validate.c daemon/common/net_l2.c \
		daemon/common/secret.c daemon/common/json.c daemon/common/yaml_lite.c \
		daemon/test/test_power_pending.c ${LDLIBS}
	rm -rf /tmp/ogma-power-pending-`id -u`
	mkdir -p /tmp/ogma-power-pending-`id -u`
	./test_power_pending

# Phase 7b.1.2a: the WireGuard wgpubkey-line parser (ogma_wg_parse_pubkey_line
# in ifconfig.c). The live derive forks ifconfig and is covered by the lab
# smoke; this pins the brittle string parser. Links the same source set as
# test-hostname (ifconfig.c pulls in ogma_valid_wg_key from hostname.c).
test-wg-derive:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_wg_derive \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_wg_derive.c ${LDLIBS}
	./test_wg_derive

# Phase 7b.1.2d: pin the PURE WireGuard drift parser + comparator + JSON builder
# (ogma_wg_parse_live / ogma_wg_drift_compare / ogma_json_build_wg_drift in
# wg_drift.c) with in-memory `ifconfig wgN` blobs and canonical net.yaml
# fragments — no live ifconfig (the netd op + authd derive are covered by the
# lab smoke). No exec, so it runs on EVERY tier (Linux c-tests + OpenBSD), the
# test-wg-derive precedent. Links the same source set + wg_drift.c. NOTE:
# hostname.c/ifconfig.c are linked ONLY for the canonical/validate symbol
# closure (ifconfig.c pulls ogma_valid_wg_key etc.); this test calls no capture/
# exec function — the live ogma_ifconfig_wg_live_capture is the smoke's job.
test-wg-drift:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_wg_drift \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/wg_drift.c \
		daemon/common/drift.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_wg_drift.c ${LDLIBS}
	./test_wg_drift

test-tunnel-drift:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_tunnel_drift \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/tunnel_drift.c \
		daemon/common/drift.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_tunnel_drift.c ${LDLIBS}
	./test_tunnel_drift

# 9.R3.7 IPv6 drift source-tagging exemption (SLAAC/temporary/PD). Same link
# closure as test-dhcp (the tagger + plain/dhcp-aware drift comparators live in
# dhcp.c + ifconfig.c; the config-wide PD delegate resolver in net_l2.c).
test-v6-drift:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_v6_drift \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/revision.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/backup.c daemon/common/backup_archive.c \
		daemon/common/backup_validate.c \
		daemon/common/schema_state.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/common/ops.c \
		daemon/common/cap_hmac.c \
		daemon/test/test_v6_drift.c ${LDLIBS}
	./test_v6_drift

test-iface-stats:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_iface_stats \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/iface_stats.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_iface_stats.c ${LDLIBS}
	./test_iface_stats

test-bridge-stats:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_bridge_stats \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
		daemon/common/canonical.c daemon/common/config_manifest.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/revision.c daemon/common/iface_stats.c \
		daemon/common/bridge_stats.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/common/yaml_lite.c \
		daemon/test/test_bridge_stats.c ${LDLIBS}
	./test_bridge_stats

# Shared source list for the YAML parser fuzzer. Single source of truth for
# BOTH the default deterministic-stream build (test-fuzz, compiled on every CI
# push; its seed is the compile-time default locally and a per-run value
# derived from run identity on both blocking CI legs since 10.D2 E2) and
# the coverage-guided libFuzzer build (fuzz-libfuzzer). Keeping one list means a
# newly added canonical parser can never be silently omitted from the
# libFuzzer target — the L5-01 drift class. fuzz_yaml.c's fuzz_one() dispatches
# to every domain below, so each domain's parser MUST be linked here.
#
# 10.D2 S2: "MUST be linked here" is now MACHINE-CHECKED rather than asserted —
# scripts/check_fuzz_reach.pl (the `fuzz-reach` target, a prerequisite of
# test-fuzz) proves set(daemon/common/*_canonical.c) is a subset of this list.
# The assertion was FALSE on main until this slice added ipsec_canonical.c and
# arp_canonical.c, which is why the guard could not ship in S1.
FUZZ_SRCS = \
	daemon/common/jsmn.c daemon/common/ogma_parse.c \
	daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
	daemon/common/revision.c daemon/common/config_manifest.c \
	daemon/common/validate.c daemon/common/util.c \
	daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
	daemon/common/canonical.c \
	daemon/common/routes_canonical.c daemon/common/routes_compose.c \
	daemon/common/pf_canonical.c daemon/common/pf_structured.c \
	daemon/common/dns_canonical.c \
	daemon/common/dhcpd_canonical.c \
	daemon/common/routing_canonical.c \
	daemon/common/time_canonical.c \
	daemon/common/auth_canonical.c \
	daemon/common/cert_canonical.c \
	daemon/common/gateways_canonical.c \
	daemon/common/remotelog_canonical.c \
	daemon/common/alerts_canonical.c \
	daemon/common/egress_url.c \
	daemon/common/sysctl_canonical.c \
	daemon/common/identity_canonical.c \
	daemon/common/system_canonical.c \
	daemon/common/ipsec_canonical.c \
	daemon/common/arp_canonical.c \
	daemon/common/yaml_lite.c \
	daemon/netd/hostname.c daemon/netd/ifconfig.c \
	daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
	daemon/test/legacy_parse.c \
	daemon/test/legacy_build.c \
	daemon/test/fuzz_yaml.c

# Build (only) the default-mode fuzzer binary. fuzz-libfuzzer.yml uses it as the
# seed dumper (OGMA_FUZZ_DUMP_SEEDS) to populate the coverage-guided corpus from
# seeds[] — one source of truth, no seed-file drift. Kept separate from test-fuzz
# so the workflow can build it without -fsanitize=fuzzer (which would collide
# with the default build's own main()).
fuzz-build:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_fuzz ${FUZZ_SRCS} ${LDLIBS}

# 10.D2 S2 (contract §7 D9 / M17): the durable ratchet against this slice's own
# finding class — a hand-maintained list drifting while the fuzz leg stays green.
# Asserts set(daemon/common/*_canonical.c) is a subset of FUZZ_SRCS, that the
# enum / name-table / fuzz_one() roster agree as SETS, and that no fuzz arm uses
# another domain's index. Red-on-arrival before this slice added the two parsers,
# which is why it could not ship in S1. Fail-closed proof: t/check_fuzz_reach.t.
#
# THREE naming/wiring constraints, all load-bearing (see the script header):
#   * target prefixed `fuzz-`, NOT `check-`, so check_ci_coverage.pl's recipe
#     regex never sees it => no `test:` aggregate edit, no sanitized-targets
#     edit, no %NOT_AGGREGATED row;
#   * script named check_fuzz_reach.pl, because check_smoke_registry.pl
#     auto-allowlists check_*.pl (any other name is an unclassified tracked
#     scripts/ file and trips its T1 bijection);
#   * EMPTY prerequisite list — check_smoke_registry.pl's T8 derives the
#     `make dist` ship-allowlist from Makefile DEPENDENCY lists and skips only
#     test-/check-/gen-/lint-/dist targets, so a `fuzz-reach: scripts/...`
#     dependency would be harvested as a shipped artifact and then red against
#     .gitattributes' wholesale `scripts/** export-ignore`.
fuzz-reach:
	perl scripts/check_fuzz_reach.pl

# 10.D2 S1 (contract M16): two DURABLE assertions on the reproducibility floor,
# in the EXISTING recipe — the slice adds no test-*/check-* target, so without
# these the banner and the fail-loud env parse could be deleted later and no
# check would notice, which is the exact rot H1 exists to prevent.
#
# Shape rules (all three were live traps, one reproduced on the lab):
#   1. The expected-to-FAIL command must sit in an `if` condition. OpenBSD make
#      runs error-checked recipe lines under `sh -ec`, and a bare
#      `out=$$(cmd-that-exits-2)` takes the substitution's status, so the recipe
#      aborts with *** Error 2 before the assertion's own logic runs — and
#      test-fuzz is in the `test:` aggregate the authoritative openbsd CI leg
#      runs. An `if` condition both consumes the status and is -e exempt.
#   2. `$$(` never `$(` — make would expand it itself, yielding an empty string
#      and an assertion that passes on nothing.
#   3. Assertion 2 chains with `&&`, never `;` — under GNU make (no -e) a `;`
#      would discard grep's status and be permanently, invisibly vacuous.
#   4. Every assertion captures the child's output and prints it on failure. A
#      bare `cmd | grep -q X && echo ok` reds with an EMPTY message — @ hides
#      the command and grep -q eats both streams — so a corpus-vacuity refusal,
#      an exit-2 env diagnostic and a genuine banner regression are
#      indistinguishable.
#   5. The two assertions UNSET the inherited controls in a subshell; they must
#      not use `NAME=` to clear them, because an empty value is itself a hard
#      error since S1, so `OGMA_FUZZ_STRICT=` would exit 2 and misfire the
#      assertion (measured on the lab). scripts/_fuzz_triage.py invokes
#      `make test-fuzz` with OGMA_FUZZ_DUMP_DIR (and now SEED/STRICT) set.
#   6. OGMA_FUZZ_INPUT and OGMA_FUZZ_DUMP_SEEDS are REFUSED at the head, not
#      unset: each short-circuits main() before the mutation loop, and the real
#      `./test_fuzz` on the last line is outside both subshells, so unsetting
#      them for the assertions alone would leave THAT run vacuously green on a
#      target the authoritative openbsd leg runs via the `test:` aggregate.
test-fuzz: fuzz-reach fuzz-build fuzz-standalone-build
	@if [ -n "$${OGMA_FUZZ_INPUT+x}" ] || [ -n "$${OGMA_FUZZ_DUMP_SEEDS+x}" ] \
		|| [ -n "$${OGMA_FUZZ_DUMP_DICT+x}" ]; then \
		echo "FAIL: unset OGMA_FUZZ_INPUT / OGMA_FUZZ_DUMP_SEEDS / OGMA_FUZZ_DUMP_DICT before 'make test-fuzz'"; \
		echo "      each short-circuits main() and would make this target vacuously green"; \
		exit 1; fi
	@if out=$$( (unset OGMA_FUZZ_DUMP_DIR OGMA_FUZZ_STRICT; \
		OGMA_FUZZ_SEED=0x10 OGMA_FUZZ_ITERS=1 ./test_fuzz) 2>&1 ); then \
		echo "FAIL: a malformed OGMA_FUZZ_SEED was ACCEPTED (fail-loud env parse gone)"; \
		echo "$$out"; exit 1; fi; \
	case "$$out" in *OGMA_FUZZ_SEED*) echo "ok fuzz self-test 1 (fail-loud OGMA_FUZZ_* parse)";; \
		*) echo "FAIL: rejected the seed but printed no OGMA_FUZZ_SEED diagnostic:"; \
		   echo "$$out"; exit 1;; esac
	@if out=$$( (unset OGMA_FUZZ_DUMP_DIR OGMA_FUZZ_STRICT OGMA_FUZZ_SEED; \
		OGMA_FUZZ_ITERS=1 ./test_fuzz) 2>&1 ); then :; else \
		echo "FAIL: the ITERS=1 self-test run exited non-zero:"; echo "$$out"; exit 1; fi; \
	case "$$out" in \
	*"fuzz_yaml: seed="*" iters="*" corpus="*) \
		echo "ok fuzz self-test 2 (start-of-run seed banner)";; \
	*) echo "FAIL: no 'seed=/iters=/corpus=' start-of-run banner:"; \
	   echo "$$out"; exit 1;; esac
	./test_fuzz
	# 10.D2.1 (VD-D2-4/-5): the standalone harnesses run on the SAME legs,
	# inheriting OGMA_FUZZ_SEED/OGMA_FUZZ_ITERS (the authoritative openbsd
	# line's 100k applies here too); the head-of-recipe refusals above
	# already cover their OGMA_FUZZ_INPUT / DUMP_SEEDS short-circuits.
	./fuzz_untrusted
	./fuzz_arp_feed

# Coverage-guided (libFuzzer) build of the SAME sources as test-fuzz. Compile
# only — fuzz-libfuzzer.yml runs fuzz_yaml_lf against the seed corpus. Invoked
# as: make CC=clang \
#          CFLAGS="-fsanitize=fuzzer,address,undefined ... -DOGMA_HAVE_LIBMD" \
#          LDLIBS=-lmd fuzz-libfuzzer
# -DOGMA_FUZZ_USE_LIBFUZZER switches fuzz_yaml.c to the LLVMFuzzerTestOneInput
# entry point (no main(); libFuzzer supplies its own).
fuzz-libfuzzer:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -DOGMA_FUZZ_USE_LIBFUZZER \
		-o fuzz_yaml_lf ${FUZZ_SRCS} ${LDLIBS}

# 10.D2.2 (VD-D2-20): -Werror DRY-BUILD of the OGMA_FUZZ_USE_LIBFUZZER arm, run
# as its own blocking c-tests step (ci.yml). That arm -- LLVMFuzzerInitialize,
# the atexit summary, the non-fatal switch, the per-domain dump reservation --
# lives only in daemon/test/fuzz_yaml.c and was compiled by NOTHING a required
# check runs, so a syntax or type error there was caught by the scheduled
# fuzz-libfuzzer lane a day later (the L5-01 dead-build-line lesson applied to
# the same file's other half). Compile-only, -o /dev/null: the LINK needs
# libclang_rt.fuzzer (absent on OpenBSD) and stays the scheduled lane's job.
# -Werror HERE, deliberately stricter than the lane's own -Wall -Wextra (no
# -Werror): the lane tolerates a new warning for a day, a required check must
# not. ci.yml invokes this with CC=clang (the lane's compiler, so the lane's
# diagnostics are the ones that gate); a bare `make fuzz-lf-drybuild` also
# works on the lab, whose cc IS clang. Flags mirror the lane's build of this
# TU, not PORTABLE_CFLAGS: no sanitizers, so the dry-build cannot redden on an
# ASan/UBSan-only diagnostic the lane itself never sees.
# Naming/wiring constraints (the fuzz-reach precedent, see its header):
#   * `fuzz-` prefix, NOT test-/check- -- invisible to check_ci_coverage.pl's
#     recipe harvest AND its workflow `make`-line harvest, so no `test:`
#     aggregate edit, no sanitized-targets edit, no %NOT_AGGREGATED row;
#   * EMPTY prerequisite list -- check_smoke_registry.pl's T8 dist harvest
#     reads Makefile dependency lists and skips only test-/check-/gen-/lint-/
#     dist targets.
fuzz-lf-drybuild:
	${CC} -Wall -Wextra -Werror -O1 -DOGMA_HAVE_LIBMD -DOGMA_HAVE_IFCONFIG \
		-DOGMA_FUZZ_USE_LIBFUZZER -I daemon/common \
		-c -o /dev/null daemon/test/fuzz_yaml.c
	${CC} -Wall -Wextra -Werror -O1 -DOGMA_HAVE_LIBMD \
		-DOGMA_FUZZ_USE_LIBFUZZER -I daemon/common \
		-c -o /dev/null daemon/test/fuzz_untrusted.c
	${CC} -Wall -Wextra -Werror -O1 -DOGMA_HAVE_LIBMD \
		-DOGMA_FUZZ_USE_LIBFUZZER -I daemon/common \
		-c -o /dev/null daemon/test/fuzz_arp_feed.c

# 10.D2.1 (VD-D2-4 / VD-D2-5): the STANDALONE fuzz harnesses for the untrusted
# NON-canonical parsers — fuzz_untrusted (tar_read_archive_mem + the dhcpd
# server-lease parser) and fuzz_arp_feed (the streaming `arp -an` reader).
# Deliberately NOT folded into FUZZ_SRCS/fuzz_yaml.c: joining that corpus
# re-rolls the deterministic stream for all 17 domains and owes a baseline lab
# pass; these carry their own seeds, their own streams, and postcondition (not
# round-trip) oracles — see daemon/test/fuzz_standalone.inc. Source lists are
# single-sourced between the default and libFuzzer builds, the FUZZ_SRCS
# discipline (L5-01: a per-build hand copy is how a parser goes missing).
# Same naming/wiring constraints as fuzz-reach/fuzz-lf-drybuild (see their
# headers): `fuzz-` prefix, EMPTY prerequisite lists. The default binaries RUN
# on every push from the test-fuzz recipe below — the anti-rot leg; the
# coverage-guided binaries run daily in fuzz-libfuzzer.yml.
FUZZ_UNTRUSTED_SRCS = \
	daemon/common/backup_archive.c \
	daemon/common/dhcpd_leases.c \
	daemon/common/cap_hmac.c \
	daemon/common/validate.c \
	daemon/common/util.c \
	daemon/common/net_l2.c \
	daemon/common/secret.c \
	daemon/test/fuzz_untrusted.c

FUZZ_ARP_FEED_SRCS = \
	daemon/common/jsmn.c daemon/common/ogma_parse.c \
	daemon/common/validate.c daemon/common/util.c daemon/common/json.c \
	daemon/common/net_l2.c daemon/common/secret.c daemon/common/revision.c \
	daemon/common/routes_canonical.c daemon/common/config_manifest.c \
	daemon/common/yaml_lite.c daemon/common/apply_lock.c \
	daemon/common/arp_canonical.c daemon/common/drift.c \
	daemon/arpd/audit.c \
	daemon/test/fuzz_arp_feed.c

fuzz-standalone-build:
	${CC} ${CFLAGS} -o fuzz_untrusted ${FUZZ_UNTRUSTED_SRCS} ${LDLIBS}
	${CC} ${CFLAGS} -o fuzz_arp_feed ${FUZZ_ARP_FEED_SRCS} ${LDLIBS}

fuzz-standalone-libfuzzer:
	${CC} ${CFLAGS} -DOGMA_FUZZ_USE_LIBFUZZER \
		-o fuzz_untrusted_lf ${FUZZ_UNTRUSTED_SRCS} ${LDLIBS}
	${CC} ${CFLAGS} -DOGMA_FUZZ_USE_LIBFUZZER \
		-o fuzz_arp_feed_lf ${FUZZ_ARP_FEED_SRCS} ${LDLIBS}

# 10.D2.3 (VD-D2-18): the LF end-of-run summary contract, pinned from the C
# side. fuzz-libfuzzer.yml's gate 3a extracts the violation count with an
# anchored sed over ONE emitter line and gate 4 keys on the dumps line's
# `dir=` — but the emitter and the workflow only ever agreed on a STRING, not
# a meaning. test_fuzz_summary.c #includes fuzz_yaml.c (lf_summary and its
# counters are static — the test_netd_batch/dispatch.c idiom), defining
# OGMA_FUZZ_USE_LIBFUZZER itself, so the source list below is FUZZ_SRCS minus
# fuzz_yaml.c. That copy cannot drift silently: fuzz_one() (included) calls
# every domain's parser, so a source added to FUZZ_SRCS but not here is a
# LINK failure in the same PR.
test-fuzz-summary:
	${CC} ${CFLAGS} -DOGMA_HAVE_IFCONFIG -o test_fuzz_summary \
		daemon/common/jsmn.c daemon/common/ogma_parse.c \
		daemon/common/proto.c daemon/common/json.c daemon/common/json_pf.c \
		daemon/common/revision.c daemon/common/config_manifest.c \
		daemon/common/validate.c daemon/common/util.c \
		daemon/common/net_l2.c daemon/common/secret.c daemon/common/dhcp.c \
		daemon/common/canonical.c \
		daemon/common/routes_canonical.c daemon/common/routes_compose.c \
		daemon/common/pf_canonical.c daemon/common/pf_structured.c \
		daemon/common/dns_canonical.c \
		daemon/common/dhcpd_canonical.c \
		daemon/common/routing_canonical.c \
		daemon/common/time_canonical.c \
		daemon/common/auth_canonical.c \
		daemon/common/cert_canonical.c \
		daemon/common/gateways_canonical.c \
		daemon/common/remotelog_canonical.c \
		daemon/common/alerts_canonical.c \
		daemon/common/egress_url.c \
		daemon/common/sysctl_canonical.c \
		daemon/common/identity_canonical.c \
		daemon/common/system_canonical.c \
		daemon/common/ipsec_canonical.c \
		daemon/common/arp_canonical.c \
		daemon/common/yaml_lite.c \
		daemon/netd/hostname.c daemon/netd/ifconfig.c \
		daemon/common/carp_drift.c daemon/common/pfsync_drift.c daemon/common/drift.c \
		daemon/test/legacy_parse.c \
		daemon/test/legacy_build.c \
		daemon/test/test_fuzz_summary.c ${LDLIBS}
	./test_fuzz_summary

test: test-yaml-lite test-ops test-drift test-window-gate test-proxy-slice test-proxy-transport test-daemon-inventory check-daemon-inventory check-rate-golden check-drift-registry check-class-golden check-req-golden test-egress check-egress-single-source check-capacity-matrix test-capacity-matrix test-inventory-fixtures test-authd-support-walk test-daemon-base test-frame test-lateral test-cap test-idempotency test-wg-derive test-hostname test-proto test-op-crossing test-req-fields test-req-emit test-audit test-audit-verify test-offline-verify test-rbac test-authorize test-auth-db test-auth-db-resilience test-auth-clock test-peer test-revision test-config-manifest test-schema-state test-schema-converge test-backup test-backup-sign test-backup-encrypt test-sign-helper test-backup-coordinator test-net-l2 test-routes test-routes-priority test-rtd test-rtd-backup test-rtd-drift test-pf test-pf-pending test-confirm-txn test-boot-drift test-mfa-record test-restore-journal test-restore-daemons test-route-pending test-gateways-pending test-wg-pending test-tunnel-pending test-address-pending test-carp-pending test-v6-confirm test-routing-pending test-routing-reload test-wg-apply test-netd-batch test-netd-delete test-order-repair test-wg-drift test-tunnel-drift test-v6-drift test-iface-stats test-bridge-stats test-dns test-dnsd test-dns-pending test-dhcp-server test-dhcpd-daemon test-dhcpd-apply test-routing test-routing-status test-routing-capture test-diag-view test-pf-states test-pf-table test-pf-bruteforce test-log-lines test-pflog test-hasync test-secrets test-authd-secrets test-authd-frontdoor test-authd-jobs check-authd-parent-pledge test-authd-migrate test-authd-routing-binding test-authd-ipsec-binding test-authd-route test-authd-wg test-authd-wg-apply test-authd-pppoe test-dhcp test-dhcp6leased test-rad test-ipv6-ops test-authd-dhcp-txn test-authd-dhcp-recover test-authd-routing-job test-authd-ipsec-job test-authd-ipsec-pki-job test-authd-archive-job test-authd-support-job test-diag test-health test-arp test-gateways test-gwmon-probe test-supervision test-alertd test-alert-df test-alertd-capture test-alertd-apply test-alertd-cfgdrift test-time test-timed-apply test-sysctl-canonical test-cert-canonical test-ipsec-canonical test-ipsec-render-bound test-ipsec-ikev1 test-yaml-roundtrip test-ipsec-gate test-ipsec-txn test-ipsec-pending test-ipsec-apply test-ipsec-pki test-authd-ipsec-pki test-ipsec-pf test-sys-identity test-sys-svc test-svc-expect test-sys-power test-power-pending test-gwmon-unveil test-os-release test-os-release-eol test-os-guard test-host-facts test-update-check test-e6-ctrl-server test-release-sign test-syspatch-cache test-image-marker test-updstage test-updstage-helper test-system-canonical test-fsck test-fsck-guard test-image-guard test-image-boot test-cfg-persist test-totp test-schema-version test-fuzz test-fuzz-summary test-perl

.PHONY: all dist install live-verify-status clean lint-docs fuzz-build fuzz-reach fuzz-libfuzzer fuzz-lf-drybuild fuzz-standalone-build fuzz-standalone-libfuzzer test test-yaml-lite test-ops test-drift test-window-gate test-proxy-slice test-proxy-transport test-daemon-inventory check-daemon-inventory gen-inventory gen-drift-registry check-rate-golden check-drift-registry check-class-golden check-req-golden gen-req-golden test-egress check-egress-single-source check-capacity-matrix test-capacity-matrix test-inventory-fixtures test-authd-support-walk test-daemon-base test-frame test-lateral test-cap test-idempotency test-wg-derive test-hostname test-proto test-op-crossing test-req-fields test-req-emit test-audit test-audit-verify test-offline-verify test-rbac test-authorize test-auth-db test-auth-db-resilience test-auth-clock test-peer test-revision test-config-manifest test-schema-state test-schema-converge test-backup test-backup-sign test-backup-encrypt test-sign-helper test-backup-coordinator test-net-l2 test-routes test-routes-priority test-rtd test-rtd-backup test-rtd-drift test-pf test-pf-pending test-confirm-txn test-boot-drift test-mfa-record test-restore-journal test-restore-daemons test-route-pending test-gateways-pending test-wg-pending test-tunnel-pending test-address-pending test-carp-pending test-v6-confirm test-routing-pending test-routing-reload test-wg-apply test-netd-batch test-netd-delete test-order-repair test-wg-drift test-tunnel-drift test-v6-drift test-iface-stats test-bridge-stats test-dns test-dnsd test-dns-pending test-dhcp-server test-dhcpd-daemon test-dhcpd-apply test-routing test-routing-status test-routing-capture test-diag-view test-pf-states test-pf-table test-pf-bruteforce test-log-lines test-pflog test-hasync test-secrets test-authd-secrets test-authd-frontdoor test-authd-jobs check-authd-parent-pledge test-authd-migrate test-authd-routing-binding test-authd-ipsec-binding test-authd-route test-authd-wg test-authd-wg-apply test-authd-pppoe test-dhcp test-dhcp6leased test-rad test-ipv6-ops test-authd-dhcp-txn test-authd-dhcp-recover test-authd-routing-job test-authd-ipsec-job test-authd-ipsec-pki-job test-authd-archive-job test-authd-support-job test-diag test-health test-arp test-gateways test-gwmon-probe test-supervision test-alertd test-alert-df test-alertd-capture test-alertd-apply test-alertd-cfgdrift test-time test-timed-apply test-sysctl-canonical test-cert-canonical test-ipsec-canonical test-ipsec-render-bound test-ipsec-ikev1 test-yaml-roundtrip test-ipsec-gate test-ipsec-txn test-ipsec-pending test-ipsec-apply test-ipsec-pki test-authd-ipsec-pki test-ipsec-pf test-sys-identity test-sys-svc test-svc-expect test-sys-power test-power-pending test-gwmon-unveil test-os-release test-os-release-eol test-os-guard test-host-facts test-update-check test-e6-ctrl-server test-release-sign test-syspatch-cache test-image-marker test-updstage test-updstage-helper test-system-canonical test-fsck test-fsck-guard test-image-guard test-image-boot test-cfg-persist test-totp test-schema-version test-fuzz test-fuzz-summary test-perl
