feat(guanghu-os): add cross-root repository service gate

This commit is contained in:
冰朔 2026-08-16 00:39:41 +08:00
commit 0759625053
9 changed files with 203 additions and 32 deletions

View file

@ -0,0 +1,20 @@
---
type: ADR
id: "0182"
title: "Cross-root repository service equivalence before physical boot"
status: accepted
date: 2026-08-16
refines: "0178"
development_id: "DEV-20260815-001"
---
# 跨根仓库服务等价门
真实 Forgejo 隔离副本已经证明 Linux 仓库执行面可被有界唤醒和收回,但那次根监督器仍是
Linux 宿主上的隔离进程。物理启动前还必须证明同一控制器能在 `switch_root` 后由根内光湖
监督进程调用一个真实监听的仓库服务,完成固定 `main` 读回,再让服务恢复无监听状态。
QEMU 服务等价后端只模拟这一个已登记能力,不冒充完整 Forgejo。它用 BusyBox HTTP 服务
提供固定提交读回,由同一生命周期控制器执行 `DORMANT -> READY -> verify -> DORMANT`
这条证据要与京东真实 Forgejo 隔离生命周期共同成立,才能说明“控制关系”和“真实仓库实现”
两侧都已验证;任一侧都不能单独推出物理光湖启动或完整 Linux 按需副控为 100。

View file

@ -141,6 +141,13 @@ repository lifecycle predicate to `100`; physical Guanghu boot, full Linux
on-demand subcontrol, and final master control remain `0`. See ADR-0178 and
`deployments/JD-FD-PRIMARY/REPOSITORY-BRIDGE-LIFECYCLE-RECEIPT-20260816.hldp`.
The cross-root fixture can additionally carry the same portable lifecycle
controller and a loopback repository-service-equivalence backend. After the
root supervisor becomes resident, it must wake the service, read the pinned
`main`, verify the receipt, reclaim the listener, and only then power off the
fixture. This QEMU gate complements rather than replaces the real Forgejo
shadow proof. See ADR-0182.
## Language-primary boot target
`guanghu-language-primary.target` makes the accepted cognitive-control model

View file

@ -1,14 +1,26 @@
#!/usr/bin/env bash
set -Eeuo pipefail
[[ $# == 3 ]] || {
echo 'usage: build-guanghu-cross-root-fixture.sh <output-image> <config> <root-supervisor>' >&2
[[ $# == 3 || $# == 7 ]] || {
echo 'usage: build-guanghu-cross-root-fixture.sh <output-image> <config> <root-supervisor> [<repository-lifecycle> <lifecycle-config> <repository-backend> <expected-main>]' >&2
exit 64
}
output=$(readlink -m "$1")
config=$(readlink -f "$2")
supervisor=$(readlink -f "$3")
lifecycle=
lifecycle_config=
repository_backend=
expected_main=
if [[ $# == 7 ]]; then
lifecycle=$(readlink -f "$4")
lifecycle_config=$(readlink -f "$5")
repository_backend=$(readlink -f "$6")
expected_main=$7
[[ -x ${lifecycle} && -x ${repository_backend} && -f ${lifecycle_config} ]]
[[ ${expected_main} =~ ^[0-9a-f]{40}$ ]]
fi
root_uuid=$(sed -n 's/^root_uuid=//p' "${config}")
[[ ${root_uuid} =~ ^[0-9a-f-]{36}$ ]]
[[ -x /bin/busybox && -x ${supervisor} ]]
@ -19,13 +31,21 @@ trap 'rm -rf "${staging}"' EXIT
mkdir -p "${staging}/bin" "${staging}/sbin" "${staging}/usr/lib/guanghu" \
"${staging}/etc/guanghu" "${staging}/run" "${staging}/dev" \
"${staging}/proc" "${staging}/sys" "${staging}/lib/modules" \
"${staging}/guanghu/receipts/repository-bridge-lifecycle" \
"${staging}/usr/share/guanghu/repository-service-equivalence" \
"$(dirname "${output}")"
install -m 0755 /bin/busybox "${staging}/bin/busybox"
for applet in sh sleep cat grep tr stat mkdir mv chmod readlink kill sync poweroff; do
for applet in sh sleep cat grep tr stat mkdir mv chmod readlink kill sync poweroff sed id httpd wget rm; do
ln -s busybox "${staging}/bin/${applet}"
done
install -m 0755 "${supervisor}" "${staging}/usr/lib/guanghu/guanghu-root-supervisor"
install -m 0600 "${config}" "${staging}/etc/guanghu/first-boot-supervisor.conf"
if [[ -n ${lifecycle} ]]; then
install -m 0755 "${lifecycle}" "${staging}/usr/lib/guanghu/guanghu-repository-bridge-lifecycle"
install -m 0755 "${repository_backend}" "${staging}/usr/lib/guanghu/qemu-repository-service-equivalence-backend"
install -m 0600 "${lifecycle_config}" "${staging}/etc/guanghu/repository-bridge-lifecycle.conf"
printf '%s\n' "${expected_main}" >"${staging}/usr/share/guanghu/repository-service-equivalence/main"
fi
printf '%s\n' 'guanghu.cross-root-fixture/v1' >"${staging}/etc/guanghu/rootfs.marker"
cat >"${staging}/sbin/init" <<'EOF'
@ -43,6 +63,17 @@ done
kill -0 "${supervisor_pid}"
grep -Fq '"stage":"ROOT_SUPERVISOR_ACTIVE"' /run/guanghu/root-supervisor/state.json
echo "GUANGHU_CROSS_ROOT_PERSISTENCE_VERIFIED pid=${supervisor_pid}"
if [ -x /usr/lib/guanghu/guanghu-repository-bridge-lifecycle ]; then
/usr/lib/guanghu/guanghu-repository-bridge-lifecycle run REQ-QEMU-CROSS-ROOT-001
grep -Fq '"result":"PASS_100"' /guanghu/receipts/repository-bridge-lifecycle/REQ-QEMU-CROSS-ROOT-001.json
grep -Fq '"final_state":"DORMANT"' /guanghu/receipts/repository-bridge-lifecycle/REQ-QEMU-CROSS-ROOT-001.json
if wget -q -T 1 -O /dev/null http://127.0.0.1:39301/main; then
echo 'GUANGHU_CROSS_ROOT_REPOSITORY_SERVICE_FAIL_0: repository service remained ready after reclaim' >&2
poweroff -f
while :; do sleep 30; done
fi
echo 'GUANGHU_CROSS_ROOT_REPOSITORY_SERVICE_EQUIVALENCE_VERIFIED request=REQ-QEMU-CROSS-ROOT-001 final=DORMANT'
fi
kill "${supervisor_pid}"
wait "${supervisor_pid}"
sync

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash
set -Eeuo pipefail
#!/bin/sh
set -eu
umask 077
die() {
@ -8,7 +8,7 @@ die() {
}
test_root=${GH_REPOSITORY_LIFECYCLE_TEST_ROOT:-}
if [[ -n ${test_root} ]]; then
if [ -n "${test_root}" ]; then
test_root=$(readlink -f "${test_root}")
config_path=${GH_REPOSITORY_LIFECYCLE_CONFIG:?test config is required}
dmi_path=${GH_REPOSITORY_LIFECYCLE_DMI_PATH:?test DMI path is required}
@ -16,22 +16,20 @@ if [[ -n ${test_root} ]]; then
receipt_root=${GH_REPOSITORY_LIFECYCLE_RECEIPT_ROOT:?test receipt root is required}
for path in "${config_path}" "${dmi_path}" "${root_state}" "${receipt_root}"; do
resolved=$(readlink -f "${path}")
[[ ${resolved} == "${test_root}" || ${resolved} == "${test_root}"/* ]] ||
die "test path escapes isolated root"
case "${resolved}" in "${test_root}"|"${test_root}"/*) ;; *) die "test path escapes isolated root" ;; esac
done
else
[[ ${EUID} == 0 ]] || die "root execution is required"
for name in GH_REPOSITORY_LIFECYCLE_CONFIG GH_REPOSITORY_LIFECYCLE_DMI_PATH GH_REPOSITORY_LIFECYCLE_ROOT_STATE GH_REPOSITORY_LIFECYCLE_RECEIPT_ROOT; do
[[ -z ${!name:-} ]] || die "production path override is forbidden: ${name}"
done
[ "$(id -u)" = 0 ] || die "root execution is required"
[ -z "${GH_REPOSITORY_LIFECYCLE_CONFIG:-}${GH_REPOSITORY_LIFECYCLE_DMI_PATH:-}${GH_REPOSITORY_LIFECYCLE_ROOT_STATE:-}${GH_REPOSITORY_LIFECYCLE_RECEIPT_ROOT:-}" ] ||
die "production path override is forbidden"
config_path=/etc/guanghu/repository-bridge-lifecycle.conf
dmi_path=/sys/class/dmi/id/product_uuid
root_state=/run/guanghu/root-supervisor/state.json
receipt_root=/guanghu/receipts/repository-bridge-lifecycle
fi
[[ -f ${config_path} && ! -L ${config_path} ]] || die "configuration is missing or unsafe"
[[ $(stat -c '%a' "${config_path}" 2>/dev/null || stat -f '%Lp' "${config_path}") == 600 ]] ||
[ -f "${config_path}" ] && [ ! -L "${config_path}" ] || die "configuration is missing or unsafe"
[ "$(stat -c '%a' "${config_path}" 2>/dev/null || stat -f '%Lp' "${config_path}")" = 600 ] ||
die "configuration mode must be 0600"
schema=
@ -42,58 +40,63 @@ backend_command=
capability=
while IFS='=' read -r key value; do
case "${key}" in
schema|node_id|instance_id|backend_id|backend_command|capability) printf -v "${key}" '%s' "${value}" ;;
schema) schema=${value} ;;
node_id) node_id=${value} ;;
instance_id) instance_id=${value} ;;
backend_id) backend_id=${value} ;;
backend_command) backend_command=${value} ;;
capability) capability=${value} ;;
''|'#'*) ;;
*) die "unknown configuration field: ${key}" ;;
esac
done <"${config_path}"
[[ ${schema} == guanghu.repository-bridge-lifecycle/v1 ]] || die "configuration schema mismatch"
[[ ${node_id} == JD-FD-PRIMARY ]] || die "node identity mismatch"
[[ ${backend_id} =~ ^[a-z0-9-]+$ ]] || die "backend id is malformed"
[[ ${capability} == repository-main-readback ]] || die "capability is not allowlisted"
[[ -x ${backend_command} ]] || die "backend command is unavailable"
[[ $(tr '[:upper:]' '[:lower:]' <"${dmi_path}" | tr -d '\r\n') == "${instance_id}" ]] ||
[ "${schema}" = guanghu.repository-bridge-lifecycle/v1 ] || die "configuration schema mismatch"
[ "${node_id}" = JD-FD-PRIMARY ] || die "node identity mismatch"
printf '%s\n' "${backend_id}" | grep -Eq '^[a-z0-9-]+$' || die "backend id is malformed"
[ "${capability}" = repository-main-readback ] || die "capability is not allowlisted"
[ -x "${backend_command}" ] || die "backend command is unavailable"
[ "$(tr '[:upper:]' '[:lower:]' <"${dmi_path}" | tr -d '\r\n')" = "${instance_id}" ] ||
die "DMI identity mismatch"
[[ -f ${root_state} && ! -L ${root_state} ]] || die "root supervisor state is unavailable"
[ -f "${root_state}" ] && [ ! -L "${root_state}" ] || die "root supervisor state is unavailable"
state=$(cat "${root_state}")
for binding in \
'"schema":"guanghu.root-supervisor-state/v1"' \
'"node_id":"JD-FD-PRIMARY"' \
'"control":"GUANGHU_OS"' \
'"stage":"ROOT_SUPERVISOR_ACTIVE"'; do
grep -Fq "${binding}" <<<"${state}" || die "root supervisor binding mismatch"
printf '%s' "${state}" | grep -Fq "${binding}" || die "root supervisor binding mismatch"
done
supervisor_pid=$(sed -n 's/.*"pid":\([0-9][0-9]*\).*/\1/p' <<<"${state}")
[[ -n ${supervisor_pid} ]] && kill -0 "${supervisor_pid}" 2>/dev/null ||
supervisor_pid=$(printf '%s' "${state}" | sed -n 's/.*"pid":\([0-9][0-9]*\).*/\1/p')
[ -n "${supervisor_pid}" ] && kill -0 "${supervisor_pid}" 2>/dev/null ||
die "root supervisor process is not alive"
boot_id=$(sed -n 's/.*"boot_id":"\([0-9a-f-]*\)".*/\1/p' <<<"${state}")
[[ ${boot_id} =~ ^[0-9a-f-]{36}$ ]] || die "root supervisor boot id is malformed"
boot_id=$(printf '%s' "${state}" | sed -n 's/.*"boot_id":"\([0-9a-f-]*\)".*/\1/p')
printf '%s\n' "${boot_id}" | grep -Eq '^[0-9a-f-]{36}$' || die "root supervisor boot id is malformed"
request_id=${2:-}
[[ ${1:-} == run && ${request_id} =~ ^REQ-[A-Za-z0-9._-]+$ ]] ||
[ "${1:-}" = run ] && printf '%s\n' "${request_id}" | grep -Eq '^REQ-[A-Za-z0-9._-]+$' ||
die "usage: $0 run REQ-<id>"
reclaim_needed=0
cleanup() {
if [[ ${reclaim_needed} == 1 ]]; then
if [ "${reclaim_needed}" = 1 ]; then
"${backend_command}" reclaim "${backend_id}" "${capability}" "${request_id}" >/dev/null || true
fi
}
trap cleanup EXIT
"${backend_command}" preflight "${backend_id}" "${capability}" "${request_id}" >/dev/null
[[ $("${backend_command}" observe "${backend_id}" "${capability}" "${request_id}") == dormant ]] ||
[ "$("${backend_command}" observe "${backend_id}" "${capability}" "${request_id}")" = dormant ] ||
die "repository bridge is not dormant before wake"
"${backend_command}" wake "${backend_id}" "${capability}" "${request_id}" >/dev/null
reclaim_needed=1
[[ $("${backend_command}" observe "${backend_id}" "${capability}" "${request_id}") == ready ]] ||
[ "$("${backend_command}" observe "${backend_id}" "${capability}" "${request_id}")" = ready ] ||
die "repository bridge readiness readback failed"
"${backend_command}" execute "${backend_id}" "${capability}" "${request_id}" >/dev/null
"${backend_command}" verify "${backend_id}" "${capability}" "${request_id}" >/dev/null
"${backend_command}" reclaim "${backend_id}" "${capability}" "${request_id}" >/dev/null
reclaim_needed=0
[[ $("${backend_command}" observe "${backend_id}" "${capability}" "${request_id}") == dormant ]] ||
[ "$("${backend_command}" observe "${backend_id}" "${capability}" "${request_id}")" = dormant ] ||
die "repository bridge reclaim readback failed"
mkdir -p "${receipt_root}"

View file

@ -0,0 +1,86 @@
#!/bin/sh
set -eu
die() {
echo "GUANGHU_QEMU_REPOSITORY_BACKEND_FAIL_0: $*" >&2
exit 1
}
[ "${2:-}" = jd-repository-shadow ] || die "backend binding mismatch"
[ "${3:-}" = repository-main-readback ] || die "capability is not allowlisted"
printf '%s\n' "${4:-}" | grep -Eq '^REQ-[A-Za-z0-9._-]+$' || die "request id is malformed"
test_root=${GH_QEMU_REPOSITORY_BACKEND_TEST_ROOT:-}
if [ -n "${test_root}" ]; then
test_root=$(readlink -f "${test_root}")
state_root=${test_root}/run
web_root=${test_root}/web
port=${GH_QEMU_REPOSITORY_BACKEND_TEST_PORT:?test port is required}
printf '%s\n' "${port}" | grep -Eq '^[0-9]{4,5}$' || die "test port is malformed"
else
[ -z "${GH_QEMU_REPOSITORY_BACKEND_TEST_PORT:-}" ] || die "production port override is forbidden"
state_root=/run/guanghu/repository-service-equivalence
web_root=/usr/share/guanghu/repository-service-equivalence
port=39301
fi
expected_file=${web_root}/main
pid_file=${state_root}/service.pid
readback_file=${state_root}/${4}.main
url=http://127.0.0.1:${port}/main
process_alive() {
[ -s "${pid_file}" ] || return 1
pid=$(cat "${pid_file}")
kill -0 "${pid}" 2>/dev/null
}
observe() {
if process_alive && wget -q -T 1 -O /dev/null "${url}"; then
echo ready
else
echo dormant
fi
}
case "${1:-}" in
preflight)
[ -s "${expected_file}" ] || die "expected repository main is unavailable"
[ "$(observe)" = dormant ] || die "service is not dormant"
;;
observe) observe ;;
wake)
[ "$(observe)" = dormant ] || die "service is not dormant before wake"
mkdir -p "${state_root}"
httpd -f -p 127.0.0.1:39301 -h "${web_root}" &
echo $! >"${pid_file}"
attempt=0
while [ "$(observe)" != ready ] && [ "${attempt}" -lt 100 ]; do
sleep 0.05
attempt=$((attempt + 1))
done
[ "$(observe)" = ready ] || die "service readiness failed"
;;
execute)
[ "$(observe)" = ready ] || die "service is not ready"
wget -q -T 2 -O "${readback_file}" "${url}"
;;
verify)
[ "$(cat "${readback_file}")" = "$(cat "${expected_file}")" ] || die "repository main readback mismatch"
[ "$(wget -q -T 2 -O - "${url}")" = "$(cat "${expected_file}")" ] || die "live repository main mismatch"
;;
reclaim)
if process_alive; then
pid=$(cat "${pid_file}")
kill "${pid}" 2>/dev/null || true
attempt=0
while kill -0 "${pid}" 2>/dev/null && [ "${attempt}" -lt 100 ]; do
sleep 0.02
attempt=$((attempt + 1))
done
kill -9 "${pid}" 2>/dev/null || true
fi
rm -f "${pid_file}"
[ "$(observe)" = dormant ] || die "service reclaim failed"
;;
*) die "usage: $0 <preflight|observe|wake|execute|verify|reclaim> <backend> <capability> <request>" ;;
esac

View file

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -Eeuo pipefail
source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
backend=${source_root}/scripts/qemu-repository-service-equivalence-backend.sh
builder=${source_root}/scripts/build-guanghu-cross-root-fixture.sh
controller=${source_root}/scripts/guanghu-repository-bridge-lifecycle.sh
sh -n "${backend}"
sh -n "${controller}"
grep -Fq 'httpd -f -p 127.0.0.1:39301' "${backend}"
grep -Fq 'repository-main-readback' "${backend}"
grep -Fq 'service reclaim failed' "${backend}"
grep -Fq 'GUANGHU_CROSS_ROOT_REPOSITORY_SERVICE_EQUIVALENCE_VERIFIED' "${builder}"
grep -Fq 'REQ-QEMU-CROSS-ROOT-001' "${builder}"
grep -Fq 'final_state":"DORMANT' "${builder}"
grep -Fq 'repository service remained ready after reclaim' "${builder}"
echo GUANGHU_CROSS_ROOT_REPOSITORY_SERVICE_EQUIVALENCE_CONTRACT_OK

View file

@ -100,6 +100,8 @@ run_gate guanghu_root_supervisor \
"${source_root}/scripts/test-guanghu-root-supervisor.sh"
run_gate guanghu_repository_bridge_lifecycle \
"${source_root}/scripts/test-guanghu-repository-bridge-lifecycle.sh"
run_gate cross_root_repository_service_equivalence \
"${source_root}/scripts/test-cross-root-repository-service-equivalence.sh"
run_gate auditable_line_coverage_100_percent \
bash -c '
cargo llvm-cov clean --workspace --manifest-path "$1/Cargo.toml"

View file

@ -82,6 +82,8 @@
"forgejo_repository_shadow_backend_source": 100,
"repository_bridge_failure_reclaim_contract_test": 100,
"jd_real_forgejo_repository_shadow_lifecycle": 100,
"cross_root_repository_service_equivalence_source": 100,
"jd_host_qemu_cross_root_repository_service_equivalence": 0,
"independent_guanghu_first_boot_supervisor": 0,
"jd_physical_deployment": 0
},

View file

@ -88,6 +88,8 @@ test("the physical shadow and host QEMU cycles are registered without impersonat
assert.equal(contract.implementation.forgejo_repository_shadow_backend_source, 100);
assert.equal(contract.implementation.repository_bridge_failure_reclaim_contract_test, 100);
assert.equal(contract.implementation.jd_real_forgejo_repository_shadow_lifecycle, 100);
assert.equal(contract.implementation.cross_root_repository_service_equivalence_source, 100);
assert.equal(contract.implementation.jd_host_qemu_cross_root_repository_service_equivalence, 0);
assert.equal(contract.implementation.independent_guanghu_first_boot_supervisor, 0);
assert.equal(contract.implementation.jd_physical_deployment, 0);
assert.equal(contract.current_target_evidence.final_state, "DORMANT");