diff --git a/product-source/hololake-platform/docs/adr/0181-independent-forgejo-continuity-before-native-cutover.md b/product-source/hololake-platform/docs/adr/0181-independent-forgejo-continuity-before-native-cutover.md new file mode 100644 index 0000000..eb64043 --- /dev/null +++ b/product-source/hololake-platform/docs/adr/0181-independent-forgejo-continuity-before-native-cutover.md @@ -0,0 +1,41 @@ +--- +type: ADR +id: "0181" +title: "Independent full Forgejo continuity before JD native cutover" +status: accepted +date: 2026-08-15 +depends_on: + - "0180" +--- + +# 京东原生切换前建立独立完整 Forgejo 连续性 + +## 为什么上一阶段仍没有完成 + +ADR-0180 解决了原生存活状态和固定只读锚点的认证投影,但当前公共 `/code/` 仍经 +BS-GZ-006 的 SSH 隧道进入 JD Linux。物理原生驻留已经证明这条隧道会消失并返回 502。 +只读代码摘要不能冒充 Forgejo 的仓库对象、账号、议题、发布、权限和 Git 协议。 + +## 现场纠正 + +BS-GZ-006 已有一套从五月运行的 Forgejo,但只含另一组 2 个仓库;它不是京东代码频道的 +7 个仓库。另一个同名 systemd 单元长期每五秒启动第二进程并抢同一 LevelDB 锁,累计约 +249950 次失败。该重复守卫已被单独禁用,正在运行的旧实例、数据和公网均被保留。 + +## 决定 + +在 BS-SG-003 建立京东完整 Forgejo 的独立影子副本。选择 BS-SG 而不是 BS-GZ,是因为 +BS-SG 在京东原生驻留时仍在线,并有足够内存和磁盘;BS-GZ 继续只承担公网前门和认证 +隧道。 + +影子验收必须同时匹配:Forgejo 二进制哈希、HTTP 版本、SQLite 完整性、数据库仓库数、 +用户数、精确仓库路径集合,以及每个仓库的 main 提交。任一不符即为 0。 + +初次在线快照只用于影子验证,不得成为切换权威。真正切 `/code/` 前必须开启有界写冻结、 +执行第二次精确同步、再次全量回读,并保留京东 Linux 原数据作为回滚。nginx、京东启动和 +生产原生切换均不由影子 HTTP 200 自动触发。 + +## 当前边界 + +验证器源码与隔离测试为 100。BS-SG 完整影子、广州到新加坡认证隧道、nginx 代码路由、 +原生驻留期间公共代码频道、完整 Forgejo 等价和生产原生切换仍为 0。 diff --git a/product-source/hololake-platform/guanghu-os/deployments/BS-GZ-006/DUPLICATE-FORGEJO-GUARD-REMOVAL-RECEIPT-20260815.hldp b/product-source/hololake-platform/guanghu-os/deployments/BS-GZ-006/DUPLICATE-FORGEJO-GUARD-REMOVAL-RECEIPT-20260815.hldp new file mode 100644 index 0000000..b4140b1 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/deployments/BS-GZ-006/DUPLICATE-FORGEJO-GUARD-REMOVAL-RECEIPT-20260815.hldp @@ -0,0 +1,18 @@ +schema: guanghu.repository-projected-server-receipt/v1 +receipt_id: GHOS-BS-GZ-006-DUPLICATE-FORGEJO-GUARD-REMOVAL-20260815-001 +observed_at: 2026-08-15T20:50:55+08:00 +node_id: BS-GZ-006 +instance_id: b2ecf109-1999-4643-b223-e67e24d7667d +trigger: user required removal of useless guards that repeatedly consumed runtime without progress +emergence: live Forgejo PID 2192816 already owned the data and port 3001; forgejo.service repeatedly launched a second process against the same LevelDB lock +lock: disable only the duplicate forgejo.service and preserve the live process, data, nginx, JD tunnels, and public routes +server_receipt: /var/lib/guanghu-os/receipts/BS-GZ-006-DUPLICATE-FORGEJO-GUARD-REMOVAL-RECEIPT.hldp +server_receipt_sha256: 186075da7629c289705e31d580df1f5a77c590a3214392a382459030207c8214 +prior_failed_restart_counter_observed: 249950 +duplicate_unit_enabled_after: false +duplicate_unit_active_after: false +preserved_live_forgejo_http: 200 +public_code_http_after: 200 +data_deleted: false +public_route_changed: false +boot_changed: false diff --git a/product-source/hololake-platform/guanghu-os/packaging/systemd/guanghu-independent-forgejo-shadow.service b/product-source/hololake-platform/guanghu-os/packaging/systemd/guanghu-independent-forgejo-shadow.service new file mode 100644 index 0000000..ca027fe --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/packaging/systemd/guanghu-independent-forgejo-shadow.service @@ -0,0 +1,29 @@ +[Unit] +Description=Independent full Forgejo shadow for JD native service continuity +After=network.target + +[Service] +Type=simple +User=guanghu-code-shadow +Group=guanghu-code-shadow +WorkingDirectory=/var/lib/guanghu-code-shadow +ExecStart=/var/lib/guanghu-code-shadow/bin/forgejo web --work-path /var/lib/guanghu-code-shadow --config /etc/guanghu-os/forgejo-shadow/app.ini +Restart=on-failure +RestartSec=3 +NoNewPrivileges=yes +PrivateTmp=yes +PrivateDevices=yes +ProtectSystem=strict +ProtectHome=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +ReadOnlyPaths=/etc/guanghu-os/forgejo-shadow +ReadWritePaths=/var/lib/guanghu-code-shadow +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictSUIDSGID=yes +LockPersonality=yes +UMask=0027 + +[Install] +WantedBy=multi-user.target diff --git a/product-source/hololake-platform/guanghu-os/scripts/test-guanghu-native-quality-gate-contract.sh b/product-source/hololake-platform/guanghu-os/scripts/test-guanghu-native-quality-gate-contract.sh index 7de6fd1..2f77f20 100755 --- a/product-source/hololake-platform/guanghu-os/scripts/test-guanghu-native-quality-gate-contract.sh +++ b/product-source/hololake-platform/guanghu-os/scripts/test-guanghu-native-quality-gate-contract.sh @@ -22,6 +22,7 @@ grep -Fq -- '--fail-under-lines 100' "${runner}" grep -Fq -- '--fail-under-functions 100' "${runner}" grep -Fq -- '--test broadcast_library' "${runner}" grep -Fq 'test-native-public-projection.sh' "${runner}" +grep -Fq 'test-independent-forgejo-shadow-verifier.py' "${runner}" grep -Fq 'GHNQG_PASS_100' "${runner}" grep -Fq 'GHNQG_FAIL_0' "${runner}" grep -Fq 'total_score: ${total_score}' "${runner}" diff --git a/product-source/hololake-platform/guanghu-os/scripts/test-independent-forgejo-shadow-verifier.py b/product-source/hololake-platform/guanghu-os/scripts/test-independent-forgejo-shadow-verifier.py new file mode 100755 index 0000000..87d8497 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/scripts/test-independent-forgejo-shadow-verifier.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import hashlib +import importlib.util +import json +import pathlib +import sqlite3 +import subprocess +import tempfile +import unittest + + +SCRIPT = pathlib.Path(__file__).with_name("verify-independent-forgejo-shadow.py") +SPEC = importlib.util.spec_from_file_location("forgejo_shadow_verifier", SCRIPT) +assert SPEC is not None and SPEC.loader is not None +MODULE = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(MODULE) + + +class IndependentForgejoShadowVerifierTest(unittest.TestCase): + def test_exact_shadow_passes_and_head_drift_fails(self) -> None: + with tempfile.TemporaryDirectory() as temporary: + root = pathlib.Path(temporary) + binary = root / "forgejo" + binary.write_bytes(b"fixed-forgejo-binary") + repository = root / "repositories" / "bingshuo" / "example.git" + repository.mkdir(parents=True) + subprocess.run(["git", "init", "--bare", "-q", str(repository)], check=True) + tree = subprocess.run( + ["git", f"--git-dir={repository}", "mktree"], + input="", + capture_output=True, + check=True, + text=True, + ).stdout.strip() + commit = subprocess.run( + ["git", f"--git-dir={repository}", "commit-tree", tree], + input="source\n", + capture_output=True, + check=True, + text=True, + env={ + "GIT_AUTHOR_NAME": "test", + "GIT_AUTHOR_EMAIL": "test@example.invalid", + "GIT_AUTHOR_DATE": "2001-01-01T00:00:00+00:00", + "GIT_COMMITTER_NAME": "test", + "GIT_COMMITTER_EMAIL": "test@example.invalid", + "GIT_COMMITTER_DATE": "2001-01-01T00:00:00+00:00", + }, + ).stdout.strip() + subprocess.run( + ["git", f"--git-dir={repository}", "update-ref", "refs/heads/main", commit], + check=True, + ) + database = root / "forgejo.db" + connection = sqlite3.connect(database) + connection.executescript( + "CREATE TABLE repository(id INTEGER); INSERT INTO repository VALUES(1);" + "CREATE TABLE user(id INTEGER); INSERT INTO user VALUES(1);" + ) + connection.close() + manifest = { + "schema": MODULE.SCHEMA, + "source_node": "JD-FD-PRIMARY", + "forgejo_version": "16.0.1", + "binary_sha256": hashlib.sha256(binary.read_bytes()).hexdigest(), + "database_repository_count": 1, + "database_user_count": 1, + "repositories": [{"path": "bingshuo/example.git", "head": commit}], + } + manifest_path = root / "manifest.json" + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + loaded = MODULE.load_manifest(manifest_path) + self.assertEqual(loaded["source_node"], "JD-FD-PRIMARY") + self.assertEqual( + MODULE.repository_heads(root / "repositories"), + {"bingshuo/example.git": commit}, + ) + self.assertEqual(MODULE.database_readback(database), ("ok", 1, 1)) + drift = subprocess.run( + ["git", f"--git-dir={repository}", "commit-tree", tree, "-p", commit], + input="drift\n", + capture_output=True, + check=True, + text=True, + env={ + "GIT_AUTHOR_NAME": "test", + "GIT_AUTHOR_EMAIL": "test@example.invalid", + "GIT_AUTHOR_DATE": "2001-01-02T00:00:00+00:00", + "GIT_COMMITTER_NAME": "test", + "GIT_COMMITTER_EMAIL": "test@example.invalid", + "GIT_COMMITTER_DATE": "2001-01-02T00:00:00+00:00", + }, + ).stdout.strip() + subprocess.run( + ["git", f"--git-dir={repository}", "update-ref", "refs/heads/main", drift], + check=True, + ) + self.assertNotEqual( + MODULE.repository_heads(root / "repositories"), + {"bingshuo/example.git": commit}, + ) + + def test_manifest_rejects_escape_and_duplicates(self) -> None: + with tempfile.TemporaryDirectory() as temporary: + path = pathlib.Path(temporary) / "manifest.json" + path.write_text( + json.dumps( + { + "schema": MODULE.SCHEMA, + "repositories": [ + {"path": "../escape.git", "head": "1" * 40}, + {"path": "../escape.git", "head": "1" * 40}, + ], + } + ), + encoding="utf-8", + ) + with self.assertRaises(ValueError): + MODULE.load_manifest(path) + + +if __name__ == "__main__": + unittest.main() diff --git a/product-source/hololake-platform/guanghu-os/scripts/verify-independent-forgejo-shadow.py b/product-source/hololake-platform/guanghu-os/scripts/verify-independent-forgejo-shadow.py new file mode 100755 index 0000000..da6eba3 --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/scripts/verify-independent-forgejo-shadow.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Verify one independent Forgejo shadow against a source-side manifest.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import pathlib +import sqlite3 +import subprocess +import urllib.request +from typing import Any + + +SCHEMA = "guanghu.independent-forgejo-shadow-manifest/v1" + + +def sha256(path: pathlib.Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def load_manifest(path: pathlib.Path) -> dict[str, Any]: + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict) or value.get("schema") != SCHEMA: + raise ValueError("source manifest schema mismatch") + repositories = value.get("repositories") + if not isinstance(repositories, list) or not repositories: + raise ValueError("source manifest must contain repositories") + paths: set[str] = set() + for repository in repositories: + if not isinstance(repository, dict): + raise ValueError("repository manifest entry must be an object") + path_value = repository.get("path") + head = repository.get("head") + if ( + not isinstance(path_value, str) + or path_value.startswith("/") + or ".." in pathlib.PurePosixPath(path_value).parts + or not path_value.endswith(".git") + or path_value in paths + ): + raise ValueError("repository path is unsafe or duplicated") + if not isinstance(head, str) or len(head) != 40: + raise ValueError("repository head must be a full commit") + paths.add(path_value) + return value + + +def repository_heads(root: pathlib.Path) -> dict[str, str]: + result: dict[str, str] = {} + for repository in sorted(root.glob("*/*.git")): + if repository.is_symlink() or not repository.is_dir(): + raise ValueError("repository must be a real directory") + completed = subprocess.run( + ["git", f"--git-dir={repository}", "rev-parse", "refs/heads/main"], + check=True, + capture_output=True, + text=True, + timeout=10, + ) + result[repository.relative_to(root).as_posix()] = completed.stdout.strip() + return result + + +def database_readback(path: pathlib.Path) -> tuple[str, int, int]: + connection = sqlite3.connect(f"file:{path}?mode=ro", uri=True) + try: + integrity = str(connection.execute("PRAGMA integrity_check").fetchone()[0]) + repositories = int(connection.execute("SELECT count(*) FROM repository").fetchone()[0]) + users = int(connection.execute("SELECT count(*) FROM user").fetchone()[0]) + return integrity, repositories, users + finally: + connection.close() + + +def http_version(url: str) -> str: + with urllib.request.urlopen(url, timeout=5) as response: # noqa: S310 + if response.status != 200: + raise ValueError("Forgejo version endpoint did not return 200") + value = json.load(response) + if not isinstance(value, dict) or not isinstance(value.get("version"), str): + raise ValueError("Forgejo version response is malformed") + return value["version"] + + +def verify(args: argparse.Namespace) -> dict[str, object]: + manifest = load_manifest(pathlib.Path(args.source_manifest)) + binary = pathlib.Path(args.binary) + repository_root = pathlib.Path(args.repository_root) + database = pathlib.Path(args.database) + for path in (binary, repository_root, database): + if path.is_symlink(): + raise ValueError("shadow inputs cannot be symlinks") + expected_heads = { + str(item["path"]): str(item["head"]) for item in manifest["repositories"] + } + integrity, repository_count, user_count = database_readback(database) + checks = { + "binary_sha256": sha256(binary) == manifest.get("binary_sha256"), + "repository_heads": repository_heads(repository_root) == expected_heads, + "database_integrity": integrity == "ok", + "database_repository_count": repository_count + == manifest.get("database_repository_count"), + "database_user_count": user_count == manifest.get("database_user_count"), + "http_version": http_version(args.version_url) == manifest.get("forgejo_version"), + } + if not all(checks.values()): + failed = ",".join(key for key, passed in checks.items() if not passed) + raise ValueError(f"independent Forgejo shadow mismatch: {failed}") + return { + "schema": "guanghu.independent-forgejo-shadow-verification/v1", + "result": "PASS_100", + "source_node": manifest.get("source_node"), + "shadow_node": args.shadow_node, + "repository_count": len(expected_heads), + "database_repository_count": repository_count, + "database_user_count": user_count, + "checks": checks, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--source-manifest", required=True) + parser.add_argument("--binary", required=True) + parser.add_argument("--repository-root", required=True) + parser.add_argument("--database", required=True) + parser.add_argument("--version-url", required=True) + parser.add_argument("--shadow-node", required=True) + args = parser.parse_args() + try: + result = verify(args) + except (OSError, ValueError, sqlite3.Error, subprocess.SubprocessError) as error: + raise SystemExit(str(error)) from error + print(json.dumps(result, sort_keys=True, separators=(",", ":"))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/product-source/hololake-platform/guanghu-os/standards/jd-independent-forgejo-continuity-contract.json b/product-source/hololake-platform/guanghu-os/standards/jd-independent-forgejo-continuity-contract.json new file mode 100644 index 0000000..8ba76ae --- /dev/null +++ b/product-source/hololake-platform/guanghu-os/standards/jd-independent-forgejo-continuity-contract.json @@ -0,0 +1,61 @@ +{ + "schema": "guanghu.jd-independent-forgejo-continuity-contract/v1", + "contract_id": "GHOS-JD-INDEPENDENT-FORGEJO-CONTINUITY-001", + "state": "SOURCE_AND_TEST_ONLY", + "trigger": "JD-FD-PRIMARY native residency stops the Linux-hosted Forgejo process and makes the current BS-GZ-006 tunnel return 502.", + "causal_route": { + "current": "BS-GZ-006 -> SSH tunnel -> JD Linux Forgejo", + "shadow": "BS-GZ-006 -> dedicated SSH tunnel -> BS-SG-003 independent Forgejo", + "reason_for_bs_sg": "BS-SG-003 has 7.5 GiB memory and 52 GiB free disk, remains available during JD native residency, and already carries the protected native relay role." + }, + "source_binding": { + "node_id": "JD-FD-PRIMARY", + "forgejo_version": "16.0.1", + "binary_sha256": "7a4c568136650c10498a9d3d62c7fd630a0cf09c166293ebd78708248f6398fc", + "repository_count": 7, + "work_path": "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data" + }, + "shadow_binding": { + "node_id": "BS-SG-003", + "listen": "127.0.0.1:19224", + "publicly_reachable": false, + "data_root": "/var/lib/guanghu-code-shadow", + "configuration_root": "/etc/guanghu-os/forgejo-shadow" + }, + "required_exact_readback": [ + "FORGEJO_BINARY_SHA256", + "FORGEJO_HTTP_VERSION", + "SQLITE_INTEGRITY_OK", + "DATABASE_REPOSITORY_COUNT", + "DATABASE_USER_COUNT", + "EXACT_REPOSITORY_PATH_SET", + "EXACT_MAIN_COMMIT_FOR_EVERY_REPOSITORY" + ], + "migration_policy": { + "initial_shadow_snapshot_may_be_online": true, + "online_snapshot_is_cutover_authority": false, + "final_cutover_requires_bounded_write_freeze": true, + "final_cutover_requires_second_exact_sync": true, + "source_is_deleted_after_cutover": false, + "rollback_keeps_jd_linux_forgejo_intact": true + }, + "acceptance_gates": { + "verifier_source": 100, + "isolated_verifier_test": 100, + "bs_sg_full_forgejo_shadow_deployed": 0, + "bs_sg_exact_source_snapshot_readback": 0, + "bs_gz_to_bs_sg_authenticated_tunnel": 0, + "nginx_code_route_switched": 0, + "public_code_during_jd_native_residency": 0, + "full_forgejo_equivalence": 0, + "production_native_cutover": 0 + }, + "prohibited_claims": [ + "ONLINE_DUMP_IS_FINAL_CUTOVER_SNAPSHOT", + "REPOSITORY_GIT_HEADS_ALONE_ARE_FULL_FORGEJO", + "DATABASE_ONLY_IS_FULL_FORGEJO", + "SHADOW_HTTP_200_IS_PUBLIC_ROUTE_SWITCH", + "BS_GZ_EXISTING_UNRELATED_FORGEJO_IS_JD_CODE_CHANNEL", + "SOURCE_IMPLEMENTATION_IS_DEPLOYMENT" + ] +} 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 bebf691..3d000d1 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 @@ -82,6 +82,7 @@ run_gate unit_and_integration_tests \ bash -c ' cargo test --manifest-path "$1/Cargo.toml" --all-targets "$1/scripts/test-native-public-projection.sh" + python3 "$1/scripts/test-independent-forgejo-shadow-verifier.py" ' _ "${source_root}" run_gate zero_warning_lint \ cargo clippy --manifest-path "${source_root}/Cargo.toml" --all-targets -- -D warnings diff --git a/routing/guanghu-os-control-architecture.json b/routing/guanghu-os-control-architecture.json index b9f8583..5350102 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.7", + "version": "2026-08-15.8", "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": "DESIGN_AND_TEST_FAIL_CLOSED_LINUX_FALLBACK_BEFORE_ANY_NGINX_ROUTE_SWITCH_OR_NATIVE_BOOT" + "next_engineering_gate": "DEPLOY_AND_VERIFY_BS_SG_003_FULL_FORGEJO_SHADOW_WITHOUT_PUBLIC_ROUTE_OR_NATIVE_BOOT_CHANGES" }, "implementation": { "guanghu_supervisor_lifecycle_contract_source": 100, @@ -60,6 +60,11 @@ "bs_sg_authenticated_projection_sender": 100, "live_native_projection_freshness": 0, "nginx_native_projection_route_switch": 0, + "independent_forgejo_continuity_contract_source": 100, + "independent_forgejo_shadow_verifier_test": 100, + "bs_sg_full_forgejo_shadow": 0, + "bs_gz_to_bs_sg_forgejo_tunnel": 0, + "nginx_code_route_switch": 0, "public_native_anchor_equivalence": 0, "public_native_code_channel_equivalence": 0, "full_forgejo_equivalence": 0, @@ -96,6 +101,14 @@ "live_native_freshness_readback": 0, "nginx_public_route_switched": 0 }, + "guard_cleanup_evidence": { + "bs_gz_duplicate_forgejo_guard_removed": 100, + "server_receipt": "/var/lib/guanghu-os/receipts/BS-GZ-006-DUPLICATE-FORGEJO-GUARD-REMOVAL-RECEIPT.hldp", + "server_receipt_sha256": "186075da7629c289705e31d580df1f5a77c590a3214392a382459030207c8214", + "preserved_live_forgejo_http": 200, + "public_code_http_after": 200, + "data_deleted": false + }, "historical_evidence_policy": { "bare_metal_gosk_ghal_receipts_remain_valid_for_observed_capabilities": true, "historical_linux_free_completion_formula_is_current": false, @@ -103,6 +116,7 @@ }, "native_service_equivalence_contract": "product-source/hololake-platform/guanghu-os/standards/jd-native-service-equivalence-contract.json", "native_public_projection_contract": "product-source/hololake-platform/guanghu-os/standards/jd-native-public-projection-contract.json", + "independent_forgejo_continuity_contract": "product-source/hololake-platform/guanghu-os/standards/jd-independent-forgejo-continuity-contract.json", "architecture_page": "product-source/hololake-platform/architecture/HOLOLAKE-GUANGHU-OS-MASTER-AND-ON-DEMAND-LINUX-SUBCONTROL-20260810.md", "adr": "product-source/hololake-platform/docs/adr/0175-guanghu-os-master-and-on-demand-linux-subcontrol.md", "development_id": "DEV-20260810-012" diff --git a/routing/guanghu-os-control-architecture.test.mjs b/routing/guanghu-os-control-architecture.test.mjs index 0a6fb48..c4a0445 100644 --- a/routing/guanghu-os-control-architecture.test.mjs +++ b/routing/guanghu-os-control-architecture.test.mjs @@ -15,6 +15,9 @@ const serviceContract = readJson( const projectionContract = readJson( "product-source/hololake-platform/guanghu-os/standards/jd-native-public-projection-contract.json", ); +const forgejoContract = readJson( + "product-source/hololake-platform/guanghu-os/standards/jd-independent-forgejo-continuity-contract.json", +); test("final topology makes Guanghu the master while preserving Linux as on-demand subcontrol and rescue", () => { assert.equal(contract.final_topology, "GUANGHU_MASTER_WITH_ON_DEMAND_LINUX_SUBCONTROL_AND_RESCUE"); @@ -38,7 +41,7 @@ test("current JD state stays transitional and cannot impersonate final master co }); test("the physical shadow and host QEMU cycles are registered without impersonating Guanghu-owned boot control", () => { - assert.equal(contract.version, "2026-08-15.7"); + assert.equal(contract.version, "2026-08-15.8"); 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); @@ -56,6 +59,11 @@ test("the physical shadow and host QEMU cycles are registered without impersonat assert.equal(contract.implementation.bs_sg_authenticated_projection_sender, 100); assert.equal(contract.implementation.live_native_projection_freshness, 0); assert.equal(contract.implementation.nginx_native_projection_route_switch, 0); + assert.equal(contract.implementation.independent_forgejo_continuity_contract_source, 100); + assert.equal(contract.implementation.independent_forgejo_shadow_verifier_test, 100); + assert.equal(contract.implementation.bs_sg_full_forgejo_shadow, 0); + assert.equal(contract.implementation.bs_gz_to_bs_sg_forgejo_tunnel, 0); + assert.equal(contract.implementation.nginx_code_route_switch, 0); assert.equal(contract.implementation.public_native_anchor_equivalence, 0); assert.equal(contract.implementation.public_native_code_channel_equivalence, 0); assert.equal(contract.implementation.full_forgejo_equivalence, 0); @@ -72,6 +80,27 @@ test("the physical shadow and host QEMU cycles are registered without impersonat assert.equal(contract.native_public_projection_shadow_evidence.nginx_public_route_switched, 0); assert.match(contract.native_public_projection_shadow_evidence.bs_gz_server_receipt_sha256, /^[0-9a-f]{64}$/); assert.match(contract.native_public_projection_shadow_evidence.bs_sg_server_receipt_sha256, /^[0-9a-f]{64}$/); + assert.equal(contract.guard_cleanup_evidence.bs_gz_duplicate_forgejo_guard_removed, 100); + assert.equal(contract.guard_cleanup_evidence.preserved_live_forgejo_http, 200); + assert.equal(contract.guard_cleanup_evidence.public_code_http_after, 200); + assert.equal(contract.guard_cleanup_evidence.data_deleted, false); + assert.match(contract.guard_cleanup_evidence.server_receipt_sha256, /^[0-9a-f]{64}$/); +}); + +test("full Forgejo continuity remains separate from the read-only native code snapshot", () => { + assert.equal(forgejoContract.source_binding.node_id, "JD-FD-PRIMARY"); + assert.equal(forgejoContract.source_binding.repository_count, 7); + assert.equal(forgejoContract.shadow_binding.node_id, "BS-SG-003"); + assert.equal(forgejoContract.shadow_binding.publicly_reachable, false); + assert.equal(forgejoContract.migration_policy.online_snapshot_is_cutover_authority, false); + assert.equal(forgejoContract.migration_policy.final_cutover_requires_bounded_write_freeze, true); + assert.equal(forgejoContract.acceptance_gates.verifier_source, 100); + assert.equal(forgejoContract.acceptance_gates.isolated_verifier_test, 100); + assert.equal(forgejoContract.acceptance_gates.bs_sg_full_forgejo_shadow_deployed, 0); + assert.equal(forgejoContract.acceptance_gates.bs_gz_to_bs_sg_authenticated_tunnel, 0); + assert.equal(forgejoContract.acceptance_gates.nginx_code_route_switched, 0); + assert.equal(forgejoContract.acceptance_gates.full_forgejo_equivalence, 0); + assert.equal(forgejoContract.acceptance_gates.production_native_cutover, 0); }); test("native public projection separates liveness, authenticated state, and pinned public bytes", () => {