feat(guanghu-os): add cross-root repository service gate
This commit is contained in:
parent
06a465ef73
commit
0759625053
9 changed files with 203 additions and 32 deletions
|
|
@ -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。
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue