diff --git a/product-source/hololake-platform/architecture/HOLOLAKE-JD-CROSS-ROOT-GUANGHU-SUPERVISOR-20260815.md b/product-source/hololake-platform/architecture/HOLOLAKE-JD-CROSS-ROOT-GUANGHU-SUPERVISOR-20260815.md index b09e8e045..9b111f3a7 100644 --- a/product-source/hololake-platform/architecture/HOLOLAKE-JD-CROSS-ROOT-GUANGHU-SUPERVISOR-20260815.md +++ b/product-source/hololake-platform/architecture/HOLOLAKE-JD-CROSS-ROOT-GUANGHU-SUPERVISOR-20260815.md @@ -14,3 +14,6 @@ 当前工程门按顺序关闭:挂根前绑定与失败封门、`/run` 启动周期交接、根内监督进程持续运行、 仓库桥按需唤醒与收回、真实节点启动回执。每一门只能凭当前目标的执行证据升为 100。 + +跨根持续监督已经在京东同核 QEMU 中完成。当前最小门是使用独立 Forgejo 数据副本证明仓库桥 +唤醒、固定主分支读回、失败收回与最终休眠;公网仓库进程不参与该实验。 diff --git a/product-source/hololake-platform/docs/adr/0178-guanghu-bounded-repository-bridge-lifecycle.md b/product-source/hololake-platform/docs/adr/0178-guanghu-bounded-repository-bridge-lifecycle.md new file mode 100644 index 000000000..3745a1a8c --- /dev/null +++ b/product-source/hololake-platform/docs/adr/0178-guanghu-bounded-repository-bridge-lifecycle.md @@ -0,0 +1,18 @@ +--- +type: ADR +id: "0178" +title: "Guanghu bounded repository bridge lifecycle" +status: accepted +date: 2026-08-16 +refines: "0177" +development_id: "DEV-20260815-001" +--- + +# 光湖有界仓库桥生命周期 + +根内光湖监督实体只接受登记的 `repository-main-readback` 能力和一次性请求编号。执行前必须核验 +当前机器、仍存活的根监督进程和仓库桥休眠状态;随后依次唤醒隔离仓库桥、读回固定 `main`、 +重复现场验证,并在成功或失败路径上收回到 `DORMANT`。任意阶段不得接受任意 shell 能力。 + +真实公网 Forgejo 与 PID 760 不参与隔离门。现实验证使用独立数据副本、独立回环端口和独立进程; +只有最终状态重新休眠且公网仓库不受影响时,才能把这一个隔离谓词记为 100。 diff --git a/product-source/hololake-platform/guanghu-os/README.md b/product-source/hololake-platform/guanghu-os/README.md index 3532c057f..6fec02916 100644 --- a/product-source/hololake-platform/guanghu-os/README.md +++ b/product-source/hololake-platform/guanghu-os/README.md @@ -127,6 +127,15 @@ prove physical boot or bounded wake/reclaim of the real repository bridge, so those predicates and final master control remain `0`. See ADR-0177 and `deployments/JD-FD-PRIMARY/CROSS-ROOT-SUPERVISOR-QEMU-RECEIPT-20260815.hldp`. +## Bounded repository bridge lifecycle + +`scripts/guanghu-repository-bridge-lifecycle.sh` requires a live root-supervisor +state and one allowlisted repository readback request. It wakes a registered +backend, verifies the pinned `main`, and reclaims the backend on both success +and failure. `scripts/forgejo-repository-shadow-backend.sh` provides a separate +loopback-only Forgejo data plane for physical-node isolation tests; it never +targets the public repository process. See ADR-0178. + ## Language-primary boot target `guanghu-language-primary.target` makes the accepted cognitive-control model diff --git a/product-source/hololake-platform/guanghu-os/scripts/forgejo-repository-shadow-backend.sh b/product-source/hololake-platform/guanghu-os/scripts/forgejo-repository-shadow-backend.sh new file mode 100755 index 000000000..162a67739 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/scripts/forgejo-repository-shadow-backend.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +umask 077 + +die() { + echo "GUANGHU_FORGEJO_SHADOW_FAIL_0: $*" >&2 + exit 1 +} + +test_root=${GH_FORGEJO_SHADOW_TEST_ROOT:-} +if [[ -n ${test_root} ]]; then + test_root=$(readlink -f "${test_root}") + config_path=${GH_FORGEJO_SHADOW_CONFIG:?test config is required} + dmi_path=${GH_FORGEJO_SHADOW_DMI_PATH:?test DMI path is required} +else + [[ ${EUID} == 0 ]] || die "root execution is required" + [[ -z ${GH_FORGEJO_SHADOW_CONFIG:-}${GH_FORGEJO_SHADOW_DMI_PATH:-} ]] || + die "production path override is forbidden" + config_path=/etc/guanghu/forgejo-repository-shadow.conf + dmi_path=/sys/class/dmi/id/product_uuid +fi + +[[ -f ${config_path} && ! -L ${config_path} ]] || die "configuration is missing or unsafe" +schema= +node_id= +instance_id= +backend_id= +forgejo_binary= +work_path= +app_ini= +http_port= +repository_owner= +repository_name= +expected_main= +run_user= +while IFS='=' read -r key value; do + case "${key}" in + schema|node_id|instance_id|backend_id|forgejo_binary|work_path|app_ini|http_port|repository_owner|repository_name|expected_main|run_user) + printf -v "${key}" '%s' "${value}" ;; + ''|'#'*) ;; + *) die "unknown configuration field: ${key}" ;; + esac +done <"${config_path}" + +[[ ${schema} == guanghu.forgejo-repository-shadow/v1 ]] || die "configuration schema mismatch" +[[ ${node_id} == JD-FD-PRIMARY ]] || die "node identity mismatch" +[[ $(tr '[:upper:]' '[:lower:]' <"${dmi_path}" | tr -d '\r\n') == "${instance_id}" ]] || die "DMI identity mismatch" +[[ ${backend_id} == jd-repository-shadow ]] || die "backend binding mismatch" +[[ ${2:-} == "${backend_id}" ]] || die "requested backend mismatch" +[[ ${3:-} == repository-main-readback ]] || die "capability is not allowlisted" +[[ ${4:-} =~ ^REQ-[A-Za-z0-9._-]+$ ]] || die "request id is malformed" +[[ -x ${forgejo_binary} && -f ${app_ini} ]] || die "Forgejo shadow artifact is unavailable" +[[ ${http_port} =~ ^[0-9]{4,5}$ ]] || die "HTTP port is malformed" +[[ ${repository_owner} =~ ^[a-z0-9-]+$ && ${repository_name} =~ ^[a-z0-9-]+$ ]] || die "repository binding is malformed" +[[ ${expected_main} =~ ^[0-9a-f]{40}$ ]] || die "expected main is malformed" +[[ ${run_user} == guanghu ]] || die "Forgejo shadow user binding mismatch" +[[ -x /usr/sbin/start-stop-daemon ]] || die "process launcher is unavailable" +if [[ -n ${test_root} ]]; then + for path in "${config_path}" "${work_path}" "${app_ini}"; do + resolved=$(readlink -m "${path}") + [[ ${resolved} == "${test_root}" || ${resolved} == "${test_root}"/* ]] || die "test path escapes isolated root" + done +fi + +pid_file=${work_path}/forgejo-shadow.pid +readback_file=${work_path}/${4}.main +url=http://127.0.0.1:${http_port}/code/${repository_owner}/${repository_name}.git + +process_alive() { + [[ -s ${pid_file} ]] || return 1 + pid=$(cat "${pid_file}") + [[ ${pid} =~ ^[0-9]+$ ]] && kill -0 "${pid}" 2>/dev/null +} + +observe() { + if process_alive && curl -fsS --max-time 2 "http://127.0.0.1:${http_port}/code/api/v1/version" >/dev/null; then + echo ready + else + echo dormant + fi +} + +case "${1:-}" in + preflight) + [[ -d ${work_path}/repositories/${repository_owner}/${repository_name}.git ]] || die "shadow repository is unavailable" + [[ $(git --git-dir="${work_path}/repositories/${repository_owner}/${repository_name}.git" rev-parse refs/heads/main) == "${expected_main}" ]] || + die "shadow repository main mismatch" + [[ $(observe) == dormant ]] || die "shadow is not dormant" + ;; + observe) observe ;; + wake) + [[ $(observe) == dormant ]] || die "shadow is not dormant before wake" + /usr/sbin/start-stop-daemon --start --background --make-pidfile --pidfile "${pid_file}" \ + --chuid "${run_user}" --startas "${forgejo_binary}" -- \ + web --work-path "${work_path}" --config "${app_ini}" + for _ in {1..100}; do [[ $(observe) == ready ]] && break; sleep 0.1; done + [[ $(observe) == ready ]] || die "Forgejo shadow readiness failed" + ;; + execute) + [[ $(observe) == ready ]] || die "Forgejo shadow is not ready" + git ls-remote "${url}" refs/heads/main | awk '{print $1}' >"${readback_file}" + ;; + verify) + [[ $(cat "${readback_file}") == "${expected_main}" ]] || die "repository main readback mismatch" + [[ $(git ls-remote "${url}" refs/heads/main | awk '{print $1}') == "${expected_main}" ]] || die "live repository main mismatch" + ;; + reclaim) + if process_alive; then + pid=$(cat "${pid_file}") + kill "${pid}" 2>/dev/null || true + for _ in {1..100}; do kill -0 "${pid}" 2>/dev/null || break; sleep 0.05; done + kill -KILL "${pid}" 2>/dev/null || true + fi + rm -f "${pid_file}" + [[ $(observe) == dormant ]] || die "Forgejo shadow reclaim failed" + ;; + *) die "usage: $0 " ;; +esac diff --git a/product-source/hololake-platform/guanghu-os/scripts/guanghu-repository-bridge-lifecycle.sh b/product-source/hololake-platform/guanghu-os/scripts/guanghu-repository-bridge-lifecycle.sh new file mode 100755 index 000000000..1a4fa2357 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/scripts/guanghu-repository-bridge-lifecycle.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +umask 077 + +die() { + echo "GUANGHU_REPOSITORY_BRIDGE_LIFECYCLE_FAIL_0: $*" >&2 + exit 1 +} + +test_root=${GH_REPOSITORY_LIFECYCLE_TEST_ROOT:-} +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} + root_state=${GH_REPOSITORY_LIFECYCLE_ROOT_STATE:?test root state is required} + 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" + 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 + 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 ]] || + die "configuration mode must be 0600" + +schema= +node_id= +instance_id= +backend_id= +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}" ;; + ''|'#'*) ;; + *) 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}" ]] || + die "DMI identity mismatch" +[[ -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" +done +supervisor_pid=$(sed -n 's/.*"pid":\([0-9][0-9]*\).*/\1/p' <<<"${state}") +[[ -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" + +request_id=${2:-} +[[ ${1:-} == run && ${request_id} =~ ^REQ-[A-Za-z0-9._-]+$ ]] || + die "usage: $0 run REQ-" + +reclaim_needed=0 +cleanup() { + 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 ]] || + 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 ]] || + 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 ]] || + die "repository bridge reclaim readback failed" + +mkdir -p "${receipt_root}" +temporary=${receipt_root}/${request_id}.json.tmp.$$ +printf '%s\n' "{\"schema\":\"guanghu.repository-bridge-lifecycle-receipt/v1\",\"node_id\":\"${node_id}\",\"boot_id\":\"${boot_id}\",\"request_id\":\"${request_id}\",\"capability\":\"${capability}\",\"control\":\"GUANGHU_OS\",\"result\":\"PASS_100\",\"initial_state\":\"DORMANT\",\"active_state\":\"READY\",\"final_state\":\"DORMANT\"}" >"${temporary}" +chmod 0600 "${temporary}" +mv "${temporary}" "${receipt_root}/${request_id}.json" +trap - EXIT +echo "GUANGHU_REPOSITORY_BRIDGE_LIFECYCLE_PASS_100 request=${request_id} final=DORMANT" diff --git a/product-source/hololake-platform/guanghu-os/scripts/test-guanghu-repository-bridge-lifecycle.sh b/product-source/hololake-platform/guanghu-os/scripts/test-guanghu-repository-bridge-lifecycle.sh new file mode 100755 index 000000000..f0cb8651f --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/scripts/test-guanghu-repository-bridge-lifecycle.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +controller=${source_root}/scripts/guanghu-repository-bridge-lifecycle.sh +fixture=$(mktemp -d) +trap 'rm -rf "${fixture}"' EXIT +mkdir -p "${fixture}/state" "${fixture}/receipts" +printf '%s\n' f3d4b730-7f02-452f-975b-7091a4800431 >"${fixture}/dmi" +printf '%s\n' "{\"schema\":\"guanghu.root-supervisor-state/v1\",\"node_id\":\"JD-FD-PRIMARY\",\"boot_id\":\"11111111-2222-4333-8444-555555555555\",\"pid\":$$,\"control\":\"GUANGHU_OS\",\"stage\":\"ROOT_SUPERVISOR_ACTIVE\"}" >"${fixture}/root-state.json" +chmod 0600 "${fixture}/root-state.json" + +cat >"${fixture}/backend" <<'EOF' +#!/usr/bin/env bash +set -Eeuo pipefail +action=${1:-} +case "${action}" in + preflight) ;; + observe) [[ -f ${FAKE_READY} ]] && echo ready || echo dormant ;; + wake) touch "${FAKE_READY}" ;; + execute) [[ -f ${FAKE_READY} ]]; printf '%s\n' 'd2686f8cb3bde2426f79d13ad6581f7ea6ed8385' >"${FAKE_READBACK}" ;; + verify) [[ $(cat "${FAKE_READBACK}") == d2686f8cb3bde2426f79d13ad6581f7ea6ed8385 ]]; [[ ${FAKE_FAIL_VERIFY:-0} == 0 ]] ;; + reclaim) rm -f "${FAKE_READY}" ;; + *) exit 64 ;; +esac +EOF +chmod 0755 "${fixture}/backend" + +cat >"${fixture}/controller.conf" <&2 + exit 1 +fi +[[ ! -e ${fixture}/ready ]] + +echo GUANGHU_REPOSITORY_BRIDGE_LIFECYCLE_OK diff --git a/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh b/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh index 4a0bdf3b7..e229c0d25 100755 --- a/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh +++ b/product-source/hololake-platform/guanghu-os/world-seed/scripts/run-guanghu-native-quality-gate.sh @@ -98,6 +98,8 @@ run_gate guanghu_first_boot_supervisor \ "${source_root}/scripts/test-guanghu-first-boot-supervisor.sh" 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 auditable_line_coverage_100_percent \ bash -c ' cargo llvm-cov clean --workspace --manifest-path "$1/Cargo.toml" diff --git a/routing/guanghu-os-control-architecture.json b/routing/guanghu-os-control-architecture.json index 87346f41e..53caeff52 100644 --- a/routing/guanghu-os-control-architecture.json +++ b/routing/guanghu-os-control-architecture.json @@ -1,7 +1,7 @@ { "schema": "guanghu.os-control-architecture/v1", "record_id": "HLP-GUANGHU-OS-CONTROL-001", - "version": "2026-08-15.13", + "version": "2026-08-16.1", "state": "CURRENT_CANONICAL", "final_topology": "GUANGHU_MASTER_WITH_ON_DEMAND_LINUX_SUBCONTROL_AND_RESCUE", "control_ownership": { @@ -39,7 +39,7 @@ "linux_on_demand_subcontrol": 0, "linux_rescue_preserved": 100, "final_guanghu_os_master": 0, - "next_engineering_gate": "IMPLEMENT_AND_VERIFY_BOUNDED_ROOT_SUPERVISOR_TO_LINUX_REPOSITORY_BRIDGE_LIFECYCLE_IN_ISOLATION" + "next_engineering_gate": "BUILD_AND_VERIFY_REAL_FORGEJO_REPOSITORY_SHADOW_LIFECYCLE_ON_JD_WITHOUT_TOUCHING_PUBLIC_REPOSITORY" }, "implementation": { "guanghu_supervisor_lifecycle_contract_source": 100, @@ -78,6 +78,10 @@ "guanghu_cross_root_fixture_builder_source": 100, "jd_host_qemu_cross_switch_root_supervisor": 100, "cross_switch_root_persistent_supervisor": 100, + "repository_bridge_lifecycle_controller_source": 100, + "forgejo_repository_shadow_backend_source": 100, + "repository_bridge_failure_reclaim_contract_test": 100, + "jd_real_forgejo_repository_shadow_lifecycle": 0, "independent_guanghu_first_boot_supervisor": 0, "jd_physical_deployment": 0 }, diff --git a/routing/guanghu-os-control-architecture.test.mjs b/routing/guanghu-os-control-architecture.test.mjs index ab20eef55..11bfebe5b 100644 --- a/routing/guanghu-os-control-architecture.test.mjs +++ b/routing/guanghu-os-control-architecture.test.mjs @@ -43,12 +43,12 @@ test("current JD state stays transitional and cannot impersonate final master co assert.equal(contract.jd_fd_primary.final_guanghu_os_master, 0); assert.equal( contract.jd_fd_primary.next_engineering_gate, - "IMPLEMENT_AND_VERIFY_BOUNDED_ROOT_SUPERVISOR_TO_LINUX_REPOSITORY_BRIDGE_LIFECYCLE_IN_ISOLATION", + "BUILD_AND_VERIFY_REAL_FORGEJO_REPOSITORY_SHADOW_LIFECYCLE_ON_JD_WITHOUT_TOUCHING_PUBLIC_REPOSITORY", ); }); test("the physical shadow and host QEMU cycles are registered without impersonating Guanghu-owned boot control", () => { - assert.equal(contract.version, "2026-08-15.13"); + assert.equal(contract.version, "2026-08-16.1"); assert.equal(contract.implementation.guanghu_supervisor_lifecycle_contract_source, 100); assert.equal(contract.implementation.target_readback_and_mandatory_reclaim_state_machine, 100); assert.equal(contract.implementation.declared_supervisor_core_line_and_function_coverage, 100); @@ -84,6 +84,10 @@ test("the physical shadow and host QEMU cycles are registered without impersonat assert.equal(contract.implementation.guanghu_cross_root_fixture_builder_source, 100); assert.equal(contract.implementation.jd_host_qemu_cross_switch_root_supervisor, 100); assert.equal(contract.implementation.cross_switch_root_persistent_supervisor, 100); + assert.equal(contract.implementation.repository_bridge_lifecycle_controller_source, 100); + 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, 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");