diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 17f9a9ca..3ac582aa 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -17,16 +17,139 @@ jobs: SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }} run: | set -euo pipefail - git init -q - git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" - git -c http.extraheader="AUTHORIZATION: token ${TOKEN}" fetch --depth=1 origin "${GITHUB_SHA}" - git checkout -q --detach FETCH_HEAD - if [ -z "${SUBMODULE_TOKEN}" ]; then - echo "SUBMODULE_TOKEN is not configured" >&2 - exit 1 - fi - git config --global url."https://weichen:${SUBMODULE_TOKEN}@git-home.u1680832.nyat.app:30319/".insteadOf "ssh://git@git-ssh.u1680832.nyat.app:31411/" - git -c fetch.recurseSubmodules=false submodule update --init --recursive + # Credential handling stays inline: no file from the candidate is executed. + # Workflow provenance and runner isolation still require platform controls. + python3 - <<'PY_CHECKOUT' + import os + from pathlib import Path + import re + import subprocess + import sys + from urllib.parse import urlsplit + + SERVER = "https://git-home.u1680832.nyat.app:30319" + SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/" + # Reviewed allowlist, independent of candidate .gitmodules. Add repositories + # here only with an explicit review of the checkout credential's scope. + REPOSITORIES = { + "modules/core": "Agent-System-Core", + "modules/backend": "Agent-System-Backend", + "modules/frontend": "Agent-System-Frontend", + "modules/ui": "Agent-System-UI", + "modules/documents": "Agent-System-Documents", + "deps/agent-system-core": "Agent-System-Core", + "deps/development-guidelines": "Development-Guidelines", + "deps/agent-native": "Agent-Native", + "deps/agent-squad": "Agent-Squad", + "deps/agent-intent": "Agent-Intent", + "deps/agent-command": "Agent-Command", + "deps/error-code-system": "Error-Code-System", + "deps/logging-system": "Logging-System", + } + root_token = os.environ.pop("TOKEN", "") + component_token = os.environ.pop("SUBMODULE_TOKEN", "") + if not root_token or not component_token: + sys.exit("Checkout requires TOKEN and SUBMODULE_TOKEN") + if os.environ.get("GITHUB_SERVER_URL", "").rstrip("/") != SERVER: + sys.exit("Checkout server is outside the reviewed allowlist") + if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-System-Dev": + sys.exit("Checkout repository is outside the reviewed allowlist") + sha = os.environ.get("GITHUB_SHA", "") + if not re.fullmatch(r"[0-9a-fA-F]{40}", sha): + sys.exit("Checkout requires a fixed commit SHA") + + # Discard inherited Git tracing/config/SSH hooks. Auth lives only in one + # Git process tree, in an exact URL-scoped header, never in a URL or file. + clean_env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")} + clean_env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", + GIT_CONFIG_GLOBAL="/dev/null", GIT_TERMINAL_PROMPT="0") + config = { + "core.hooksPath": "/dev/null", "core.fsmonitor": "false", + "init.templateDir": "", "credential.helper": "", + "fetch.recurseSubmodules": "false", "submodule.recurse": "false", + "http.followRedirects": "false", "protocol.allow": "never", + "protocol." + urlsplit(SERVER).scheme + ".allow": "always", + } + + + def git(repo, *args, extra=None): + values = config | (extra or {}) + env = clean_env | {"GIT_CONFIG_COUNT": str(len(values))} + for index, (key, value) in enumerate(values.items()): + env[f"GIT_CONFIG_KEY_{index}"] = key + env[f"GIT_CONFIG_VALUE_{index}"] = value + return subprocess.run(["git", "-C", str(repo), *args], env=env, + check=True, stdout=subprocess.PIPE).stdout + + + def materialize(repo, depth=0): + if depth > 16: + raise ValueError("Submodule depth exceeds the reviewed checkout limit") + links = {} + for entry in git(repo, "ls-tree", "-rz", "HEAD").split(b"\0"): + if not entry: + continue + metadata, path = entry.split(b"\t", 1) + mode, kind, commit = metadata.decode().split() + if mode == "160000": + links[path.decode()] = commit + if not links: + return + raw = git(repo, "config", "--no-includes", "--blob", "HEAD:.gitmodules", "--null", "--list") + entries = {} + for item in raw.split(b"\0"): + if not item: + continue + key, value = item.decode().split("\n", 1) + match = re.fullmatch(r"submodule\.(.+)\.(path|url)", key) + if not match: + raise ValueError("Unsupported candidate .gitmodules setting") + name, field = match.groups() + if field in entries.setdefault(name, {}): + raise ValueError("Duplicate candidate .gitmodules setting") + entries[name][field] = value + if set(entries) != set(links): + raise ValueError("Submodule names must match the fixed gitlink paths") + # Validate the entire level before any authenticated component fetch. + for name, fields in entries.items(): + repository = REPOSITORIES.get(name) + if not repository or fields.get("path") != name: + raise ValueError("Submodule path is outside the reviewed allowlist") + trusted_url = f"{SERVER}/weichen/{repository}.git" + if fields.get("url") not in {SSH + repository + ".git", trusted_url}: + raise ValueError("Submodule URL is outside the reviewed allowlist") + for name in entries: + url = f"{SERVER}/weichen/{REPOSITORIES[name]}.git" + # Persist only the reviewed public URL so Git recognizes the + # initialized submodule after process-scoped config is gone. + git(repo, "config", f"submodule.{name}.url", url) + git(repo, "submodule", "update", "--init", "--checkout", "--", name, + extra={f"submodule.{name}.url": url, + f"submodule.{name}.update": "checkout", + f"http.{url}.extraheader": "AUTHORIZATION: token " + component_token}) + child = repo / name + if git(child, "rev-parse", "HEAD").decode().strip() != links[name]: + raise ValueError("Materialized submodule does not match the fixed SHA") + materialize(child, depth + 1) + + + try: + root = Path.cwd() + # An ephemeral clean checkout is required; do not reuse prior Git state. + if (root / ".git").exists(): + raise ValueError("Checkout requires a fresh workspace") + git(root, "init", "-q") + url = SERVER + "/weichen/Agent-System-Dev.git" + git(root, "remote", "add", "origin", url) + git(root, "fetch", "--depth=1", "origin", sha, + extra={f"http.{url}.extraheader": "AUTHORIZATION: token " + root_token}) + git(root, "checkout", "-q", "--detach", "FETCH_HEAD") + materialize(root) + except (ValueError, OSError, subprocess.CalledProcessError) as error: + sys.exit(f"Checkout refused: {error}") + print("Checkout complete; credentials were not persisted in Git configuration") + PY_CHECKOUT + unset TOKEN SUBMODULE_TOKEN - name: Install Go 1.27.1 run: | set -euo pipefail diff --git a/.gitignore b/.gitignore index e43709b1..d7dc4407 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /bin/ node_modules/ +__pycache__/ +*.py[cod] /data/ /data-test/ .zcode/plans/ diff --git a/.task/feature-governance-reliability.md b/.task/feature-governance-reliability.md index 55188ad3..0dc51c41 100644 --- a/.task/feature-governance-reliability.md +++ b/.task/feature-governance-reliability.md @@ -6,11 +6,11 @@ - 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 43d33aa4921078bc202393b99cba017d7f731b98 - 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability(worktree);分支:feature/governance-reliability -- 授权与交付终点:<本次允许的动作与终点;未授权事项写明> +- 授权与交付终点:用户于 2026-10-05 批准按审查方案完善规范及门禁。交付为经过本地验证的修复与可审核 PR;不改远端保护、不发布、不部署、不替所有者合入。 ## 目标与非目标 -<预期结果、受影响入口/模块/契约;明确不做什么。> +修复回收丢工作、组件同步漂移、证据管道/脱敏/来源、启动收据闭环、远端新鲜度、写守卫和 CI 凭据边界;对齐规则、提交格式、堆叠分支及子仓入口,加入持续治理回归。保持产品运行行为与高后果双钥匙边界。 ## 同步记录 @@ -29,3 +29,236 @@ - 结果: - 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表> - 影响与剩余项: + +## 实施与验收安排 + +- 独立单元:任务生命周期;证据收据;写路径与 CI;根入口与组件文档。共享工作区按文件分工,主执行者统一提交与验证。 +- 验证:治理脚本用临时真实仓库和虚构凭据先复现再修复;验收前 task-sync,执行 make test-governance、make test、make build、差异和链接检查。 +- 初次 precheck 由原工具执行,完整原始输出已直接捕获为仓外 JSON;新工具以 `cat /tmp/governance-precheck-20261005.json` 归档原工具捕获的完整结果(SELF)。该 cat 命令仅保全旧工具输出,不冒充重新执行 precheck;原执行时未采集新 schema 的完整快照,此限制保留。 +- Core 项目事实文档在独立组件工作区修复,组件 PR 单独审核;Dev 保持现有 Core 组合,避免为文档修订擅自升级公共契约及嵌套消费者。 + + +```json +{ + "schema": 1, + "source": "SELF", + "timestamp": "2026-10-05T12:28:49.553253+00:00", + "command": "cat /tmp/governance-precheck-20261005.json", + "command_sha256": "b94d70b4507591d8e66b6de49c19ba7a2c1815f4a5f34c10819e72c57785af94", + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability", + "common_dir": "/home/weichen/projects/Agent-System-Dev/.git", + "before": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\n M modules/documents\n M modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "c1e907060c99eb60cb8d81fe5dbcccbfe56f49afd057f7d9cc4a7ecb932c7721", + "untracked": { + "policy/governance.md": "ef30f673b288c245bea2e7905d95c2206c5d3e88407b74085447e02b3b8bad7d", + "scripts/check-worktree.py": "44e55a688bb388048678024635d009505a4586539a2b731656f8943cf045a186", + "scripts/evidence.py": "f5e283748d2a4ecfca72400483df43a730935082e3adcd09e7a66f9f30cb5802", + "scripts/zcode/write_guard.py": "2cd7ba55036b4e3488ba2b1cac3f7e3ac24d1e7265eccbc401b3da65e99fd667", + "tests/test_ci_credentials.py": "7cb0f656438b838c1e9b118eb21193107289a4571b3bde362badd73f73d19282", + "tests/test_evidence.py": "18eafdb26d37655e4c59cb2d63421b6a46d0129cd8e59b7611990d254caab582", + "tests/test_task.py": "a6bc746a3ffee1773b0cad4222b274659cf959a9a7363afe49ed6512d36f0c1d", + "tests/test_write_guard.py": "6438557981ce04ea25740a695d3e46923d0e24eb51a05ab006d76b26c5873723" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n+3b1c2769e1651aedcd20df9994173b396585bd84 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 27d5adb5c679101b596fbb7701b43e946674a8e6 modules/ui (heads/feature/governance-docs)\n" + }, + "after": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\n M modules/documents\n M modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "c1e907060c99eb60cb8d81fe5dbcccbfe56f49afd057f7d9cc4a7ecb932c7721", + "untracked": { + "policy/governance.md": "ef30f673b288c245bea2e7905d95c2206c5d3e88407b74085447e02b3b8bad7d", + "scripts/check-worktree.py": "44e55a688bb388048678024635d009505a4586539a2b731656f8943cf045a186", + "scripts/evidence.py": "f5e283748d2a4ecfca72400483df43a730935082e3adcd09e7a66f9f30cb5802", + "scripts/zcode/write_guard.py": "2cd7ba55036b4e3488ba2b1cac3f7e3ac24d1e7265eccbc401b3da65e99fd667", + "tests/test_ci_credentials.py": "7cb0f656438b838c1e9b118eb21193107289a4571b3bde362badd73f73d19282", + "tests/test_evidence.py": "18eafdb26d37655e4c59cb2d63421b6a46d0129cd8e59b7611990d254caab582", + "tests/test_task.py": "a6bc746a3ffee1773b0cad4222b274659cf959a9a7363afe49ed6512d36f0c1d", + "tests/test_write_guard.py": "6438557981ce04ea25740a695d3e46923d0e24eb51a05ab006d76b26c5873723" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n+3b1c2769e1651aedcd20df9994173b396585bd84 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 27d5adb5c679101b596fbb7701b43e946674a8e6 modules/ui (heads/feature/governance-docs)\n" + }, + "after_error": null, + "exit_code": 0, + "output": "{\n \"command\": \"make precheck\",\n \"head\": \"43d33aa4921078bc202393b99cba017d7f731b98\",\n \"exit_code\": 0,\n \"output\": \"[repo] branch=main head=43d33aa492\\n[remote] origin/main=43d33aa492 ahead=0 behind=0\\n[tree] clean submodules_drift=0 uninit=0\\n[worktrees] linked=0\\n[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)\\n[facts] policy/facts.md 4 条事实均在有效期内\\n[required-reading] core/baseline.md, workflows/delivery.md §1\\n[R0] 43d33aa492(完整:43d33aa4921078bc202393b99cba017d7f731b98)\\n[verdict] READY — 可建任务工作区:make task-start NAME=feature/your-branch\\n\",\n \"source\": \"SELF\",\n \"captured_at\": \"2026-10-05T12:18:08.464Z\",\n \"capture\": \"exec_command/write_stdin\"\n}\n", + "output_sha256": "a8562eb88e916d2d4faf52b84011a7e71226b823a81e21b9ef948208f8d2e451", + "receipt_sha256": "d42c0b3bc5e09b0d82c59ea306caae39d5ca1f5cffc3cc316de9da6e89d75a42" +} +``` + + +```json +{ + "schema": 1, + "source": "SELF", + "timestamp": "2026-10-05T12:31:46.428805+00:00", + "command": "python3 -c 'import pathlib,re,urllib.request,json; s=pathlib.Path.home().joinpath('\"'\"'.secrets/gitea'\"'\"').read_text(); t=re.search(r'\"'\"'^[ \\t-]*ACCESS_TOKEN='\"'\"',s,re.M).group(1).strip(); base='\"'\"'https://git-home.u1680832.nyat.app:30319/api/v1/repos/weichen/Agent-System-Dev'\"'\"'; d=json.load(urllib.request.urlopen(urllib.request.Request(base+'\"'\"'/branch_protections/main'\"'\"',headers={'\"'\"'Authorization'\"'\"':'\"'\"'token '\"'\"'+t}),timeout=20)); print(json.dumps({k:d.get(k) for k in ['\"'\"'branch_name'\"'\"','\"'\"'enable_push'\"'\"','\"'\"'enable_status_check'\"'\"','\"'\"'status_check_contexts'\"'\"','\"'\"'required_approvals'\"'\"','\"'\"'block_on_outdated_branch'\"'\"']},ensure_ascii=False)); c=json.load(urllib.request.urlopen(urllib.request.Request(base+'\"'\"'/commits/ea54f1108b06abcdc59dd9fc9b83d4920c8af293/status'\"'\"',headers={'\"'\"'Authorization'\"'\"':'\"'\"'token '\"'\"'+t}),timeout=20)); print(json.dumps([{'\"'\"'context'\"'\"':x.get('\"'\"'context'\"'\"'),'\"'\"'status'\"'\"':x.get('\"'\"'status'\"'\"')} for x in c.get('\"'\"'statuses'\"'\"',[])],ensure_ascii=False))'", + "command_sha256": "434061e6fa363d37db52e4e79b910f045b87be9810e1793f3b200571bf3c3f0d", + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability", + "common_dir": "/home/weichen/projects/Agent-System-Dev/.git", + "before": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\nM modules/documents\nM modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "aabbccba660f27eae9ec1680a6ddd72fe96895ef836e049894a94e2234f3339f", + "untracked": { + "policy/governance.md": "deb69fbaf7666a09b07bf096675fd6daaa6052fd195121a6ce9c1ed1d7f1c46b", + "scripts/check-worktree.py": "6ef68e6751563b87bdbe4828e11fc1a4f9ee715b67914c627edbc062bbcdd0db", + "scripts/evidence.py": "0474a164344c65ec5e97b4d638948c7d776a7dd7c11de8045ef2cc029ac2f3b1", + "scripts/zcode/write_guard.py": "42cec254a43b26cac3bed85f2bc83890a13495ce7df160122a4629a2a4339673", + "tests/test_ci_credentials.py": "06170a18f7ee9914b7d486804ef7a1a3e70d7d144cfed5eb40a077eebbeea1f5", + "tests/test_evidence.py": "c9e0392853f0601672f1781c64ec92a496e63b0c0482df813581cd6aa34671e2", + "tests/test_task.py": "77fb40e8b57935dac74d5c2fdaa6f21b92a8fef79f7f73a9513bf53ab2eb94fb", + "tests/test_write_guard.py": "3a74d0b0cbee51654954312a57c867c1b7f936c7b021e00d358070c9df2f040a" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n 19e89ae10bd93306400f13a9a1f449cbc5bfaa42 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 2cb1496320dda6e65501ac94318295f067231dd7 modules/ui (heads/feature/governance-docs)\n" + }, + "after": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\nM modules/documents\nM modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "aabbccba660f27eae9ec1680a6ddd72fe96895ef836e049894a94e2234f3339f", + "untracked": { + "policy/governance.md": "deb69fbaf7666a09b07bf096675fd6daaa6052fd195121a6ce9c1ed1d7f1c46b", + "scripts/check-worktree.py": "6ef68e6751563b87bdbe4828e11fc1a4f9ee715b67914c627edbc062bbcdd0db", + "scripts/evidence.py": "0474a164344c65ec5e97b4d638948c7d776a7dd7c11de8045ef2cc029ac2f3b1", + "scripts/zcode/write_guard.py": "42cec254a43b26cac3bed85f2bc83890a13495ce7df160122a4629a2a4339673", + "tests/test_ci_credentials.py": "06170a18f7ee9914b7d486804ef7a1a3e70d7d144cfed5eb40a077eebbeea1f5", + "tests/test_evidence.py": "c9e0392853f0601672f1781c64ec92a496e63b0c0482df813581cd6aa34671e2", + "tests/test_task.py": "77fb40e8b57935dac74d5c2fdaa6f21b92a8fef79f7f73a9513bf53ab2eb94fb", + "tests/test_write_guard.py": "3a74d0b0cbee51654954312a57c867c1b7f936c7b021e00d358070c9df2f040a" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n 19e89ae10bd93306400f13a9a1f449cbc5bfaa42 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 2cb1496320dda6e65501ac94318295f067231dd7 modules/ui (heads/feature/governance-docs)\n" + }, + "after_error": null, + "exit_code": 0, + "output": "{\"branch_name\": \"main\", \"enable_push\": false, \"enable_status_check\": false, \"status_check_contexts\": null, \"required_approvals\": 0, \"block_on_outdated_branch\": true}\n[{\"context\": \"ci / test (pull_request)\", \"status\": \"success\"}]\n", + "output_sha256": "a6b91772f03be40ddbb26edb6bf4e753951ed45cc1f9cb669f089b7c74ffa749", + "receipt_sha256": "f0e565afe5ad85e2673147135060439e71aa5bbcde5e7fad5f7006f3d6726f1d" +} +``` + + +```json +{ + "schema": 1, + "source": "SELF", + "timestamp": "2026-10-05T12:32:41.548057+00:00", + "command": "make test-governance", + "command_sha256": "9776a8dfcfb8f4c95abc94c40cae3a2c0e91e8f4b91fe95d98085c290e929158", + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability", + "common_dir": "/home/weichen/projects/Agent-System-Dev/.git", + "before": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\nM modules/documents\nM modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "cf1634b5978fa70ebcf07031c85a49cac4813e95cfd64005bba230908f624da0", + "untracked": { + "policy/governance.md": "deb69fbaf7666a09b07bf096675fd6daaa6052fd195121a6ce9c1ed1d7f1c46b", + "scripts/check-worktree.py": "6ef68e6751563b87bdbe4828e11fc1a4f9ee715b67914c627edbc062bbcdd0db", + "scripts/evidence.py": "0474a164344c65ec5e97b4d638948c7d776a7dd7c11de8045ef2cc029ac2f3b1", + "scripts/zcode/write_guard.py": "42cec254a43b26cac3bed85f2bc83890a13495ce7df160122a4629a2a4339673", + "tests/test_ci_credentials.py": "06170a18f7ee9914b7d486804ef7a1a3e70d7d144cfed5eb40a077eebbeea1f5", + "tests/test_evidence.py": "c9e0392853f0601672f1781c64ec92a496e63b0c0482df813581cd6aa34671e2", + "tests/test_task.py": "77fb40e8b57935dac74d5c2fdaa6f21b92a8fef79f7f73a9513bf53ab2eb94fb", + "tests/test_write_guard.py": "3a74d0b0cbee51654954312a57c867c1b7f936c7b021e00d358070c9df2f040a" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n 19e89ae10bd93306400f13a9a1f449cbc5bfaa42 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 2cb1496320dda6e65501ac94318295f067231dd7 modules/ui (heads/feature/governance-docs)\n" + }, + "after": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\nM modules/documents\nM modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "cf1634b5978fa70ebcf07031c85a49cac4813e95cfd64005bba230908f624da0", + "untracked": { + "policy/governance.md": "deb69fbaf7666a09b07bf096675fd6daaa6052fd195121a6ce9c1ed1d7f1c46b", + "scripts/check-worktree.py": "6ef68e6751563b87bdbe4828e11fc1a4f9ee715b67914c627edbc062bbcdd0db", + "scripts/evidence.py": "0474a164344c65ec5e97b4d638948c7d776a7dd7c11de8045ef2cc029ac2f3b1", + "scripts/zcode/write_guard.py": "42cec254a43b26cac3bed85f2bc83890a13495ce7df160122a4629a2a4339673", + "tests/test_ci_credentials.py": "06170a18f7ee9914b7d486804ef7a1a3e70d7d144cfed5eb40a077eebbeea1f5", + "tests/test_evidence.py": "c9e0392853f0601672f1781c64ec92a496e63b0c0482df813581cd6aa34671e2", + "tests/test_task.py": "77fb40e8b57935dac74d5c2fdaa6f21b92a8fef79f7f73a9513bf53ab2eb94fb", + "tests/test_write_guard.py": "3a74d0b0cbee51654954312a57c867c1b7f936c7b021e00d358070c9df2f040a" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n 19e89ae10bd93306400f13a9a1f449cbc5bfaa42 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 2cb1496320dda6e65501ac94318295f067231dd7 modules/ui (heads/feature/governance-docs)\n" + }, + "after_error": null, + "exit_code": 0, + "output": "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability'\nPYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_*.py' -v\ntest_candidate_cannot_read_checkout_credentials (test_ci_credentials.CheckoutTests.test_candidate_cannot_read_checkout_credentials) ... ok\ntest_candidate_submodule_update_commands_are_rejected (test_ci_credentials.CheckoutTests.test_candidate_submodule_update_commands_are_rejected) ... ok\ntest_external_or_unlisted_urls_are_rejected_before_component_fetch (test_ci_credentials.CheckoutTests.test_external_or_unlisted_urls_are_rejected_before_component_fetch) ... ok\ntest_inherited_template_hook_does_not_execute_with_credentials (test_ci_credentials.CheckoutTests.test_inherited_template_hook_does_not_execute_with_credentials) ... ok\ntest_nested_external_url_is_rejected_before_authenticated_fetch (test_ci_credentials.CheckoutTests.test_nested_external_url_is_rejected_before_authenticated_fetch) ... ok\ntest_recursive_gitlinks_materialize_exact_sha_without_persisting_credentials (test_ci_credentials.CheckoutTests.test_recursive_gitlinks_materialize_exact_sha_without_persisting_credentials) ... ok\ntest_redirects_do_not_forward_credential_or_fetch_unlisted_target (test_ci_credentials.CheckoutTests.test_redirects_do_not_forward_credential_or_fetch_unlisted_target) ... ok\ntest_after_snapshot_failure_keeps_command_exit_and_output (test_evidence.EvidenceTests.test_after_snapshot_failure_keeps_command_exit_and_output) ... ok\ntest_all_persisted_and_displayed_text_is_sanitized (test_evidence.EvidenceTests.test_all_persisted_and_displayed_text_is_sanitized) ... ok\ntest_before_after_heads_do_not_relabel_changed_command (test_evidence.EvidenceTests.test_before_after_heads_do_not_relabel_changed_command) ... ok\ntest_capture_does_not_overwrite_an_existing_receipt (test_evidence.EvidenceTests.test_capture_does_not_overwrite_an_existing_receipt) ... ok\ntest_capture_without_card_and_import_without_rerun (test_evidence.EvidenceTests.test_capture_without_card_and_import_without_rerun) ... ok\ntest_child_failure_code_is_preserved (test_evidence.EvidenceTests.test_child_failure_code_is_preserved) ... ok\ntest_command_line_token_arguments_are_redacted (test_evidence.EvidenceTests.test_command_line_token_arguments_are_redacted) ... ok\ntest_dirty_and_untracked_contents_have_object_digests (test_evidence.EvidenceTests.test_dirty_and_untracked_contents_have_object_digests) ... ok\ntest_failed_child_exit_survives_receipt_write_failure (test_evidence.EvidenceTests.test_failed_child_exit_survives_receipt_write_failure) ... ok\ntest_full_output_is_preserved_not_only_tail (test_evidence.EvidenceTests.test_full_output_is_preserved_not_only_tail) ... ok\ntest_import_missing_card_fails_without_losing_receipt (test_evidence.EvidenceTests.test_import_missing_card_fails_without_losing_receipt) ... ok\ntest_import_retry_is_idempotent (test_evidence.EvidenceTests.test_import_retry_is_idempotent) ... ok\ntest_invalid_field_types_and_forged_source_are_rejected (test_evidence.EvidenceTests.test_invalid_field_types_and_forged_source_are_rejected) ... ok\ntest_known_secret_environment_value_is_redacted_without_label (test_evidence.EvidenceTests.test_known_secret_environment_value_is_redacted_without_label) ... ok\ntest_linked_worktree_import_preserves_source_identity (test_evidence.EvidenceTests.test_linked_worktree_import_preserves_source_identity) ... ok\ntest_missing_card_does_not_execute (test_evidence.EvidenceTests.test_missing_card_does_not_execute) ... ok\ntest_missing_required_snapshot_fields_are_rejected (test_evidence.EvidenceTests.test_missing_required_snapshot_fields_are_rejected) ... ok\ntest_nested_cwd_records_untracked_files_from_repository_root (test_evidence.EvidenceTests.test_nested_cwd_records_untracked_files_from_repository_root) ... ok\ntest_output_digest_checked_even_if_receipt_digest_recomputed (test_evidence.EvidenceTests.test_output_digest_checked_even_if_receipt_digest_recomputed) ... ok\ntest_pipeline_failure_is_recorded_and_returned (test_evidence.EvidenceTests.test_pipeline_failure_is_recorded_and_returned) ... ok\ntest_quoted_credentials_with_spaces_are_fully_redacted (test_evidence.EvidenceTests.test_quoted_credentials_with_spaces_are_fully_redacted) ... ok\ntest_receipt_write_failure_cannot_claim_success (test_evidence.EvidenceTests.test_receipt_write_failure_cannot_claim_success) ... ok\ntest_recorder_environment_does_not_leak_into_nested_commands (test_evidence.EvidenceTests.test_recorder_environment_does_not_leak_into_nested_commands) ... ok\ntest_source_cannot_be_promoted_by_environment (test_evidence.EvidenceTests.test_source_cannot_be_promoted_by_environment) ... ok\ntest_tamper_is_rejected_before_card_append (test_evidence.EvidenceTests.test_tamper_is_rejected_before_card_append) ... ok\ntest_unrelated_repo_cannot_import (test_evidence.EvidenceTests.test_unrelated_repo_cannot_import) ... ok\ntest_validate_precheck_checks_result_and_current_base (test_evidence.EvidenceTests.test_validate_precheck_checks_result_and_current_base) ... ok\ntest_validate_precheck_rejects_successful_arbitrary_command (test_evidence.EvidenceTests.test_validate_precheck_rejects_successful_arbitrary_command) ... ok\ntest_finish_clean_worktree_has_accurate_remote_result (test_task.TaskLifecycleTests.test_finish_clean_worktree_has_accurate_remote_result) ... ok\ntest_finish_deletes_only_merged_remote_branch (test_task.TaskLifecycleTests.test_finish_deletes_only_merged_remote_branch) ... ok\ntest_finish_preserves_component_commits_off_the_pinned_head (test_task.TaskLifecycleTests.test_finish_preserves_component_commits_off_the_pinned_head) ... ok\ntest_finish_preserves_component_untracked_file (test_task.TaskLifecycleTests.test_finish_preserves_component_untracked_file) ... ok\ntest_finish_preserves_locked_worktree (test_task.TaskLifecycleTests.test_finish_preserves_locked_worktree) ... ok\ntest_finish_preserves_main_and_active_index_lock (test_task.TaskLifecycleTests.test_finish_preserves_main_and_active_index_lock) ... ok\ntest_finish_preserves_modified_untracked_and_ignored_files (test_task.TaskLifecycleTests.test_finish_preserves_modified_untracked_and_ignored_files) ... ok\ntest_finish_preserves_remote_unmerged_tip (test_task.TaskLifecycleTests.test_finish_preserves_remote_unmerged_tip) ... ok\ntest_finish_recycles_clean_submodule_without_shared_config_changes (test_task.TaskLifecycleTests.test_finish_recycles_clean_submodule_without_shared_config_changes) ... ok\ntest_finish_rejects_subdirectory_without_deleting_it (test_task.TaskLifecycleTests.test_finish_rejects_subdirectory_without_deleting_it) ... ok\ntest_finish_remote_deletion_lease_preserves_concurrent_update (test_task.TaskLifecycleTests.test_finish_remote_deletion_lease_preserves_concurrent_update) ... ok\ntest_finish_requires_writer_exit_confirmation (test_task.TaskLifecycleTests.test_finish_requires_writer_exit_confirmation) ... ok\ntest_precheck_feature_behind_requires_sync (test_task.TaskLifecycleTests.test_precheck_feature_behind_requires_sync) ... ok\ntest_precheck_rejects_unreachable_authority (test_task.TaskLifecycleTests.test_precheck_rejects_unreachable_authority) ... ok\ntest_start_imports_precheck_and_creates_four_part_commit (test_task.TaskLifecycleTests.test_start_imports_precheck_and_creates_four_part_commit) ... ok\ntest_start_offline_does_not_create_worktree (test_task.TaskLifecycleTests.test_start_offline_does_not_create_worktree) ... ok\ntest_start_rejects_receipt_when_authoritative_base_changed (test_task.TaskLifecycleTests.test_start_rejects_receipt_when_authoritative_base_changed) ... ok\ntest_start_requires_precheck_receipt_before_creating_worktree (test_task.TaskLifecycleTests.test_start_requires_precheck_receipt_before_creating_worktree) ... ok\ntest_start_stacked_branch_records_parent_and_main_baseline (test_task.TaskLifecycleTests.test_start_stacked_branch_records_parent_and_main_baseline) ... ok\ntest_sync_commit_failure_never_reports_synced (test_task.TaskLifecycleTests.test_sync_commit_failure_never_reports_synced) ... ok\ntest_sync_keeps_unchanged_component_branch (test_task.TaskLifecycleTests.test_sync_keeps_unchanged_component_branch) ... ok\ntest_sync_materializes_nested_detached_components (test_task.TaskLifecycleTests.test_sync_materializes_nested_detached_components) ... ok\ntest_sync_materializes_updated_component (test_task.TaskLifecycleTests.test_sync_materializes_updated_component) ... ok\ntest_sync_preserves_component_feature_branch (test_task.TaskLifecycleTests.test_sync_preserves_component_feature_branch) ... ok\ntest_sync_preserves_dirty_child (test_task.TaskLifecycleTests.test_sync_preserves_dirty_child) ... ok\ntest_sync_preserves_nested_feature_branch_when_parent_pin_changes (test_task.TaskLifecycleTests.test_sync_preserves_nested_feature_branch_when_parent_pin_changes) ... ok\ntest_verify_rejects_recursive_drift_even_when_root_has_local_edits (test_task.TaskLifecycleTests.test_verify_rejects_recursive_drift_even_when_root_has_local_edits) ... ok\ntest_verify_rejects_uninitialized_component (test_task.TaskLifecycleTests.test_verify_rejects_uninitialized_component) ... ok\ntest_deleting_main_symlink_to_outside_is_denied (test_write_guard.WriteGuardTests.test_deleting_main_symlink_to_outside_is_denied) ... ok\ntest_detached_and_bad_branches_are_denied (test_write_guard.WriteGuardTests.test_detached_and_bad_branches_are_denied) ... ok\ntest_every_patch_file_and_move_destination_is_checked (test_write_guard.WriteGuardTests.test_every_patch_file_and_move_destination_is_checked) ... ok\ntest_feature_missing_parent_is_allowed (test_write_guard.WriteGuardTests.test_feature_missing_parent_is_allowed) ... ok\ntest_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok\ntest_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok\ntest_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok\ntest_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok\ntest_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok\ntest_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok\ntest_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok\ntest_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok\n\n----------------------------------------------------------------------\nRan 75 tests in 13.555s\n\nOK\nmake[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability'\n", + "output_sha256": "2cfe8716dc1ed38ad270f38a8e76a72585588a9e66df0591a4f502dfc5cd62da", + "receipt_sha256": "48e7a27e61e932a83d77cee3cd08a06a5221dca17d89e4b2b7cb673a203924e5" +} +``` + + +```json +{ + "schema": 1, + "source": "SELF", + "timestamp": "2026-10-05T12:35:19.119587+00:00", + "command": "make test-governance", + "command_sha256": "9776a8dfcfb8f4c95abc94c40cae3a2c0e91e8f4b91fe95d98085c290e929158", + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability", + "common_dir": "/home/weichen/projects/Agent-System-Dev/.git", + "before": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\nM modules/documents\nM modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? policy/platform-admission.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "0da614b93dd1f62d46bd06955113fcffbd2c0931d0e78ffe22cc2d75d57e082c", + "untracked": { + "policy/governance.md": "deb69fbaf7666a09b07bf096675fd6daaa6052fd195121a6ce9c1ed1d7f1c46b", + "policy/platform-admission.md": "56a4e0b8ced66de70bac1e580dfa12dd25cd3d4bd0441b16e0fbd2d838911f34", + "scripts/check-worktree.py": "6ef68e6751563b87bdbe4828e11fc1a4f9ee715b67914c627edbc062bbcdd0db", + "scripts/evidence.py": "1347527e19e8df4e6eccdd3ae2a988cf58a16fa022b61a6144d32f0cd6966481", + "scripts/zcode/write_guard.py": "42cec254a43b26cac3bed85f2bc83890a13495ce7df160122a4629a2a4339673", + "tests/test_ci_credentials.py": "06170a18f7ee9914b7d486804ef7a1a3e70d7d144cfed5eb40a077eebbeea1f5", + "tests/test_evidence.py": "c9e0392853f0601672f1781c64ec92a496e63b0c0482df813581cd6aa34671e2", + "tests/test_task.py": "77fb40e8b57935dac74d5c2fdaa6f21b92a8fef79f7f73a9513bf53ab2eb94fb", + "tests/test_write_guard.py": "3a74d0b0cbee51654954312a57c867c1b7f936c7b021e00d358070c9df2f040a" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n 19e89ae10bd93306400f13a9a1f449cbc5bfaa42 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 2cb1496320dda6e65501ac94318295f067231dd7 modules/ui (heads/feature/governance-docs)\n" + }, + "after": { + "head": "46a17707988e63c764317a0af46cc63eb1b3e879", + "branch": "feature/governance-reliability", + "target_branch": "main", + "target_head": "43d33aa4921078bc202393b99cba017d7f731b98", + "status": " M .gitea/workflows/ci.yaml\n M .gitignore\n M .task/feature-governance-reliability.md\n M AGENTS.md\n M DESIGN.md\n M Makefile\n M PROJECT.md\nM modules/documents\nM modules/ui\n M policy/adoption.md\n M policy/tool-adaptation-matrix.md\n M policy/tool-adaptation-zcode.md\n M scripts/evidence.sh\n M scripts/task.sh\n M scripts/zcode/write-guard.sh\n?? policy/governance.md\n?? policy/platform-admission.md\n?? scripts/check-worktree.py\n?? scripts/evidence.py\n?? scripts/zcode/write_guard.py\n?? tests/test_ci_credentials.py\n?? tests/test_evidence.py\n?? tests/test_task.py\n?? tests/test_write_guard.py\n", + "tracked_diff_sha256": "0da614b93dd1f62d46bd06955113fcffbd2c0931d0e78ffe22cc2d75d57e082c", + "untracked": { + "policy/governance.md": "deb69fbaf7666a09b07bf096675fd6daaa6052fd195121a6ce9c1ed1d7f1c46b", + "policy/platform-admission.md": "56a4e0b8ced66de70bac1e580dfa12dd25cd3d4bd0441b16e0fbd2d838911f34", + "scripts/check-worktree.py": "6ef68e6751563b87bdbe4828e11fc1a4f9ee715b67914c627edbc062bbcdd0db", + "scripts/evidence.py": "1347527e19e8df4e6eccdd3ae2a988cf58a16fa022b61a6144d32f0cd6966481", + "scripts/zcode/write_guard.py": "42cec254a43b26cac3bed85f2bc83890a13495ce7df160122a4629a2a4339673", + "tests/test_ci_credentials.py": "06170a18f7ee9914b7d486804ef7a1a3e70d7d144cfed5eb40a077eebbeea1f5", + "tests/test_evidence.py": "c9e0392853f0601672f1781c64ec92a496e63b0c0482df813581cd6aa34671e2", + "tests/test_task.py": "77fb40e8b57935dac74d5c2fdaa6f21b92a8fef79f7f73a9513bf53ab2eb94fb", + "tests/test_write_guard.py": "3a74d0b0cbee51654954312a57c867c1b7f936c7b021e00d358070c9df2f040a" + }, + "submodules": " cc1ad7749816afe129f81bcf0732d0ca7c789d10 deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n af199eeaa23c5b39e08b4649f2513d7572dc5274 deps/development-guidelines (heads/main)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 deps/logging-system/deps/development-guidelines (26b562d)\n cb26e4c9473f75ad015a83e3bba8137a08ff56f9 modules/backend (remotes/origin/feature/ext-1-inproc-handler)\n cc1ad7749816afe129f81bcf0732d0ca7c789d10 modules/backend/deps/agent-command (remotes/origin/feature/core-v0-1-gcc1ad77)\n ed2af3e6048928b1fd0ba6da1f9d0c1bafec82d4 modules/backend/deps/agent-command/deps/development-guidelines (remotes/origin/feature/delivery-mechanism-5-ged2af3e)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 5a57636cc0b8426a86a72bbbdd5b7f30e4fcac88 modules/backend/deps/agent-squad (remotes/origin/feature/agent-harness-p1-27-g5a57636)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 3a110a728cf520cfbbbafc0c4c75f14bf78bec3f modules/backend/deps/agent-squad/deps/agent-native (remotes/origin/feature/agent-harness-p1-27-g3a110a7)\n 65aaebca1ea69ab7d48eda51d33ef1d4eea5a0bf modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent (65aaebc)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/agent-intent/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/agent-native/deps/logging-system/deps/development-guidelines (26b562d)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/development-guidelines (26b562d)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/agent-squad/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/agent-squad/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/agent-squad/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/backend/deps/agent-system-core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n a3ee67bba269e1276c14e22c026171ecf2cf6190 modules/backend/deps/error-code-system (a3ee67b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/error-code-system/deps/development-guidelines (26b562d)\n 49b5d8be604d05e4246dffa0d10fa1c2b7ad45d7 modules/backend/deps/logging-system (49b5d8b)\n 26b562d2e34fbc560711296a309c91c74ffc55b1 modules/backend/deps/logging-system/deps/development-guidelines (26b562d)\n 0b5c7747a3165bf0d0f2219204b7b70ec3ed7645 modules/core (remotes/origin/feature/agent-harness-p3b-12-g0b5c774)\n 19e89ae10bd93306400f13a9a1f449cbc5bfaa42 modules/documents (heads/feature/governance-docs)\n 50c724460159171254242ae48e956a80f4eb9c7e modules/frontend (heads/main)\n 2cb1496320dda6e65501ac94318295f067231dd7 modules/ui (heads/feature/governance-docs)\n" + }, + "after_error": null, + "exit_code": 0, + "output": "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability'\nPYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_*.py' -v\ntest_candidate_cannot_read_checkout_credentials (test_ci_credentials.CheckoutTests.test_candidate_cannot_read_checkout_credentials) ... ok\ntest_candidate_submodule_update_commands_are_rejected (test_ci_credentials.CheckoutTests.test_candidate_submodule_update_commands_are_rejected) ... ok\ntest_external_or_unlisted_urls_are_rejected_before_component_fetch (test_ci_credentials.CheckoutTests.test_external_or_unlisted_urls_are_rejected_before_component_fetch) ... ok\ntest_inherited_template_hook_does_not_execute_with_credentials (test_ci_credentials.CheckoutTests.test_inherited_template_hook_does_not_execute_with_credentials) ... ok\ntest_nested_external_url_is_rejected_before_authenticated_fetch (test_ci_credentials.CheckoutTests.test_nested_external_url_is_rejected_before_authenticated_fetch) ... ok\ntest_recursive_gitlinks_materialize_exact_sha_without_persisting_credentials (test_ci_credentials.CheckoutTests.test_recursive_gitlinks_materialize_exact_sha_without_persisting_credentials) ... ok\ntest_redirects_do_not_forward_credential_or_fetch_unlisted_target (test_ci_credentials.CheckoutTests.test_redirects_do_not_forward_credential_or_fetch_unlisted_target) ... ok\ntest_after_snapshot_failure_keeps_command_exit_and_output (test_evidence.EvidenceTests.test_after_snapshot_failure_keeps_command_exit_and_output) ... ok\ntest_all_persisted_and_displayed_text_is_sanitized (test_evidence.EvidenceTests.test_all_persisted_and_displayed_text_is_sanitized) ... ok\ntest_before_after_heads_do_not_relabel_changed_command (test_evidence.EvidenceTests.test_before_after_heads_do_not_relabel_changed_command) ... ok\ntest_capture_does_not_overwrite_an_existing_receipt (test_evidence.EvidenceTests.test_capture_does_not_overwrite_an_existing_receipt) ... ok\ntest_capture_without_card_and_import_without_rerun (test_evidence.EvidenceTests.test_capture_without_card_and_import_without_rerun) ... ok\ntest_child_failure_code_is_preserved (test_evidence.EvidenceTests.test_child_failure_code_is_preserved) ... ok\ntest_command_line_token_arguments_are_redacted (test_evidence.EvidenceTests.test_command_line_token_arguments_are_redacted) ... ok\ntest_dirty_and_untracked_contents_have_object_digests (test_evidence.EvidenceTests.test_dirty_and_untracked_contents_have_object_digests) ... ok\ntest_failed_child_exit_survives_receipt_write_failure (test_evidence.EvidenceTests.test_failed_child_exit_survives_receipt_write_failure) ... ok\ntest_full_output_is_preserved_not_only_tail (test_evidence.EvidenceTests.test_full_output_is_preserved_not_only_tail) ... ok\ntest_import_missing_card_fails_without_losing_receipt (test_evidence.EvidenceTests.test_import_missing_card_fails_without_losing_receipt) ... ok\ntest_import_retry_is_idempotent (test_evidence.EvidenceTests.test_import_retry_is_idempotent) ... ok\ntest_invalid_field_types_and_forged_source_are_rejected (test_evidence.EvidenceTests.test_invalid_field_types_and_forged_source_are_rejected) ... ok\ntest_known_secret_environment_value_is_redacted_without_label (test_evidence.EvidenceTests.test_known_secret_environment_value_is_redacted_without_label) ... ok\ntest_linked_worktree_import_preserves_source_identity (test_evidence.EvidenceTests.test_linked_worktree_import_preserves_source_identity) ... ok\ntest_missing_card_does_not_execute (test_evidence.EvidenceTests.test_missing_card_does_not_execute) ... ok\ntest_missing_required_snapshot_fields_are_rejected (test_evidence.EvidenceTests.test_missing_required_snapshot_fields_are_rejected) ... ok\ntest_nested_cwd_records_untracked_files_from_repository_root (test_evidence.EvidenceTests.test_nested_cwd_records_untracked_files_from_repository_root) ... ok\ntest_output_digest_checked_even_if_receipt_digest_recomputed (test_evidence.EvidenceTests.test_output_digest_checked_even_if_receipt_digest_recomputed) ... ok\ntest_pipeline_failure_is_recorded_and_returned (test_evidence.EvidenceTests.test_pipeline_failure_is_recorded_and_returned) ... ok\ntest_quoted_credentials_with_spaces_are_fully_redacted (test_evidence.EvidenceTests.test_quoted_credentials_with_spaces_are_fully_redacted) ... ok\ntest_receipt_write_failure_cannot_claim_success (test_evidence.EvidenceTests.test_receipt_write_failure_cannot_claim_success) ... ok\ntest_recorder_environment_does_not_leak_into_nested_commands (test_evidence.EvidenceTests.test_recorder_environment_does_not_leak_into_nested_commands) ... ok\ntest_source_cannot_be_promoted_by_environment (test_evidence.EvidenceTests.test_source_cannot_be_promoted_by_environment) ... ok\ntest_tamper_is_rejected_before_card_append (test_evidence.EvidenceTests.test_tamper_is_rejected_before_card_append) ... ok\ntest_unrelated_repo_cannot_import (test_evidence.EvidenceTests.test_unrelated_repo_cannot_import) ... ok\ntest_validate_precheck_checks_result_and_current_base (test_evidence.EvidenceTests.test_validate_precheck_checks_result_and_current_base) ... ok\ntest_validate_precheck_rejects_successful_arbitrary_command (test_evidence.EvidenceTests.test_validate_precheck_rejects_successful_arbitrary_command) ... ok\ntest_finish_clean_worktree_has_accurate_remote_result (test_task.TaskLifecycleTests.test_finish_clean_worktree_has_accurate_remote_result) ... ok\ntest_finish_deletes_only_merged_remote_branch (test_task.TaskLifecycleTests.test_finish_deletes_only_merged_remote_branch) ... ok\ntest_finish_preserves_component_commits_off_the_pinned_head (test_task.TaskLifecycleTests.test_finish_preserves_component_commits_off_the_pinned_head) ... ok\ntest_finish_preserves_component_untracked_file (test_task.TaskLifecycleTests.test_finish_preserves_component_untracked_file) ... ok\ntest_finish_preserves_locked_worktree (test_task.TaskLifecycleTests.test_finish_preserves_locked_worktree) ... ok\ntest_finish_preserves_main_and_active_index_lock (test_task.TaskLifecycleTests.test_finish_preserves_main_and_active_index_lock) ... ok\ntest_finish_preserves_modified_untracked_and_ignored_files (test_task.TaskLifecycleTests.test_finish_preserves_modified_untracked_and_ignored_files) ... ok\ntest_finish_preserves_remote_unmerged_tip (test_task.TaskLifecycleTests.test_finish_preserves_remote_unmerged_tip) ... ok\ntest_finish_recycles_clean_submodule_without_shared_config_changes (test_task.TaskLifecycleTests.test_finish_recycles_clean_submodule_without_shared_config_changes) ... ok\ntest_finish_rejects_subdirectory_without_deleting_it (test_task.TaskLifecycleTests.test_finish_rejects_subdirectory_without_deleting_it) ... ok\ntest_finish_remote_deletion_lease_preserves_concurrent_update (test_task.TaskLifecycleTests.test_finish_remote_deletion_lease_preserves_concurrent_update) ... ok\ntest_finish_requires_writer_exit_confirmation (test_task.TaskLifecycleTests.test_finish_requires_writer_exit_confirmation) ... ok\ntest_precheck_feature_behind_requires_sync (test_task.TaskLifecycleTests.test_precheck_feature_behind_requires_sync) ... ok\ntest_precheck_rejects_unreachable_authority (test_task.TaskLifecycleTests.test_precheck_rejects_unreachable_authority) ... ok\ntest_start_imports_precheck_and_creates_four_part_commit (test_task.TaskLifecycleTests.test_start_imports_precheck_and_creates_four_part_commit) ... ok\ntest_start_offline_does_not_create_worktree (test_task.TaskLifecycleTests.test_start_offline_does_not_create_worktree) ... ok\ntest_start_rejects_receipt_when_authoritative_base_changed (test_task.TaskLifecycleTests.test_start_rejects_receipt_when_authoritative_base_changed) ... ok\ntest_start_requires_precheck_receipt_before_creating_worktree (test_task.TaskLifecycleTests.test_start_requires_precheck_receipt_before_creating_worktree) ... ok\ntest_start_stacked_branch_records_parent_and_main_baseline (test_task.TaskLifecycleTests.test_start_stacked_branch_records_parent_and_main_baseline) ... ok\ntest_sync_commit_failure_never_reports_synced (test_task.TaskLifecycleTests.test_sync_commit_failure_never_reports_synced) ... ok\ntest_sync_keeps_unchanged_component_branch (test_task.TaskLifecycleTests.test_sync_keeps_unchanged_component_branch) ... ok\ntest_sync_materializes_nested_detached_components (test_task.TaskLifecycleTests.test_sync_materializes_nested_detached_components) ... ok\ntest_sync_materializes_updated_component (test_task.TaskLifecycleTests.test_sync_materializes_updated_component) ... ok\ntest_sync_preserves_component_feature_branch (test_task.TaskLifecycleTests.test_sync_preserves_component_feature_branch) ... ok\ntest_sync_preserves_dirty_child (test_task.TaskLifecycleTests.test_sync_preserves_dirty_child) ... ok\ntest_sync_preserves_nested_feature_branch_when_parent_pin_changes (test_task.TaskLifecycleTests.test_sync_preserves_nested_feature_branch_when_parent_pin_changes) ... ok\ntest_verify_rejects_recursive_drift_even_when_root_has_local_edits (test_task.TaskLifecycleTests.test_verify_rejects_recursive_drift_even_when_root_has_local_edits) ... ok\ntest_verify_rejects_uninitialized_component (test_task.TaskLifecycleTests.test_verify_rejects_uninitialized_component) ... ok\ntest_deleting_main_symlink_to_outside_is_denied (test_write_guard.WriteGuardTests.test_deleting_main_symlink_to_outside_is_denied) ... ok\ntest_detached_and_bad_branches_are_denied (test_write_guard.WriteGuardTests.test_detached_and_bad_branches_are_denied) ... ok\ntest_every_patch_file_and_move_destination_is_checked (test_write_guard.WriteGuardTests.test_every_patch_file_and_move_destination_is_checked) ... ok\ntest_feature_missing_parent_is_allowed (test_write_guard.WriteGuardTests.test_feature_missing_parent_is_allowed) ... ok\ntest_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok\ntest_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok\ntest_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok\ntest_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok\ntest_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok\ntest_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok\ntest_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok\ntest_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok\n\n----------------------------------------------------------------------\nRan 75 tests in 12.943s\n\nOK\nmake[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-governance-reliability'\n", + "output_sha256": "de5eae6114ff15a092f38cb4e75270eb3e0a77f46827a3879ba486e3f0c874e4", + "receipt_sha256": "ccf076975e0e7b4998d614944eba68aeeee8d791baa5ad061ad090652022b581" +} +``` diff --git a/AGENTS.md b/AGENTS.md index b8165ff1..f1513951 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,27 +10,34 @@ - 颜色/圆角/字号只消费 `@ui/tokens.css` 令牌(字号走 `--fs-ui-*` 标尺);动效只用 `@ui/motion.css` 的 `mk-*` 工具类;折叠一律用 `@ui` Collapsible 原语,禁止 `
`。 - 守护测试(UI 仓与 Frontend 仓各一份 `design-guard.test.ts`)以棘轮拦截新增裸色值、 - 私有 `@keyframes` 与行内字号;改风格 = `DESIGN.md` + 令牌文件同 PR 成对修改。 + 私有 `@keyframes` 与行内字号;风格调整须在同一变更集中关联 UI 令牌 PR 与 Dev 的 + `DESIGN.md`/gitlink 组合 PR,并验证精确组件 SHA(跨仓不要求两个文件出现在同一 PR)。 - 异步状态只有一个所有者。事件按 seq 处理,过期事件丢弃。 ## 任务启动门禁(强制) -- 任务启动或恢复后,**第一次写操作前**运行 `make precheck` 并附完整输出;verdict 为 - `SHARED_WORKSPACE_BUSY / SYNC_REQUIRED` 时不得编辑。 -- 按其必读清单完成阅读,然后 `make task-start NAME=feature/` 建立独立 worktree + - feature 分支(自动记录 R0 与任务卡);任务中途不重复运行 precheck。 +- 任务启动或恢复后,**第一次项目写操作前**通过 + `RECEIPT=/tmp/<本次唯一名称>.json CMD="make precheck" make evidence` 运行一次启动检查并保存 + 完整脱敏收据(不要求已有任务卡);按输出完成必读。`SHARED_WORKSPACE_BUSY / SYNC_REQUIRED / + REMOTE_UNVERIFIED` 均不得进入写入流程;失败后排除原因再启动,不把缓存远端当最新事实。 +- 新任务运行 `make task-start NAME=feature/ PRECHECK_RECEIPT=/tmp/<本次唯一名称>.json`, + 建立独立 worktree 与任务卡并导入首次收据。恢复已有任务在原任务工作区导入收据: + `IMPORT_RECEIPT=/tmp/<本次唯一名称>.json make evidence`。任务中途不重复启动检查。 - **分支层级**:任务分支一律从权威 `origin/main` 切(`task-start` 已强制);同批互不依赖的 子任务用同一 R0 的兄弟分支。确有顺序依赖需从父 feature 分支切子分支时(collaboration.md - 允许的堆叠例外):任务卡显式记录父任务与父 PR;子分支命名 `/<父slug>-<子题>`, + 允许的堆叠例外):启动命令增加 `PARENT=feature/<父slug> PARENT_PR=<父PR链接>`, + 任务卡记录主干 R0、父分支及其 SHA、父 PR;子分支命名 `/<父slug>-<子题>`, 不嵌 SHA;合入顺序固定父→子;父合入后子分支须 `make task-sync` 并入最新 main 并把 PR base 改回 main,方可声明可合入(分支保护要求 head 含 origin/main,未同步必被拦)。 - **验收、请求审核、合入前**各运行一次 `make task-sync`;完成声明的 precheck、task-sync、 测试三段输出由 `make evidence` 写入任务卡(见「证据与验证门禁」),不手抄。 -- 合入后**在 worktree 内**运行 `make task-finish` 回收 worktree 与分支(脚本会把 - 当前目录当 worktree 回收——在主检出运行会误删主检出,已加防护);放弃未合入 - 工作用 `make task-finish ABANDON="--abandon <原因>"` 并记录原因。 +- 合入后由工作区 owner 确认所有写者退出,在**已登记任务 worktree 根目录**运行 + `TASK_WRITERS_STOPPED=1 make task-finish`。父子仓改动、未跟踪/ignored 唯一文件先保存或显式处置;删除被拒时 + 保留现场并按原因处理,禁止强删兜底。放弃未合入工作用 + `make task-finish ABANDON="--abandon <原因>"` 并记录原因;放弃不豁免现场保护。 - **任务卡闭合(防欠账)**:分支合入 main 后,其任务卡须回填"已完成"+主记录 PR 链接并 - 归档至 `.task/archive/`。自动路径:`make task-finish` 收尾触发、主检出 pull 触发 post-merge + 归档至 `.task/archive/`。`task-finish` 回收后提示所有者运行 `make task-close`,不借回收修改 + 共享主检出或推送另一任务。自动路径保留主检出 pull 触发 post-merge 钩子(实测 ff-only pull 也触发);钩子内 close 只扫主检出本地 `.task/`,远端刚合入的卡片 要等 pull 完成才能被看到,扫不到时由手动 `make task-close` 兜底。手动闭合在主检出运行→建 `feature/card-close-*` 分支+PR,任务分支内加 `FOLD=--fold` 随当前 PR 合入。 @@ -43,10 +50,13 @@ 规范依据:guidelines `standards/verification.md`「声明与证据来源」(draft.10)。 - **来源三分**:产出物(任务卡、PR、报告、review 结论)中的通过类断言须标注来源—— - `GATE`(CI/分支保护/precheck 实测)/ `REPLAY`(审阅者独立重跑)/ `SELF`(作者粘贴)。 - 合入与验收判定只认 GATE/REPLAY;SELF 仅为线索,与重验不一致时以重验为准。 + `GATE`(可核验身份及固定对象的 CI/受信执行器)/ `REPLAY`(独立审阅者的重跑记录)/ + `SELF`(作者或本地工具记录,含 precheck)。工具写入、摘要和来源标签不提升可信等级。 + 合入与验收须核验 GATE 的运行链接/身份及 SHA,或 REPLAY 的独立审阅者、命令和对象; + 本地 evidence 只生成 SELF,不能靠 `SRC` 自行升级;与重验不一致时以重验为准。 - **证据由工具写入**:命令输出进入任务卡/PR 一律经 `CMD="<命令>" make evidence` 写入 - (自动记录命令、被测 HEAD、退出码、输出 sha256 与尾部摘录并脱敏 token)。手抄、转写、 + (记录脱敏命令、执行前后对象、退出码、完整脱敏输出与摘要)。凭据只经环境或受保护文件 + 引用;命令、输出和摘要统一脱敏,摘要只证明记录完整性,不证明执行者身份。手抄、转写、 凭记忆重构的输出视为无效证据;补写输出与伪造测试同级红线。 - **完成声明三段输出**(precheck / task-sync / 测试)必须由 evidence 工具写入任务卡, PR 描述引用任务卡段落,不另手抄。测试通过的权威源是 CI(`.gitea/workflows/ci.yaml`, @@ -70,6 +80,8 @@ - 验证命令见 PROJECT.md「验证」节;完成声明的三段输出按「证据与验证门禁」节由 `make evidence` 写入任务卡,不手抄。 - 首次克隆本仓后运行 `make setup` 启用本地 pre-commit 钩子。 +- 治理脚本改动须运行 `make test-governance`:临时真实仓库覆盖正常、拒绝及异常路径;该套件 + 同时纳入 `make test`。证据、工作区和 CI 凭据边界的操作细则见 `policy/governance.md`。 ## Gitea 与 Git 访问事实(2026-09-30 实测;登记表 `policy/facts.md`) diff --git a/DESIGN.md b/DESIGN.md index 0506632a..c7ef9473 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -281,7 +281,8 @@ Primary(`--primary` 反色)、Ghost(透明,hover 出表面)、Danger ## 12. 演进规则 -改风格 = `DESIGN.md` 与 `tokens.css`(必要时 `motion.css`)**同 PR 成对修改**,附前后值对照; +改风格须在**同一变更集**中成对修改:UI 仓 PR 调整 `tokens.css`(必要时 `motion.css`), +Dev 组合 PR 更新 `DESIGN.md` 并固定对应 UI 提交;两者互链,附前后值对照并验证精确组合。 不允许只改代码不改文档。新增视觉规则先问:它属于结构、表面、交互还是状态? 拿不准时,选更安静的方案、用更强的信息层级。 diff --git a/Makefile b/Makefile index 94a08629..fb811663 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,10 @@ version: @echo "agent-system $(VERSION)" TASK := scripts/task.sh +export PRECHECK_RECEIPT RECEIPT IMPORT_RECEIPT TASK_WRITERS_STOPPED +TASK_PARENT_BRANCH ?= $(PARENT) +TASK_PARENT_PR ?= $(PARENT_PR) +export TASK_PARENT_BRANCH TASK_PARENT_PR setup: git config core.hooksPath scripts/hooks @@ -42,7 +46,8 @@ task-finish: @bash $(TASK) finish $(ABANDON) # 证据写入:执行命令并把结果行追加进当前任务卡(AGENTS.md「证据与验证门禁」)。 -# 用法:CMD="<单行命令>" make evidence [SRC=SELF|REPLAY|GATE] [TAIL=15];退出码随被执行命令。 +# 本地执行只记 SELF;RECEIPT 捕获启动收据,IMPORT_RECEIPT 导入原始收据而不重跑。 +# 用法:CMD="<单行命令>" make evidence [TAIL=15];退出码随被执行命令。 # CMD 经环境直传(不经 make 变量展开重引号,命令内可含引号/管道);SRC/TAIL 为简单词,走 make 变量。 evidence: @SRC='$(SRC)' TAIL='$(TAIL)' bash scripts/evidence.sh @@ -72,7 +77,7 @@ status: @git submodule status # 契约同步:Core 的 TS 类型镜像到前端与 UI 组件库(Core 为权威,单一来源)。 -contracts: +contracts: test-worktree cp $(CORE)/ts/types.ts $(FRONTEND)/src/contracts.ts.tmp { echo "// 从 Agent-System-Core ts/types.ts 同步(make contracts 生成;Core 为权威)。"; cat $(FRONTEND)/src/contracts.ts.tmp; } > $(FRONTEND)/src/contracts.ts rm $(FRONTEND)/src/contracts.ts.tmp @@ -128,7 +133,18 @@ plugin-new: @test -n "$(NAME)" || (echo "用法:make plugin-new NAME="; exit 2) bash scripts/plugin-new.sh "$(NAME)" -test: test-boundaries test-pins test-libs test-core test-backend test-frontend test-ui test-agentcmd +.PHONY: test test-governance test-worktree contracts + +test-governance: + PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_*.py' -v + +test-worktree: + python3 scripts/check-worktree.py verify + +# 即使 make -j,也先确认实际被测子仓与固定组合一致。 +test-boundaries test-pins test-libs test-core test-backend test-frontend test-ui test-agentcmd: | test-worktree + +test: test-worktree test-governance test-boundaries test-pins test-libs test-core test-backend test-frontend test-ui test-agentcmd # 运行演示(mock 模式):单二进制 + 示例插件 + 嵌入式存储,浏览器 http://127.0.0.1:8787 # 环境服务管理(scripts/svc.sh):deploy = 停旧 → 构建 → 启动;dev/prod 相互隔离。 diff --git a/PROJECT.md b/PROJECT.md index 15102969..2a3b79c3 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -2,5 +2,17 @@ - 用途:开发集成父仓(gitlink 权威);非分发单元。 - 组合:modules/{core,backend,frontend,ui,documents} + deps/{development-guidelines,agent-native,agent-squad,agent-intent,agent-command,error-code-system,logging-system}。 -- 验证:`make test` = test-boundaries(公开边界,另含三库之间导入配对棘轮)+ test-pins + test-libs(agent-intent、agent-native、agent-squad 各自 `GOWORK=off` 的 vet 与 test)+ test-core + test-backend + test-frontend + test-ui + test-agentcmd。`make build` 在 backend 模块内编译,replace 指向该模块自己的 `deps/`;`scripts/check-pins.sh` 要求这些 gitlink 与 Dev 顶层指针相同。未加 go.work:工作区最小版本选择会把 `golang.org/x/crypto`、`exp`、`net`、`term`、`text` 选得比 backend 单独构建更新。Releases 仓的 `scripts/build-release.sh` 递归克隆本仓固定提交后运行 `make build`,因此跟随同一套指针。 - 部署:非容器;Releases 仓从本仓固定提交导出版本。 + +## 验证 + +`make test` 先核对子模块工作树与固定指针,再运行治理脚本、公开边界、跨仓指针、库及各模块 +测试。实际目标以根 Makefile 为准。`make test-governance` 可独立检查开发门禁,使用临时 +真实仓库,不访问生产数据;操作与证据要求见 [开发门禁](policy/governance.md)。 + +`make build` 在 backend 模块内编译,replace 指向该模块自己的 `deps/`; +`scripts/check-pins.sh` 核对这些 gitlink 与 Dev 顶层指针,`make test-worktree` 另核对 +真正被测的组件工作树。两者分别验证固定组合与实际检出,不能相互替代。 + +未加 go.work:工作区最小版本选择会抬高 backend 单独构建使用的部分 `golang.org/x` 版本。 +发布消费方须从固定 Dev 提交递归物化后运行 `make build`,再核验制品与实际运行状态。 diff --git a/modules/documents b/modules/documents index a47754b4..19e89ae1 160000 --- a/modules/documents +++ b/modules/documents @@ -1 +1 @@ -Subproject commit a47754b4944ffab2f09db80d8f3272324f5ef4bf +Subproject commit 19e89ae10bd93306400f13a9a1f449cbc5bfaa42 diff --git a/modules/ui b/modules/ui index 27d5adb5..2cb14963 160000 --- a/modules/ui +++ b/modules/ui @@ -1 +1 @@ -Subproject commit 27d5adb5c679101b596fbb7701b43e946674a8e6 +Subproject commit 2cb1496320dda6e65501ac94318295f067231dd7 diff --git a/policy/adoption.md b/policy/adoption.md index b26dea49..abcdf605 100644 --- a/policy/adoption.md +++ b/policy/adoption.md @@ -10,14 +10,14 @@ | 规范/工作流 | 适用条件与范围 | 执行方式/证据 | |---|---|---| | core/baseline.md | 日常 | manual | -| workflows/delivery.md(含启动自检 §1) | 交付 | advisory(make precheck);判定输出 BUSY/SYNC 时阻断 | +| workflows/delivery.md(含启动自检 §1) | 交付 | 本地命令检查;首次收据与 BUSY/SYNC/REMOTE_UNVERIFIED 语义见 policy/governance.md | | standards/collaboration.md | 并行/同步节点 | **enforced(本地)**:pre-commit 拒绝集成分支直提与坏分支名(负例:hook-main/badbranch 拒绝输出);节点同步由 task-sync 落卡 | -| 提交格式(主题行/四段 body) | 全部提交 | **enforced(本地)**:commit-msg 拒绝 >100 列与句点结尾(负例:longsubject 拒绝输出);受保护分支经 Gitea PR 合入(本记录 3 个 PR 为正例) | -| standards/verification.md | 验收 | manual:make test(12 ok 行)+ scratch 19/19 为证 | +| 提交格式(主题行/四段 body) | 全部提交 | 主题行 enforced(local),body manual;任务工具生成四段 body,历史 PR 仅作旧版本证据 | +| standards/verification.md | 验收 | 本地 make test-governance + 组合测试;当前证据与历史 scratch 记录分开 | | standards/multi-repo.md | 跨仓/gitlink | manual;受控历史迁移通道已于 2026-09-30 行使一次(documents/records/2026-09-30-history-migration.md) | | 工作区回收 | 交付终点 | advisory(task-finish);负例:合入前 finish 拒绝输出 | | standards/tool-adaptation.md(draft.8) | 为工具创建规则/钩子实例 | 本组合 CLI/git 级实例已登记:policy/tool-adaptation-matrix.md(A1–A7 逐行定级+正负例证据) | -| 同上(zcode 实例) | zcode 客户端写路径 | policy/tool-adaptation-zcode.md:PreToolUse 写守卫(.zcode/config.json + scripts/zcode/write-guard.sh),A1/A3 落笔前 enforced | +| 同上(zcode 实例) | zcode 客户端写路径 | policy/tool-adaptation-zcode.md:PreToolUse 写守卫;脚本负例与客户端接线分开,客户端 pending-live | | core/baseline.md「规范层级」+ documentation.md「记忆分层/机制化」(draft.9) | 层级效力、记忆权威、经验机制化 | manual:本仓 policy/ 仅收紧不放宽;记忆四层按条文执行(任务卡=任务记忆,documents/records=项目记忆,Dev 仓 AGENTS.md 指针已就位) | ## 项目差异 @@ -26,4 +26,5 @@ |---|---|---|---| | baseline 硬边界 1(授权范围) | 高后果双钥匙:改写历史/解除分支保护/删归档/发布——Agent 只准备,用户执行(AGENTS.md 条文) | 单人+Agent 场景下消除单点;风险=用户不在场时高后果操作排队 | weichen;首次触发时复查 | -- Gitea 分支保护已启用(2026-09-30):11 个组合仓 main 禁直推、拒绝陈旧候选;负例:直推被 pre-receive hook declined。平台层 A3 升级为 enforced。 +- 历史记录(2026-09-30)报告 11 个组合仓 main 禁直推、拒绝陈旧候选;当前平台能力须独立重验,本地脚本测试不替代远端证据。 +- 当前规范内容以 gitlink 及其 VERSION 为准。治理可靠性修复的命令与证据约定见 [开发门禁](governance.md),本地与客户端/平台能力分别登记;本轮不改变分支保护、发布授权或通用库版本。 diff --git a/policy/governance.md b/policy/governance.md new file mode 100644 index 00000000..e9d88252 --- /dev/null +++ b/policy/governance.md @@ -0,0 +1,81 @@ +# 开发门禁的执行与证据 + +本文件细化根 AGENTS.md 的启动、验证和回收规则。通用语义以固定的 +`deps/development-guidelines` 为准;门禁实现及回归用例随本仓版本管理。 + +## 启动与恢复 + +从当前检出运行一次启动检查,使用每次唯一、仓外的绝对收据路径: + +```bash +RECEIPT=/tmp/my-task-precheck.json CMD="make precheck" make evidence +make task-start NAME=feature/my-task PRECHECK_RECEIPT=/tmp/my-task-precheck.json +``` + +第一步先捕获原始执行,再创建任务卡;收据保存完整脱敏输出、退出码、执行位置和对象。 +第二步核验收据与当前仓库/基线,再创建工作区并导入;不靠事后重跑冒充首次检查。 +同一收据不可覆盖。首次检查失败时处理原因,使用新路径重新启动。主干在两步间前进时, +重新确认新的启动基线,不能把旧收据移花接木到新对象。 + +恢复已有任务仍先捕获本次启动检查,再在原任务工作区用 +`IMPORT_RECEIPT=/tmp/my-task-resume.json make evidence` 导入;不重新建分支。 +工作区忙或落后时先由 owner 保存现场并同步;远端不可达时只继续不依赖新鲜基线的阅读和分析。 + +堆叠任务使用 `PARENT=feature/parent PARENT_PR=<父PR链接>`。父分支须包含本批主干 R0; +卡片同时记录父分支、父 SHA 和父 PR。父先合入,子同步最新 main 并改回 main 为 PR base。 + +## 被测对象 + +`make task-sync` 在父仓合并后按固定 gitlink 物化未修改组件,并核对实际组件工作树。 +组件 feature 分支若与新指针不同,先保留其分支,交给 owner 显式同步并更新组合; +不能用自动 checkout 丢弃组件分支现场。任何失败都保留现场并阻止同步成功声明。 + +`make test-worktree` 核对递归子模块:未初始化、冲突、指针漂移及子仓未保存改动均拒绝。 +它是组合测试和构建的前置检查。父仓未提交改动可以局部测试,但证据必须记录实际对象状态; +正式验收使用已提交的固定对象,不能仅写一个与工作树不同的 HEAD。 + +## 本地记录与独立证据 + +本地 `make evidence` 一律生成 SELF。`SRC=GATE/REPLAY` 不可把本地执行升级为权威来源。 +GATE 引用受信平台的运行链接、执行身份、完整 SHA 和结果;REPLAY 引用独立审阅者的 +环境、命令、对象和结果。审核者核验这些来源,不能只读作者填入的标签。 + +收据中的摘要检测损坏,不是签名或身份凭据。保存的是完整脱敏输出及其 SHA-256; +命令含敏感值时展示脱敏形式,并记录原命令摘要。执行前后对象分别记录;命令改变 HEAD +时不把执行前 SHA 冒充执行后对象。不能读取执行后状态时明确记录缺项。 + +凭据从环境或受保护文件引用,避免出现在命令字面量。脱敏是补充防护,不是允许输出秘密。 +执行脚本不通过 shell tracing 暴露环境;管道任何一段失败应传回非零退出码。 +工具写入失败本身也必须报错,不能只保留一个看似成功的退出状态。 + +## 回收 + +仅在登记的任务 worktree 根目录回收。owner 先确认全部写者已退出、任务成果与证据已保存, +以 `TASK_WRITERS_STOPPED=1 make task-finish` 明确确认; +脚本检查主检出/子目录误用、Git 操作和锁、父子仓修改及未跟踪/ignored 文件。 +可再生缓存由 owner 按已识别路径单独处置;无法确认用途的文件保留。 + +任务合入需以本次成功获取的权威目标验证。合入提交不能证明未提交文件已经落盘。 +Git 对含子模块的工作区需要显式删除选项时,只有全部现场检查通过才允许该路径; +它不豁免任何脏状态、忽略文件、独有提交/引用或锁检查。普通删除失败不转入强删兜底。 +保留无法确认安全的目录和分支,输出下一步处理依据。 +远端分支删除与本地回收分别报告,失败不能声明两者全部完成。 +回收后由所有者执行 `make task-close` 闭合卡片;回收命令不顺带改写共享主检出。 + +## CI 凭据边界 + +候选代码执行前结束有凭据的源码获取阶段。获取只访问受信主机及登记的组件仓, +不根据候选提供的任意 URL 携带 token;认证配置限单次进程,不写全局 Git 配置或 remote URL。 +测试/构建阶段检查配置中不存在凭据,并使用不含 checkout token 的环境。 + +仓内测试只能证明脚本边界。runner 隔离、token 权限、工作流来源和平台分支保护 +由平台维护者验证;没有对应 GATE/REPLAY 时标未验证,不由本地测试推断。 + +## 持续回归 + +`make test-governance` 在临时真实 Git 仓库中覆盖回收、组件同步、离线启动、收据捕获/导入、 +管道失败、假凭据脱敏、写路径及 CI 凭据隔离;临时 remote 均为本地路径,禁止使用真实秘密。 +它纳入 `make test`,新机制必须有正常、拒绝和故障分支。历史临时测试记录仅作定位线索。 + +普通文档核对真实入口和相对链接;规范升级检查条文、命令、模板与能力矩阵是否一致。 +机制未变可按固定版本复用证据,变更的机制重新运行对应负例。 diff --git a/policy/platform-admission.md b/policy/platform-admission.md new file mode 100644 index 00000000..38ec5d0a --- /dev/null +++ b/policy/platform-admission.md @@ -0,0 +1,32 @@ +# 平台准入的所有者操作 + +状态:待所有者执行;本次仅准备,不修改远端分支保护。 +依据:根 AGENTS.md「高后果操作双钥匙」要求分支保护变更由用户确认并亲手执行。 + +## 已核实的差距 + +2026-10-05 读取 Dev 的 main 分支保护 API,观察到禁直推与陈旧分支拦截开启, +必需状态检查关闭、批准数为 0。读取历史 PR head 的状态 API,实际检查名为 +`ci / test (pull_request)`。完整命令与输出由 evidence 工具记录在 +`.task/feature-governance-reliability.md`,来源 SELF(只读配置观察,不是门禁负例)。 +执行以下步骤前再次读取配置,不能把本记录当长期有效的平台事实。 + +## 所有者执行 + +1. 在 Gitea 的 Agent-System-Dev 仓库设置中打开 main 的分支保护,先保留当前配置截图或导出。 +2. 确认本次修复 PR 的固定 head 已实际运行 CI,并在提交状态中核对完整检查名。 +3. 启用必需状态检查,选择实际的 `ci / test (pull_request)`;保持禁直推及陈旧分支拦截。 + 不用仅在合入后运行的 `ci / test (push)` 替代 PR 检查。 +4. 批准数暂不随此修复自动调整。若另行启用非作者批准,先确认有可用且符合权限要求的 + 独立审核账号;不能把切换 Agent 角色当独立账号,也不能盲设阈值使单账号仓库无法交付。 +5. 在隔离的测试 PR 上验证:无检查、检查失败和陈旧候选均不可合入;合法且检查通过的 + PR 可进入所有者审核。记录 head/base、运行链接及实际拒绝结果,标 GATE/REPLAY。 + +任一判据未满足时暂停合入并保留测试对象。配置调整失败由所有者对照保存的配置恢复; +不把“配置 API 返回成功”写成“门禁已有效”。其他组合仓分别核验,不外推 Dev 的结果。 + +## CI 信任前提 + +本次 workflow 修复减少 checkout token 对候选代码的暴露;仍须由平台维护者核验 +workflow 来源策略、runner 隔离/销毁及 token 最小权限。仅要求一个可由候选自行修改的 +绿色检查,不能证明测试定义或执行身份可信。平台验证结果回填任务主记录与能力矩阵。 diff --git a/policy/tool-adaptation-matrix.md b/policy/tool-adaptation-matrix.md index 9395c581..23cbd76a 100644 --- a/policy/tool-adaptation-matrix.md +++ b/policy/tool-adaptation-matrix.md @@ -1,33 +1,28 @@ -# CLI/git 级门禁机制实例——能力矩阵 +# CLI/git 门禁能力矩阵 -- 适配对象:任何经终端操作本仓的执行者(CLI Agent、IDE 内嵌终端);机制为工具无关的 shell + git hooks -- 依据:Development-Guidelines 0.1.0-draft.8(c9ab623)standards/tool-adaptation.md + policy/adoption.md -- 状态:verified(正负例见下);owner:weichen -- 实例位置:scripts/task.sh(precheck/start/sync/finish)、scripts/hooks/pre-commit、scripts/hooks/commit-msg、Makefile 目标、AGENTS.md 常驻条文 -- 单一权威声明:本实例是上述规范的机制实现,语义以库与项目入口为准 +适配对象:通过 shell/git 操作本仓的执行者。要求来自固定的 Development-Guidelines +`standards/tool-adaptation.md` 与根 AGENTS.md;本表登记机制范围,不复制另一套规则。 +当前修订为治理可靠性修复候选;实际验证记录见 `.task/feature-governance-reliability.md` +及其关联 PR。历史 19/19 临时测试不代替本次回归。 -## 能力矩阵 - -| 要求 | 等级 | 机制(执行时机) | 正例证据 | 负例证据 | 备注 | -|---|---|---|---|---|---| -| A1 启动门禁 | advisory(verdict 纪律)+ enforced(start 拒绝) | make precheck 任务首次写前;task-start 拒绝脏树(脚本退出码) | Dev PR#1 验证记录 case1 READY | 同套件 case2/4:BUSY exit 3、start-dirty exit 1;2026-09-30 实战拦截 data/ 脏树 | 「不得编辑」属执行者纪律,如实记 advisory | -| A2 节点同步 | advisory | make task-sync(验收/审核/合入前),落卡三字段 | PR#1/2/3 均经 sync | 实战两次拒绝脏树 sync(exit 1,不隐式 stash) | MUST 属流程义务,机制为建议级 | -| A3 集成分支保护 | enforced(local)+enforced(平台) | pre-commit 拒绝 main 直提;Gitea 分支保护:11 组合仓 main 禁直推+拒绝陈旧候选(2026-09-30 起) | 13+ PR 全部经平台合入 | 套件 hook-main-refused exit 1;平台负例:直推 Logging-System main 被 pre-receive hook declined | | -| A4 提交与任务记录格式 | enforced(local)(主题行)+manual(body 四段) | commit-msg 钩子查宽度/句点 | 本批提交全部通过 | 套件 hook-longsubject-refused exit 1 | body 四段结构无机器校验,如实 manual | -| A5 工作区回收 | advisory + enforced(未合入拒绝) | task-finish(合入核对→deinit→回收);abandon 双确认 | PR#3 后 worktree/分支回收 | 套件 finish-before-merge exit 1、abandon 需 FORCE_ABANDON | | -| A6 秘密边界 | manual | Gitea token 从 ~/.secrets/gitea 读取;会话/日志无明文 | 全程 API 自动化无泄漏 | 无构造负例(纪律项) | 凭证文件模式已验证 | -| A7 证据落盘 | manual | 完成声明附 precheck/sync/测试三段输出 | 各 PR body 携带 | 无构造负例 | | - -## 降级与失败行为 - -| 机制 | 依赖缺失时行为 | 对应等级调整 | 解锁路径 | +| 要求 | 执行能力及范围 | 机制 | 复验入口 | |---|---|---|---| -| task.sh(bash+python3) | 脚本报错退出(fail-closed) | 不变 | 修复解释器后重跑 | -| commit-msg 宽度计算(python3) | 缺 python3 时宽度为空使比较失败→拒绝(fail-closed,报错信息可读性差) | 不变 | 安装 python3;改进报错文案待办 | -| Gitea API(网络/凭证) | 拒绝并打印 http 码(fail-closed) | 合入退化为人工网页操作 | 修凭证/网络 | +| A1 启动 | 本地命令阻断;执行者首次调用仍属 manual | precheck 区分忙/落后/远端未核实;start 验证首次收据 | `tests/test_task.py`、`tests/test_evidence.py` | +| A2 同步 | 本地命令阻断;最终准入由平台承担 | 合并父仓、保护组件分支、物化固定指针、递归核对 | `tests/test_task.py`、`make test-worktree` | +| A3 集成分支 | 本地提交闸;平台状态须独立核验 | pre-commit 拒绝 main/master 与坏分支名 | 本地 hook 负例;远端缺 CI/陈旧候选/直推等负例另验 | +| A4 提交格式 | 主题行 enforced(local),body manual | commit-msg 校验宽度/句点;任务工具生成四段 body | `tests/test_task.py`;人工核对 PR/卡片同源 | +| A5 回收 | 本地命令检查;写者退出由 owner 确认 | 登记根目录、锁/操作状态、父子仓与唯一文件、合入核验 | `tests/test_task.py` | +| A6 秘密 | 本地脱敏及 CI 获取边界;权限与宿主隔离另验 | 命令/输出/摘要脱敏,凭据不持久写 Git 配置 | `tests/test_evidence.py`、`tests/test_ci_credentials.py` | +| A7 证据 | 本地 SELF 收据;外部来源独立核验 | 首次捕获、完整脱敏输出/摘要、对象记录、原样导入 | `tests/test_evidence.py`;CI/审阅链接另核 | -## 验证记录 +## 失败处理 -- 正负例:2026-09-30 临时真实仓套件 19/19 PASS(precheck 三态、start/hook/sync/finish/abandon 拒绝与放行);执行者 weichen;证据摘录见 Dev PR#1 描述与 .task 卡 -- 实战补充:data/ 脏树拦截、task-finish 子模块回收缺陷(PR#4/#6 两击修复后实战回收通过)、子仓未初始化盲区修复(本 PR) -- 证据复用:guidelines 升级到 draft.8 仅新增专题,A 表未变,本矩阵行级证据沿用(verification.md 证据复用条款) +网络不可达、引用缺失、必要解释器不可用均显式失败,不能给 READY 或同步成功。 +命令非零、管道失败及证据写入失败返回非零;保留已有收据,不覆盖历史结果。 +工作区回收被拒时保留现场;本地及远端回收分开报告。具体操作见 [开发门禁](governance.md)。 + +## 平台与历史证据 + +2026-09-30 记录曾报告 11 个组合仓 main 已启用禁直推与陈旧候选保护;这是历史结论, +本次代码/文档修复不据此认定当前远端准入有效。正式审核仍须核验当前配置和匹配负例, +与本地回归分开记录。机制升级只复用未变化范围的证据。 diff --git a/policy/tool-adaptation-zcode.md b/policy/tool-adaptation-zcode.md index 38b20e18..246cc928 100644 --- a/policy/tool-adaptation-zcode.md +++ b/policy/tool-adaptation-zcode.md @@ -1,33 +1,22 @@ -# zcode 客户端机制实例——能力矩阵 +# zcode 写路径门禁能力矩阵 -- 适配对象:zcode CLI(工作区级配置) -- 依据:Development-Guidelines 0.1.0-draft.8(c9ab623)standards/tool-adaptation.md + policy/adoption.md -- 状态:verified(脚本级正负例 8/8;客户端实弹拦截待下个会话日志确认,见备注) -- 实例位置:.zcode/config.json(PreToolUse 钩子)、scripts/zcode/write-guard.sh、AGENTS.md(常驻条文 = zcode 的 rules 层) -- 单一权威声明:本实例是上述规范的机制实现,语义以库与项目入口为准 +适配对象:`.zcode/config.json` 登记的 Edit/Write/ApplyPatch PreToolUse 钩子。 +要求来自 Development-Guidelines `standards/tool-adaptation.md` 与根 AGENTS.md; +实现为 `scripts/zcode/write-guard.sh` 及其 Python 解析器。 -## 能力矩阵 +| 要求 | 范围与等级 | 验证与限制 | +|---|---|---| +| A1/A3 写入前检查 | 脚本对 main/master、detached HEAD、非法任务分支拒绝 | `tests/test_write_guard.py` 覆盖现有/新目录、相对路径、软链接、多文件补丁与移动目标 | +| A1 启动检查 | manual | 写路径判断不证明 precheck 已执行,仍须首次收据 | +| A2/A4/A5/A6/A7 | 复用 CLI/git 对应机制 | 见 [CLI/git 矩阵](tool-adaptation-matrix.md),不另行声明接线 | -| 要求 | 等级 | 机制(执行时机) | 正例证据 | 负例证据 | 备注 | -|---|---|---|---|---|---| -| A1 启动门禁 | enforced(写路径)+ advisory(检测纪律) | PreToolUse 拦 Edit/Write/ApplyPatch:目标仓在集成分支、detached HEAD、坏分支名时 exit 2 阻断(落笔前,git 钩子只拦提交时) | 脚本套件 P1–P4 放行 | N1 main 拦截、N2 detached 子仓拦截、N3 坏分支名拦截(均 exit 2 带指引) | detached 拦截正是多租户事故形态;检测/verdict 仍靠 precheck 纪律 | -| A2 节点同步 | advisory | 无对应 zcode 事件;走 task-sync 纪律 | Dev PR#1–6 均经 sync | 实战脏树拒绝 | 不虚报 | -| A3 集成分支保护 | enforced(local) 双层 | zcode 写路径(落笔前)+ git pre-commit(提交时);平台分支保护未配置 | 12+ PR 全走平台 | N1 + 套件 hook-main-refused | 平台层配置后升级 | -| A4 提交与任务记录格式 | enforced(local) 主题行 + manual body | git commit-msg 钩子(对 zcode 的 Bash git commit 同样生效) | 本批提交 | 套件 hook-longsubject-refused | body 四段无机器校验 | -| A5 工作区回收 | advisory | task-finish(rm+prune 兜底) | PR#5/#6 后实战回收 | finish-before-merge 拒绝 | | -| A6 秘密边界 | manual | token 自 ~/.secrets/gitea;守卫与配置无凭证 | 全程无明文泄漏 | 无构造负例 | 纪律项 | -| A7 证据落盘 | manual | 完成声明附三段输出 | 各 PR body | 无构造负例 | | +写请求输入无法解析、匹配工具缺少可识别路径、必要解释器缺失时显式拒绝并给出原因。 +仓内新目录从最近已存在祖先定位,软链接按实际目标判断;补丁逐个检查全部目标。 +识别为仓外路径时显式报告该钩子不管辖;该结果不授予仓外写权限,仍由工具授权策略决定。 -## 降级与失败行为 +## 客户端接线状态 -| 机制 | 依赖缺失时行为 | 对应等级调整 | 解锁路径 | -|---|---|---|---| -| write-guard 输入解析(python3) | 缺 python3 或 stdin 无路径/垃圾 → 放行(fail-open) | A1 写路径降级 manual | 装 python3;提交时仍有 git 钩子兜底,构成第二道 | -| write-guard(bash/git) | 脚本报错非 0/2 → 客户端记为错误事件 | 同上 | 修复后重跑套件 | -| zcode 钩子运行器 | 配置文件钩子须 hooks.enabled=true(已设) | A1 写路径整体失效 | 客户端设置确认运行器激活 | - -## 验证记录 - -- 2026-09-30 脚本级套件 8/8 PASS(N1–N3 拦截、P1–P5 放行与配置合法),执行者 weichen,输出留存任务卡 -- 客户端内实弹拦截:pending-live——两次无副作用探针(配置创建当期会话)均未被拦,ZCode 日志无 hook 执行记录,证实钩子配置随会话启动加载;本会话早于配置存在,属预期。下个会话首次写入即自然验证 -- 证据复用:A2–A7 行沿用 CLI/git 实例矩阵证据(机制未变) +脚本级正负例与客户端实际拦截分开。原记录中的客户端状态为 pending-live; +本次本地测试不替代真实 zcode 会话中 hook 加载及拒绝路径证据。 +在客户端实测前,端到端能力仍标未验证,不能仅凭脚本 exit 2 登记客户端 enforced。 +已有 git 提交钩子和平台准入分别承担各自范围,不能证明落笔前检查已生效。 diff --git a/scripts/check-worktree.py b/scripts/check-worktree.py new file mode 100644 index 00000000..283bf86c --- /dev/null +++ b/scripts/check-worktree.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Fail closed before task synchronization, verification, or worktree disposal.""" +import os +from pathlib import Path +import subprocess +import sys + + +def git(repo, *args): + result = subprocess.run(["git", "-C", str(repo), *args], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if result.returncode: + raise RuntimeError(f"Git check failed in {repo}: {' '.join(args)}") + return result.stdout + + +def text(repo, *args): + return os.fsdecode(git(repo, *args)).strip() + + +def check_operations(repo): + for marker in ("index.lock", "HEAD.lock", "MERGE_HEAD", "CHERRY_PICK_HEAD", + "REVERT_HEAD", "rebase-merge", "rebase-apply", "BISECT_LOG"): + path = Path(text(repo, "rev-parse", "--path-format=absolute", "--git-path", marker)) + if path.exists(): + raise RuntimeError(f"active Git operation/lock in {repo}: {marker}; owner must finish it") + + +def check_clean(repo, ignored=False, allow_gitlink_drift=False): + args = ["status", "--porcelain=v1", "--untracked-files=all", + "--ignore-submodules=all" if allow_gitlink_drift else "--ignore-submodules=none"] + if ignored: + args.append("--ignored=matching") + status = git(repo, *args) + if status: + raise RuntimeError(f"unsaved, untracked{', or ignored' if ignored else ''} files in {repo}; preserve them before continuing") + + +def submodules(repo): + for item in git(repo, "ls-files", "--stage", "-z").split(b"\0"): + if not item: + continue + meta, path = item.split(b"\t", 1) + mode, sha, stage = meta.split() + if stage != b"0": + raise RuntimeError(f"unmerged index in {repo}; resolve conflicts first") + if mode == b"160000": + yield repo / os.fsdecode(path), sha.decode("ascii") + + +def check_children(repo, mode): + for child, expected in submodules(repo): + if child.is_symlink(): + raise RuntimeError(f"submodule path is a symbolic link: {child}") + if not (child / ".git").exists(): + if mode in ("verify", "after-sync", "finish"): + raise RuntimeError(f"uninitialized submodule: {child}; initialize and inspect it first") + continue + actual = text(child, "rev-parse", "HEAD") + check_operations(child) + check_clean(child, ignored=(mode == "finish"), allow_gitlink_drift=(mode == "before-update")) + branch = text(child, "branch", "--show-current") + if mode == "before-update" and branch and actual != expected: + raise RuntimeError(f"submodule {child} owns branch {branch}; keep that work and explicitly reconcile pin {expected} before retrying task-sync") + if mode in ("verify", "after-sync", "finish") and actual != expected: + raise RuntimeError(f"submodule SHA drift: {child}: expected {expected}, actual {actual}") + if mode == "finish" and git(child, "rev-list", "--all", "--reflog", "--not", expected).strip(): + raise RuntimeError(f"submodule has commits outside its pinned history: {child}; preserve branches, stash and reflogs before removal") + check_children(child, mode) + + +def materialize(repo): + """Check out one level, then inspect the newly exposed nested pins before moving them.""" + for child, expected in submodules(repo): + check_children(repo, "before-update") + result = subprocess.run(["git", "-C", str(repo), "submodule", "update", "--init", + "--checkout", "--", str(child.relative_to(repo))]) + if result.returncode: + raise RuntimeError(f"component materialization failed: {child}; preserve and reconcile before retrying") + materialize(child) + + +def check_finish_root(repo): + if Path.cwd().resolve() != repo: + raise RuntimeError("task-finish must run at the registered worktree root, not a subdirectory") + records = git(repo, "worktree", "list", "--porcelain", "-z").split(b"\0\0") + parsed = [record.split(b"\0") for record in records if record] + if not parsed or not parsed[0][0].startswith(b"worktree "): + raise RuntimeError("cannot identify the main checkout") + main = Path(os.fsdecode(parsed[0][0][9:])).resolve() + if main == repo: + raise RuntimeError("task-finish must run in a linked task worktree, never the main checkout") + matches = [r for r in parsed if r[0].startswith(b"worktree ") + and Path(os.fsdecode(r[0][9:])).resolve() == repo] + if len(matches) != 1: + raise RuntimeError("worktree is not registered exactly once; preserve it for owner inspection") + if any(field.startswith((b"locked", b"prunable", b"bare", b"detached")) for field in matches[0][1:]): + raise RuntimeError("locked, detached, bare, or prunable worktree must be preserved") + common = Path(text(repo, "rev-parse", "--path-format=absolute", "--git-common-dir")) + if any(common.rglob("*.lock")): + raise RuntimeError("Git lock exists in shared repository metadata; wait for its owner") + + +def main(): + mode = sys.argv[1] if len(sys.argv) > 1 else "verify" + if mode not in ("verify", "before-sync", "before-update", "materialize", "after-sync", "finish"): + raise RuntimeError("usage: check-worktree.py [verify|before-sync|before-update|materialize|after-sync|finish]") + repo = Path(text(Path.cwd(), "rev-parse", "--show-toplevel")).resolve() + check_operations(repo) + if mode == "finish": + check_finish_root(repo) + if mode in ("before-sync", "after-sync", "finish"): + check_clean(repo, ignored=(mode == "finish")) + if mode == "materialize": + materialize(repo) + else: + check_children(repo, mode) + print(f"[worktree] {mode}: recursive component checks passed") + + +if __name__ == "__main__": + try: + main() + except RuntimeError as exc: + print(f"ERROR: {exc}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/evidence.py b/scripts/evidence.py new file mode 100644 index 00000000..395d0d1a --- /dev/null +++ b/scripts/evidence.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +"""Record local command evidence without upgrading its trust beyond SELF. + +Receipts preserve sanitized full output and the exact command when it contains no +credentials. A hash of the original command remains when redaction is necessary. +Checksums detect accidental modification, not forgery by a local file's author. +""" +import datetime +import fcntl +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import sys + + +SCHEMA = 1 +SECRET_NAME = re.compile(r"token|password|passwd|secret|api[_-]?key|credential|private[_-]?key", re.I) +SHA256 = re.compile(r"[0-9a-f]{64}\Z") +GIT_SHA = re.compile(r"(?:[0-9a-f]{40}|[0-9a-f]{64})\Z") + + +class EvidenceError(Exception): + pass + + +def digest(value): + return hashlib.sha256(value.encode() if isinstance(value, str) else value).hexdigest() + + +def canonical(value): + return json.dumps(value, sort_keys=True, ensure_ascii=False, separators=(",", ":")) + + +def sanitize(value): + """Redact known secret values and conventional credential syntax everywhere.""" + for name, secret in sorted(os.environ.items(), key=lambda item: -len(item[1])): + if SECRET_NAME.search(name) and len(secret) >= 4: + value = value.replace(secret, "") + value = re.sub(r"-----BEGIN [^-\r\n]*PRIVATE KEY-----.*?-----END [^-\r\n]*PRIVATE KEY-----", + "", value, flags=re.S) + value = re.sub(r"(?i)(\bAuthorization[ \t]*:[ \t]*(?:Bearer|token|Basic)[ \t]+)[^\s'\";]+", + r"\1", value) + value = re.sub(r"(?i)\b(https?|ssh)://[^\s/@:]+:[^\s/@]+@", r"\1://@", value) + credential = r"[\w-]*(?:token|password|passwd|secret|api[_-]?key|credential)[\w-]*" + prefix = r"(?:\b" + credential + r"['\"]?[ \t]*[=:][ \t]*|--" + credential + r"[ \t]+)" + value = re.sub(r"(?i)(" + prefix + r")(['\"])(.*?)\2", + r"\1\2\2", value, flags=re.S) + value = re.sub(r"(?i)(--" + credential + r"[ \t]+)(?!)[^\s'\";]+", + r"\1", value) + value = re.sub( + r"(?i)(\b[\w-]*(?:token|password|passwd|secret|api[_-]?key|credential)[\w-]*" + r"['\"]?[ \t]*[=:][ \t]*['\"]?)(?!)[^\s'\"&;,}]+", + r"\1", value) + return value + + +def sanitize_tree(value): + if isinstance(value, str): + return sanitize(value) + if isinstance(value, list): + return [sanitize_tree(item) for item in value] + if isinstance(value, dict): + return {sanitize(key): sanitize_tree(item) for key, item in value.items()} + return value + + +def git(*args, required=True): + result = subprocess.run(["git", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode and required: + raise EvidenceError("Git object could not be recorded: " + " ".join(args[:2])) + return result.stdout if result.returncode == 0 else b"" + + +def git_text(*args, **kwargs): + return git(*args, **kwargs).decode("utf-8", "replace").strip() + + +def common_dir(): + return str(Path(git_text("rev-parse", "--git-common-dir")).resolve()) + + +def snapshot(): + untracked = {} + root = Path(git_text("rev-parse", "--show-toplevel")) + for raw in git("-C", str(root), "ls-files", "--full-name", "--others", "--exclude-standard", "-z").split(b"\0"): + if not raw: + continue + name = raw.decode("utf-8", "replace") + path = root / name + if path.is_symlink(): + untracked[name] = digest(os.fsencode(os.readlink(path))) + elif path.is_file(): + with path.open("rb") as stream: + untracked[name] = hashlib.file_digest(stream, "sha256").hexdigest() + else: + untracked[name] = None + target = os.environ.get("TASK_TARGET_BRANCH", "main") + return { + "head": git_text("rev-parse", "HEAD"), + "branch": git_text("rev-parse", "--abbrev-ref", "HEAD"), + "target_branch": target, + "target_head": git_text("rev-parse", "--verify", "refs/remotes/origin/" + target, + required=False) or None, + "status": git("status", "--porcelain=v1", "--untracked-files=all").decode("utf-8", "replace"), + "tracked_diff_sha256": digest(git("diff", "--binary", "HEAD", "--")), + "untracked": untracked, + "submodules": git("submodule", "status", "--recursive").decode("utf-8", "replace"), + } + + +def create_receipt(command, output, exit_code, before, after, *, timestamp=None, + cwd=None, git_common_dir=None, after_error=None, capture_note=None): + """Package an actual captured run; callers must not reconstruct missing output.""" + receipt = sanitize_tree({ + "schema": SCHEMA, "source": "SELF", + "timestamp": timestamp or datetime.datetime.now(datetime.timezone.utc).isoformat(), + "command": command, "command_sha256": digest(command), + "cwd": cwd or str(Path.cwd().resolve()), + "common_dir": git_common_dir or common_dir(), + "before": before, "after": after, "after_error": after_error, + "exit_code": exit_code, "output": output, + }) + if capture_note is not None: + receipt["capture_note"] = sanitize(capture_note) + receipt["output_sha256"] = digest(receipt["output"]) + receipt["receipt_sha256"] = digest(canonical(receipt)) + return receipt + + +def validate_snapshot(value): + if not isinstance(value, dict): + raise EvidenceError("Invalid receipt object snapshot") + expected = {"head", "branch", "target_branch", "target_head", "status", + "tracked_diff_sha256", "submodules", "untracked"} + if set(value) != expected: + raise EvidenceError("Invalid receipt object fields") + for key in ("head", "branch", "target_branch", "status", "tracked_diff_sha256", "submodules"): + if not isinstance(value.get(key), str): + raise EvidenceError("Invalid receipt object field: " + key) + if not GIT_SHA.fullmatch(value["head"]) or not SHA256.fullmatch(value["tracked_diff_sha256"]): + raise EvidenceError("Invalid receipt object digest") + if value.get("target_head") is not None and ( + not isinstance(value["target_head"], str) or not GIT_SHA.fullmatch(value["target_head"])): + raise EvidenceError("Invalid receipt target identity") + if not isinstance(value.get("untracked"), dict): + raise EvidenceError("Invalid receipt untracked object") + for name, checksum in value["untracked"].items(): + if not isinstance(name, str) or (checksum is not None and ( + not isinstance(checksum, str) or not SHA256.fullmatch(checksum))): + raise EvidenceError("Invalid receipt untracked digest") + + +def read_receipt(filename): + if not Path(filename).is_absolute(): + raise EvidenceError("Receipt path must be absolute") + value = json.loads(Path(filename).read_text()) + if not isinstance(value, dict) or type(value.get("schema")) is not int or value["schema"] != SCHEMA: + raise EvidenceError("Unsupported receipt schema") + expected = {"schema", "source", "timestamp", "command", "command_sha256", "cwd", "common_dir", + "before", "after", "after_error", "exit_code", "output", "output_sha256", "receipt_sha256"} + if set(value) not in (expected, expected | {"capture_note"}): + raise EvidenceError("Invalid receipt fields") + for key in ("source", "timestamp", "command", "command_sha256", "cwd", "common_dir", + "output", "output_sha256", "receipt_sha256"): + if not isinstance(value.get(key), str): + raise EvidenceError("Invalid receipt field: " + key) + if value["source"] != "SELF" or type(value.get("exit_code")) is not int or not 0 <= value["exit_code"] <= 255: + raise EvidenceError("Invalid receipt source or exit code") + datetime.datetime.fromisoformat(value["timestamp"].replace("Z", "+00:00")) + for key in ("cwd", "common_dir"): + if not Path(value[key]).is_absolute(): + raise EvidenceError("Receipt identity paths must be absolute") + for key in ("command_sha256", "output_sha256", "receipt_sha256"): + if not SHA256.fullmatch(value[key]): + raise EvidenceError("Invalid receipt digest: " + key) + validate_snapshot(value.get("before")) + if value.get("after") is not None: + validate_snapshot(value["after"]) + elif not isinstance(value.get("after_error"), str) or not value["after_error"]: + raise EvidenceError("Missing after object or capture error") + if value.get("after_error") is not None and not isinstance(value["after_error"], str): + raise EvidenceError("Invalid after capture error") + if "capture_note" in value and not isinstance(value["capture_note"], str): + raise EvidenceError("Invalid capture note") + if digest(value["output"]) != value["output_sha256"]: + raise EvidenceError("Receipt output checksum mismatch") + payload = {key: item for key, item in value.items() if key != "receipt_sha256"} + if digest(canonical(payload)) != value["receipt_sha256"]: + raise EvidenceError("Receipt checksum mismatch") + if value != sanitize_tree(value): + raise EvidenceError("Receipt contains unredacted credential syntax") + if Path(value["common_dir"]).resolve() != Path(common_dir()): + raise EvidenceError("Receipt belongs to a different Git common directory") + return value + + +def validate_precheck(receipt): + after = receipt["after"] + current = snapshot() + if receipt["command"] not in ("make precheck", "bash scripts/task.sh precheck"): + raise EvidenceError("Task start requires a precheck receipt") + if receipt["exit_code"] != 0 or not re.search(r"^\[verdict\] READY(?:\s|$)", receipt["output"], re.M): + raise EvidenceError("Precheck receipt is not READY") + if after is None or receipt["after_error"] is not None: + raise EvidenceError("Precheck object capture is incomplete") + if receipt["before"]["head"] != current["head"] or after["head"] != current["head"]: + raise EvidenceError("Precheck source HEAD has changed") + if after["target_branch"] != current["target_branch"] or not after["target_head"] or after["target_head"] != current["target_head"]: + raise EvidenceError("Precheck authoritative target has changed") + + +def find_card(): + root = Path(git_text("rev-parse", "--show-toplevel")) + branch = git_text("rev-parse", "--abbrev-ref", "HEAD") + card = root / ".task" / (branch.replace("/", "-") + ".md") + if card.is_file(): + return card + for candidate in (root / ".task").glob("*.md"): + if re.search(r"^- 工作区:.*分支:" + re.escape(branch) + r"(?:\s|$)", candidate.read_text(), re.M): + return candidate + raise EvidenceError("No task card exists for the current branch") + + +def append_card(card, receipt): + encoded = json.dumps(receipt, ensure_ascii=False, indent=2) + block = "\n## 证据(Evidence)\n\n" + ( + "\n" + + "```json\n" + encoded + "\n```\n") + with card.open("r+", encoding="utf-8") as stream: + fcntl.flock(stream, fcntl.LOCK_EX) + existing = stream.read() + if receipt["receipt_sha256"] in existing: + return + if "## 证据(Evidence)" in existing: + block = block.replace("\n## 证据(Evidence)\n\n", "\n", 1) + stream.write(block) + stream.flush() + os.fsync(stream.fileno()) + + +def write_receipt(path, receipt): + with path.open("x", encoding="utf-8") as stream: + os.chmod(path, 0o600) + json.dump(receipt, stream, ensure_ascii=False, indent=2) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + + +def report(receipt, destination): + print(sanitize("[evidence] " + destination + " exit=" + str(receipt["exit_code"]) + + " src=SELF before=" + receipt["before"]["head"] + + " receipt_sha256=" + receipt["receipt_sha256"])) + + +def main(): + child_exit = None + try: + if os.environ.get("SRC", "SELF") not in ("", "SELF"): + raise EvidenceError("Local evidence is SELF; GATE/REPLAY require external execution identity") + capture, imported, validated = (os.environ.get(key) for key in + ("RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT")) + command = os.environ.get("CMD", "") + if sum(bool(item) for item in (capture, imported, validated)) > 1 or ((imported or validated) and command): + raise EvidenceError("Choose one capture/import/validate operation") + if imported or validated: + receipt = read_receipt(imported or validated) + if validated: + validate_precheck(receipt) + report(receipt, "precheck validated") + else: + card = find_card() + append_card(card, receipt) + report(receipt, "card=" + str(card)) + return receipt["exit_code"] + if not command or "\n" in command or "\r" in command or "\0" in command: + raise EvidenceError("CMD must be a nonempty single-line command") + path = Path(capture) if capture else None + if path is not None and (not path.is_absolute() or path.exists() or path.is_symlink()): + raise EvidenceError("Capture requires an absolute, unused receipt path") + card = find_card() if path is None else None + before = snapshot() + source_cwd, source_common = str(Path.cwd().resolve()), common_dir() + timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat() + child_env = os.environ.copy() + for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT", "TAIL"): + child_env.pop(key, None) + result = subprocess.run(["bash", "-o", "pipefail", "-c", command], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=child_env) + child_exit = result.returncode if result.returncode >= 0 else 128 - result.returncode + after, after_error = None, None + try: + after = snapshot() + except (EvidenceError, OSError) as error: + after_error = str(error) + receipt = create_receipt(command, result.stdout.decode("utf-8", "replace"), child_exit, + before, after, timestamp=timestamp, cwd=source_cwd, + git_common_dir=source_common, after_error=after_error) + if path is not None: + write_receipt(path, receipt) + report(receipt, "receipt=" + str(path)) + else: + append_card(card, receipt) + report(receipt, "card=" + str(card)) + print(receipt["output"], end="" if receipt["output"].endswith("\n") else "\n") + if after_error: + print("[evidence] after object unavailable: " + sanitize(after_error), file=sys.stderr) + return child_exit + except (EvidenceError, OSError, ValueError, TypeError) as error: + print("[evidence] ERROR: " + sanitize(str(error)), file=sys.stderr) + return child_exit if child_exit else 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/evidence.sh b/scripts/evidence.sh index a600ab0f..5378f0e0 100755 --- a/scripts/evidence.sh +++ b/scripts/evidence.sh @@ -1,55 +1,8 @@ #!/usr/bin/env bash -# 证据写入工具:执行命令,把(命令、被测 HEAD、退出码、输出 sha256、尾部摘录、时间、来源) -# 追加到当前分支任务卡的「## 证据(Evidence)」表。规范依据:AGENTS.md「证据与验证门禁」、 -# guidelines standards/verification.md「声明与证据来源」——转写/手抄的输出视为无效证据。 -# 用法:CMD="<单行命令>" make evidence [SRC=SELF|REPLAY|GATE] [TAIL=15] -# (CMD 经环境变量直传;也可直接 CMD=… SRC=… TAIL=… bash scripts/evidence.sh) -# 退出码 = 被执行命令的退出码(失败可见),证据行无论成败都会写入。 -set -uo pipefail - -CMD="${CMD:-}"; SRC="${SRC:-SELF}"; TAILN="${TAIL:-15}" -[ -n "$CMD" ] || { echo 'usage: make evidence CMD="" [SRC=SELF|REPLAY|GATE] [TAIL=15]' >&2; exit 2; } -case "$SRC" in SELF|REPLAY|GATE) ;; *) echo "ERROR: SRC 须为 SELF|REPLAY|GATE(当前:$SRC)" >&2; exit 2 ;; esac -case "$TAILN" in ''|*[!0-9]*) TAILN=15 ;; esac -case "$CMD" in *$'\n'*) echo 'ERROR: CMD 须为单行命令' >&2; exit 2 ;; esac - -branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) -[ -n "$branch" ] || { echo "ERROR: 不在 git 仓库内" >&2; exit 1; } -slug=$(echo "$branch" | tr '/' '-') -card=".task/${slug}.md" -if [ ! -f "$card" ]; then - # 回退:按卡片「分支:」字段匹配(与 task.sh cmd_sync 同款口径) - for pc in .task/*.md; do - [ -f "$pc" ] || continue - if [ "$(sed -n 's/^- 工作区:.*分支:\([A-Za-z0-9._/-]\+\).*/\1/p' "$pc" | head -1)" = "$branch" ]; then - card=$pc; break - fi - done -fi -[ -f "$card" ] || { echo "ERROR: 找不到分支 ${branch} 的任务卡(.task/${slug}.md)" >&2; exit 1; } - -head=$(git rev-parse HEAD) -short_head=$(echo "$head" | cut -c1-10) -ts=$(date -u +%FT%TZ) - -tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT -bash -c "$CMD" >"$tmp" 2>&1; rc=$? -digest=$(sha256sum "$tmp" | cut -c1-16) - -# 脱敏(baseline 硬边界 2):token 风格的 40 位 hex / Authorization 头不打进任务卡。 -sanitize() { - sed -E -e 's/(token[ =:]+)[0-9a-fA-F]{40}/\1/g' \ - -e 's/(Authorization: *token +)[^ ]+/\1/Ig' \ - -e 's/(access_token[=:]+)[0-9a-fA-F]{40}/\1/Ig' -} - -if ! grep -q '^## 证据(Evidence)' "$card"; then - printf '\n## 证据(Evidence)\n\n\n| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |\n|---|---|---|---|---|---|\n' >> "$card" -fi -cmd_cell=$(echo "$CMD" | sed -e 's/|/\\|/g' -e 's/`/‘/g') -printf '| %s | `%s` | %s | %d | %s | %s |\n' "$ts" "$cmd_cell" "$short_head" "$rc" "$SRC" "$digest" >> "$card" -printf '\n\n```\n$ %s\n[exit %d | HEAD %s | sha256:%s] 输出尾部 %s 行:\n%s\n```\n' \ - "$ts" "$CMD" "$rc" "$short_head" "$digest" "$TAILN" "$(tail -n "$TAILN" "$tmp" | sanitize)" >> "$card" - -echo "[evidence] card=$card cmd=\"${CMD}\" exit=$rc src=$SRC head=$short_head sha256=$digest" -exit "$rc" +# Local evidence is always SELF; GATE/REPLAY require external run/reviewer identity. +# CMD='make test' make evidence append a recorded run to the card +# RECEIPT=/tmp/precheck.json CMD='make precheck' make evidence capture before a card exists +# IMPORT_RECEIPT=/tmp/precheck.json make evidence import without re-executing +# VALIDATE_RECEIPT=/tmp/precheck.json bash scripts/evidence.sh task-start preflight +set -euo pipefail +exec python3 "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/evidence.py" diff --git a/scripts/task.sh b/scripts/task.sh index 8a31e3ab..5cfc8901 100755 --- a/scripts/task.sh +++ b/scripts/task.sh @@ -4,24 +4,36 @@ set -euo pipefail TARGET_BRANCH="${TASK_TARGET_BRANCH:-main}" +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) die() { echo "ERROR: $*" >&2; exit 1; } need_repo() { git rev-parse --git-dir >/dev/null 2>&1 || die "not inside a git repository"; } width() { python3 -c 'import sys,unicodedata;print(sum(2 if unicodedata.east_asian_width(c) in "WF" else 1 for c in sys.argv[1]))' "$1"; } +fetch_target() { + git check-ref-format "refs/heads/${TARGET_BRANCH}" >/dev/null || die "invalid target branch" + if ! git fetch -q origin "refs/heads/${TARGET_BRANCH}:refs/remotes/origin/${TARGET_BRANCH}"; then + echo "[verdict] REMOTE_UNVERIFIED — cannot refresh authoritative origin/${TARGET_BRANCH}; preserve local work and retry" >&2 + return 6 + fi + git rev-parse --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" >/dev/null \ + || { echo "[verdict] REMOTE_UNVERIFIED — target commit missing" >&2; return 6; } +} +check_worktree() { python3 "${SCRIPT_DIR}/check-worktree.py" "$1"; } +main_worktree_root() { git worktree list --porcelain -z | python3 -c 'import os,sys; print(os.fsdecode(sys.stdin.buffer.read().split(bytes([0]),1)[0][9:]))'; } cmd_precheck() { need_repo - git fetch -q origin "${TARGET_BRANCH}" 2>/dev/null || true + fetch_target local branch head rmain dirty sub_drift sub_uninit branch=$(git rev-parse --abbrev-ref HEAD) head=$(git rev-parse HEAD) - rmain=$(git rev-parse --verify -q "origin/${TARGET_BRANCH}" 2>/dev/null || echo "$head") + rmain=$(git rev-parse --verify "origin/${TARGET_BRANCH}") dirty=$(git status --porcelain | { grep -v '^.. \.zcode/' || true; } | wc -l) sub_drift=$(git submodule status --recursive 2>/dev/null | { grep -cE '^\+|^U' || true; }) sub_uninit=$(git submodule status --recursive 2>/dev/null | { grep -cE '^-' || true; }) local behind ahead - behind=$(git rev-list --count "HEAD..origin/${TARGET_BRANCH}" 2>/dev/null || echo 0) - ahead=$(git rev-list --count "origin/${TARGET_BRANCH}..HEAD" 2>/dev/null || echo 0) + behind=$(git rev-list --count "HEAD..origin/${TARGET_BRANCH}") + ahead=$(git rev-list --count "origin/${TARGET_BRANCH}..HEAD") echo "[repo] branch=${branch} head=$(echo "$head" | cut -c1-10)" echo "[remote] origin/${TARGET_BRANCH}=$(echo "$rmain" | cut -c1-10) ahead=${ahead} behind=${behind}" echo "[tree] $([ "$dirty" -eq 0 ] && echo clean || echo "dirty(${dirty})") submodules_drift=${sub_drift} uninit=${sub_uninit}" @@ -93,8 +105,12 @@ cmd_precheck() { echo "[verdict] SHARED_WORKSPACE_BUSY — 工作树不干净,不得写入;先收尾或经 owner 确认" exit 3 fi - if [ "$behind" -gt 0 ] && [ "$branch" = "${TARGET_BRANCH}" ]; then - echo "[verdict] SYNC_REQUIRED — 本地 ${TARGET_BRANCH} 落后远端 ${behind} 提交;先 git pull --ff-only" + if ! check_worktree before-sync; then + echo "[verdict] SHARED_WORKSPACE_BUSY — component or active operation check failed; preserve the workspace" + exit 3 + fi + if [ "$behind" -gt 0 ]; then + echo "[verdict] SYNC_REQUIRED — ${branch} 缺少 origin/${TARGET_BRANCH} 的 ${behind} 个提交;主检出 pull --ff-only,任务分支 make task-sync" exit 4 fi echo "[verdict] READY — 可建任务工作区:make task-start NAME=feature/your-branch" @@ -104,19 +120,34 @@ cmd_start() { need_repo local name="${1:-}"; [ -n "$name" ] || die "usage: task.sh start /" echo "$name" | grep -qE '^(feature|fix|hotfix|release)/[A-Za-z0-9._-]+$' || die "branch name must match (feature|fix|hotfix|release)/" - git fetch -q origin "${TARGET_BRANCH}" 2>/dev/null || true + fetch_target + [ -n "${PRECHECK_RECEIPT:-}" ] || die "PRECHECK_RECEIPT required: capture RECEIPT=/tmp/task-precheck.json CMD='make precheck' make evidence first" + PRECHECK_RECEIPT=$(realpath -- "$PRECHECK_RECEIPT") + VALIDATE_RECEIPT="$PRECHECK_RECEIPT" bash "${SCRIPT_DIR}/evidence.sh" + check_worktree before-sync [ "$(git status --porcelain | { grep -v '^.. \.zcode/' || true; } | wc -l)" -eq 0 ] || die "dirty tree — resolve first (precheck verdict BUSY)" [ "$(git submodule status --recursive 2>/dev/null | { grep -cE '^\+|^U' || true; })" -eq 0 ] || die "submodule drift/conflict — resolve first (precheck verdict BUSY)" git merge-base --is-ancestor "origin/${TARGET_BRANCH}" HEAD || die "local ${TARGET_BRANCH} behind origin — pull --ff-only first" - local rmain slug wt mainroot + local rmain slug wt mainroot start_ref parent_head="" parent_note="" rmain=$(git rev-parse "origin/${TARGET_BRANCH}") + start_ref=$rmain + if [ -n "${TASK_PARENT_BRANCH:-}${TASK_PARENT_PR:-}" ]; then + [ -n "${TASK_PARENT_BRANCH:-}" ] && [ -n "${TASK_PARENT_PR:-}" ] || die "stacked task requires TASK_PARENT_BRANCH and TASK_PARENT_PR" + echo "$TASK_PARENT_BRANCH" | grep -qE '^(feature|fix|hotfix|release)/[A-Za-z0-9._-]+$' || die "invalid parent task branch" + echo "$TASK_PARENT_PR" | grep -qE '^https?://[^[:space:]]+/pulls/[0-9]+$' || die "TASK_PARENT_PR must be a parent PR URL" + case "${name#*/}" in "${TASK_PARENT_BRANCH#*/}"-?*) ;; *) die "stacked child slug must start with parent slug plus '-'" ;; esac + parent_head=$(git rev-parse --verify "refs/heads/${TASK_PARENT_BRANCH}^{commit}") || die "parent branch missing locally" + git merge-base --is-ancestor "$rmain" "$parent_head" || die "parent branch does not include current origin/${TARGET_BRANCH}; sync it first" + start_ref=$parent_head + parent_note="- 堆叠父任务:${TASK_PARENT_BRANCH};父 PR:${TASK_PARENT_PR};parent_head_sha=${parent_head};父合入后 task-sync 并将 PR base 改回 ${TARGET_BRANCH}" + fi slug=$(echo "$name" | tr '/' '-') # 落点须为主检出的平级目录:$PWD 推算在子目录(或任务 worktree 内)运行时会算错, - # 改用 git-common-dir 的父目录定位主检出(与 cmd_finish 同款惯用法)。 - mainroot=$(dirname "$(cd "$(git rev-parse --git-common-dir)" && pwd)") + # 以 Git 登记表定位主检出,兼容 linked worktree 与独立 git-dir。 + mainroot=$(main_worktree_root) wt="$(dirname "$mainroot")/$(basename "$mainroot")-wt-${slug}" [ -e "$wt" ] && die "worktree path exists: $wt" - git worktree add -q "$wt" -b "$name" "$rmain" + git worktree add -q "$wt" -b "$name" "$start_ref" # 物化后按 submodule status 实际状态核对,不只信退出码:网络抖动或嵌套指针 # 漂移时 update 可能静默留坑(2026-09-30 实测深嵌套层带病通过,构建期才炸)。 (cd "$wt" && git submodule update --init --recursive >/dev/null 2>&1) \ @@ -130,12 +161,13 @@ cmd_start() { cat > "$card" <;状态:进行中;owner:$(git config user.name || echo unknown) +任务主记录:;状态:进行中;owner:$(git config user.name || echo unknown) ## 基线与授权 - 权威仓:$(git -C "$wt" remote get-url origin 2>/dev/null || echo '(local)');目标分支:${TARGET_BRANCH};R0(initial_base_sha)= ${rmain} - 工作区:${wt}(worktree);分支:${name} +${parent_note} - 授权与交付终点:<本次允许的动作与终点;未授权事项写明> ## 目标与非目标 @@ -160,16 +192,31 @@ cmd_start() { - 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表> - 影响与剩余项: EOF - git -C "$wt" add .task && git -C "$wt" commit -q -m "Record the task card for ${slug}" && echo "[card] $card(已随分支提交)" + (cd "$wt" && IMPORT_RECEIPT="$PRECHECK_RECEIPT" bash "${SCRIPT_DIR}/evidence.sh") + git -C "$wt" add .task + git -C "$wt" commit -q -m "Record the task card for ${slug}" \ + -m "Why: establish the authorized task workspace and its initial baseline." \ + -m "Evidence: preserved precheck receipt in .task/${slug}.md; R0=${rmain}." \ + -m "Impact: task metadata only; the feature workspace starts at ${start_ref}." \ + -m "Refs: .task/${slug}.md" + echo "[card] $card(已随分支提交)" echo "[started] branch=${name} worktree=${wt} R0=$(echo "$rmain" | cut -c1-10)" } cmd_sync() { need_repo - git fetch -q origin "${TARGET_BRANCH}" - [ "$(git status --porcelain | grep -v '^.. \.zcode/' | wc -l)" -eq 0 ] || die "dirty tree — 先显式提交;不隐式 stash(collaboration.md:11)" - git merge --no-edit "origin/${TARGET_BRANCH}" + fetch_target + check_worktree before-sync + git merge --no-edit "origin/${TARGET_BRANCH}" \ + -m "Merge authoritative ${TARGET_BRANCH} into the task branch" \ + -m "Why: include the current integration baseline before verification." \ + -m "Evidence: fetched origin/${TARGET_BRANCH}=$(git rev-parse "origin/${TARGET_BRANCH}")." \ + -m "Impact: integrate upstream changes without rewriting task history." \ + -m "Refs: task-sync" git merge-base --is-ancestor "origin/${TARGET_BRANCH}" HEAD || die "sync incomplete" + check_worktree before-update + check_worktree materialize + check_worktree after-sync local base head card branch slug pc base=$(git rev-parse "origin/${TARGET_BRANCH}"); head=$(git rev-parse HEAD) branch=$(git rev-parse --abbrev-ref HEAD) @@ -188,7 +235,10 @@ cmd_sync() { if [ -n "$card" ] && [ -f "$card" ]; then printf '| 同步前验收 | %s | %s | %s |\n' "$base" "$head" "$(date -u +%FT%TZ)" >> "$card" git add "$card" - git commit -q -m "Record the sync checkpoint" || true + git commit -q -m "Record the sync checkpoint" \ + -m "Why: bind the task validation checkpoint to the latest integration baseline." \ + -m "Evidence: SELF recursive component checks; sync_base=${base}; candidate=${head}." \ + -m "Impact: task metadata only." -m "Refs: ${card}" # [evidence] 同步改变了被测对象:卡片证据表中旧 HEAD 的行,其后若有非 .task 变更则失效须重跑。 local eh stale_ev="" short_head changed short_head=$(echo "$head" | cut -c1-10) @@ -252,7 +302,7 @@ cmd_close() { need_repo local fold=0 [ "${1:-}" = "--fold" ] && fold=1 - git fetch -q origin "${TARGET_BRANCH}" 2>/dev/null || true + fetch_target # 默认值取远端通告的 Web 面(git push 输出的 PR 建议链接);可用 TASK_CARD_WEB_BASE 覆盖。 # git-panel 前缀已失效(TLS 握手失败),默认值改为实测可用的 git-home(AGENTS.md 访问事实节)。 local web_base="${TASK_CARD_WEB_BASE:-https://git-home.u1680832.nyat.app:30319}" @@ -358,37 +408,61 @@ for p in json.load(sys.stdin): cmd_finish() { need_repo - local abandon="${1:-}" - local branch head common mainrepo - branch=$(git rev-parse --abbrev-ref HEAD) + local abandon="${1:-}" branch head root mainrepo remote_head="" remote_status + root=$(git rev-parse --show-toplevel) + check_worktree finish + [ "${TASK_WRITERS_STOPPED:-}" = 1 ] || die "TASK_WRITERS_STOPPED=1 required: the workspace owner must first confirm all writers have stopped" + branch=$(git symbolic-ref --quiet --short HEAD) || die "detached task worktree must be preserved" + case "$branch" in feature/*|fix/*|hotfix/*|release/*) ;; *) die "refuse to recycle non-task branch ${branch}" ;; esac head=$(git rev-parse HEAD) - common=$(git rev-parse --git-common-dir); common=$(cd "$common" && pwd) - mainrepo=$(dirname "$common") - # 防护(T0 事故教训):finish 必须在任务 worktree 内运行——本脚本把 $PWD 当 - # worktree 回收(rm -rf);从主检出运行会误删主检出。mainrepo==PWD 即主检出。 - [ "$(cd "$mainrepo" && pwd)" = "$PWD" ] && die "task-finish 须在任务 worktree 内运行(cd 到 worktree 再执行),防止误删主检出" - git fetch -q origin "${TARGET_BRANCH}" 2>/dev/null || true + mainrepo=$(main_worktree_root) + fetch_target if [ "$abandon" = "--abandon" ]; then - local reason="${2:-}"; [ -n "$reason" ] || die "--abandon 需要原因:task.sh finish --abandon " - local untracked; untracked=$(git status --porcelain | grep -c '^??' || true) - [ "$untracked" -gt 0 ] && die "存在 ${untracked} 个未跟踪文件 — 先处置或用 task-finish FORCE_ABANDON=1" - [ "${FORCE_ABANDON:-0}" = "1" ] || { echo "[confirm] 放弃未合入工作属破坏性动作:重跑 FORCE_ABANDON=1 task.sh finish --abandon \"${reason}\""; exit 5; } - echo "[abandoned] branch=${branch} reason=${reason} recorded_at=$(date -u +%FT%TZ)" + local reason="${2:-}"; [ -n "$reason" ] || die "--abandon requires a reason" + # An explicit abandon removes only the clean checkout; commits stay reachable. + git notes --ref=refs/notes/task-abandon append -m "Task ${branch} abandoned at $(date -u +%FT%TZ): ${reason}" "$head" + echo "[abandoned] branch=${branch}; commits retained; reason recorded in refs/notes/task-abandon" + elif [ -n "$abandon" ]; then + die "usage: task.sh finish [--abandon ]" else - git merge-base --is-ancestor "$head" "origin/${TARGET_BRANCH}" || die "分支未合入 origin/${TARGET_BRANCH}——先走 PR 合入;放弃则用 --abandon " - echo "[verified] ${branch} 已包含于 origin/${TARGET_BRANCH};成果已落盘" + git merge-base --is-ancestor "$head" "origin/${TARGET_BRANCH}" || die "branch has not merged into origin/${TARGET_BRANCH}; preserve it or explicitly abandon the checkout" + remote_status=$(git ls-remote --heads origin "refs/heads/${branch}") || die "cannot verify remote task branch; preserve workspace" + if [ -n "$remote_status" ]; then + # Refresh its object, then use that exact observation as a deletion lease. + git fetch -q origin "refs/heads/${branch}" || die "cannot fetch remote task branch; preserve workspace" + remote_head=$(git rev-parse --verify FETCH_HEAD) + git merge-base --is-ancestor "$remote_head" "origin/${TARGET_BRANCH}" || die "remote task branch has unmerged work; preserve workspace and remote branch" + fi + echo "[verified] ${branch} is included in origin/${TARGET_BRANCH}" fi - # git 对含 gitlink 的 worktree 无条件拒绝 remove(查索引),回收走 rm -rf + prune。 - # 此处不得跑 submodule deinit:submodule..url 写在共享的 .git/config 里,deinit - # 会把其他检出/worktree 的子模块一并标成未初始化(2026-09-30 实测 finish 后主检出 uninit=12)。 - git -C "$mainrepo" worktree remove "$PWD" 2>/dev/null || { rm -rf "$PWD"; git -C "$mainrepo" worktree prune; } - git -C "$mainrepo" branch -d "$branch" 2>/dev/null || git -C "$mainrepo" branch -D "$branch" - git -C "$mainrepo" worktree prune - git push -q --delete origin "$branch" 2>/dev/null || true - echo "[recycled] worktree 与分支 ${branch} 已回收(本地+远端,$(date -u +%FT%TZ))" - # 合入成功→自动闭合任务卡(含本任务卡;经 feature/card-close-* 分支+PR,合入权在所有者) - ( cd "$mainrepo" && bash scripts/task.sh close ) \ - || echo "[next] 卡片自动闭合未完成——在主检出运行 make task-close" + # Re-check after fetch. --force only compensates Git's gitlink limitation after all + # registered-root, owner, lock, recursive dirt and ignored-file checks have passed. + check_worktree finish + local -a removal=(worktree remove) + if git ls-files --stage | grep -q '^160000 '; then + removal+=(--force) + fi + if ! git -C "$mainrepo" "${removal[@]}" "$root"; then + die "Git refused worktree removal; workspace and branch retained. Owner must inspect the refusal; no rm fallback or additional force level is permitted." + fi + cd "$mainrepo" + if [ "$abandon" = "--abandon" ]; then + echo "[recycled] worktree removed; local branch=${branch} retained; remote=NOT_RUN" + elif git branch -d "$branch"; then + if [ -z "$remote_head" ]; then + echo "[recycled] worktree and local branch=${branch} removed; remote=ALREADY_ABSENT" + elif git push -q "--force-with-lease=refs/heads/${branch}:${remote_head}" origin ":refs/heads/${branch}"; then + echo "[recycled] worktree and local branch=${branch} removed; remote=DELETED observed_head=${remote_head}" + else + echo "[partial] local worktree/branch removed; remote=${branch} retained or result unknown; query remote before retrying" >&2 + return 1 + fi + else + echo "[partial] worktree removed; local branch=${branch} retained because safe branch deletion was refused; remote=NOT_RUN" >&2 + return 1 + fi + # Keep card closure separate: its push/PR side effects are not implied by local disposal. + echo "[next] inspect task-card closure with make task-close in the main checkout when authorized" } case "${1:-}" in diff --git a/scripts/zcode/write-guard.sh b/scripts/zcode/write-guard.sh index da9f7da5..9e2a1d90 100644 --- a/scripts/zcode/write-guard.sh +++ b/scripts/zcode/write-guard.sh @@ -1,38 +1,10 @@ #!/usr/bin/env bash -# zcode PreToolUse 写路径守卫(Edit/Write/ApplyPatch)。 -# 单一权威声明:本脚本是 Development-Guidelines standards/tool-adaptation.md -# 要求 A1/A3 在 zcode 客户端的机制实例;语义以库与项目入口为准,本文件不复制规则正文。 -# 退出码:0 放行;2 阻断(PreToolUse 的 deny);其余非零为错误。 +# PreToolUse 写路径守卫。解析/依赖错误均拒绝,不能把未知请求当作安全请求。 set -uo pipefail - -input=$(cat) -file=$(printf '%s' "$input" | python3 -c ' -import json, sys -try: - d = json.load(sys.stdin) -except Exception: - sys.exit(0) -ti = d.get("tool_input") if isinstance(d.get("tool_input"), dict) else {} -for src in (ti, d): - for k in ("file_path", "path", "filename"): - v = src.get(k) - if isinstance(v, str) and v: - print(v) - sys.exit(0) -' 2>/dev/null) -[ -n "$file" ] || exit 0 - -repo=$(git -C "$(dirname "$file")" rev-parse --show-toplevel 2>/dev/null) || exit 0 -branch=$(git -C "$repo" rev-parse --abbrev-ref HEAD 2>/dev/null) || exit 0 - -deny() { echo "write-guard: 拒绝——${1}(目标 ${file},仓库 ${repo})" >&2; exit 2; } - -case "$branch" in - main|master) - deny "集成分支 ${branch} 不得直接写入;先 make task-start NAME=feature|fix/(A1/A3)" ;; - HEAD) - deny "仓库处于 detached HEAD(常为他人现场或未建分支的子仓);先显式建任务分支(A1)" ;; -esac -echo "$branch" | grep -qE '^(feature|fix|hotfix|release)/[A-Za-z0-9._-]+$' \ - || deny "分支名 ${branch} 不符 feature|fix|hotfix|release/(A3)" -exit 0 +if ! command -v python3 >/dev/null 2>&1; then + echo 'write-guard: 拒绝——缺少 python3,无法检查写入目标' >&2 + exit 2 +fi +python3 "$(dirname "$0")/write_guard.py" +result=$? +if [ "$result" -ne 0 ]; then exit 2; fi diff --git a/scripts/zcode/write_guard.py b/scripts/zcode/write_guard.py new file mode 100644 index 00000000..df9f7366 --- /dev/null +++ b/scripts/zcode/write_guard.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Check every write target; a local branch guard, not an OS sandbox.""" +import json +import os +from pathlib import Path +import re +import subprocess +import sys + + +def deny(reason): + raise ValueError(reason) + + +def path_value(value): + if not isinstance(value, str) or not value.strip() or any(c in value for c in "\x00\r\n"): + deny("写入路径缺失或无法解析") + return value + + +def patch_paths(patch): + if not isinstance(patch, str): + deny("ApplyPatch 缺少 patch_text") + lines = patch.strip().splitlines() + if len(lines) < 3 or lines[0] != "*** Begin Patch" or lines[-1] != "*** End Patch": + deny("无法解析 ApplyPatch 边界") + paths = [] + operation = None + moved = False + for line in lines[1:-1]: + match = re.fullmatch(r"\*\*\* (Add File|Update File|Delete File): (.+)", line) + if match: + operation = match[1] + moved = False + paths.append(path_value(match[2])) + elif line.startswith("*** Move to: ") and operation == "Update File" and not moved: + paths.append(path_value(line[len("*** Move to: "):])) + moved = True + elif line == "*** End of File" and operation == "Update File": + continue + elif line.startswith("***") or operation is None: + deny("ApplyPatch 包含未知路径指令") + elif not (line.startswith(("+", "-", " ", "@@")) or line == ""): + deny("ApplyPatch 包含无法解析的内容") + if not paths: + deny("ApplyPatch 没有可检查的路径") + return paths + + +def check_path(raw, cwd): + target = Path(raw) + if not target.is_absolute(): + target = cwd / target + # A tool can follow a symlink OR replace/delete its directory entry. Check + # both locations, including a dangling final symlink on a protected branch. + location = target.parent.resolve() / target.name + check_location(location, entry_only=location.is_symlink()) + resolved = target.resolve() + if resolved != location: + check_location(resolved) + + +def check_location(target, entry_only=False): + ancestor = target if target.is_dir() and not entry_only else target.parent + while not ancestor.exists(): + ancestor = ancestor.parent + if not ancestor.is_dir(): + deny(f"目标祖先不是目录:{ancestor}") + # GIT_DIR/GIT_WORK_TREE must not redirect inspection to another checkout. + env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")} + env["LC_ALL"] = "C" + result = subprocess.run(["git", "-C", str(ancestor), "rev-parse", "--show-toplevel"], + env=env, capture_output=True, text=True) + if result.returncode: + if any((p / ".git").exists() or (p / ".git").is_symlink() + for p in (ancestor, *ancestor.parents)): + deny(f"无法读取目标仓库状态:{target}") + if "not a git repository" not in result.stderr: + deny(f"无法确认目标仓库:{target}") + print(f"write-guard: outside-git(未受 Git 分支规则覆盖):{target}", file=sys.stderr) + return + repo = result.stdout.strip() + branch_result = subprocess.run(["git", "-C", repo, "symbolic-ref", "--quiet", "--short", "HEAD"], + env=env, capture_output=True, text=True) + if branch_result.returncode: + deny(f"仓库处于 detached HEAD 或状态不可读:{repo};先建立任务分支(A1)") + branch = branch_result.stdout.strip() + if not re.fullmatch(r"(?:feature|fix|hotfix|release)/[A-Za-z0-9._-]+", branch): + deny(f"分支 {branch} 不允许写入(目标 {target},仓库 {repo});先 make task-start NAME=feature/(A1/A3)") + + +def main(): + data = json.load(sys.stdin) + if not isinstance(data, dict): + deny("钩子输入必须为对象") + tool = data.get("tool_name") + if tool in {"Read", "Glob", "Grep", "WebFetch", "WebSearch"}: + return + if tool not in {None, "Edit", "Write", "ApplyPatch", "apply_patch"}: + deny("未知写入工具,必须先补充适配") + payload = data.get("tool_input", data) + if isinstance(payload, str) and tool in {"ApplyPatch", "apply_patch"}: + paths = patch_paths(payload) + elif isinstance(payload, dict): + # Installed zcode uses patch_text; aliases use the same structured format. + patches = [payload[k] for k in ("patch_text", "patch", "input") if k in payload] + paths = [path_value(payload[k]) for k in ("file_path", "path", "filename") if k in payload] + for patch in patches: + paths.extend(patch_paths(patch)) + else: + deny("无法解析写入工具参数") + if not paths: + deny("写入请求未提供可检查路径") + cwd = Path(path_value(data.get("cwd", os.getcwd()))).resolve() + if not cwd.is_dir(): + deny("钩子 cwd 不存在") + for path in paths: + check_path(path, cwd) + + +if __name__ == "__main__": + try: + main() + except (ValueError, OSError, RuntimeError) as error: + print(f"write-guard: 拒绝——{error}", file=sys.stderr) + sys.exit(2) diff --git a/tests/test_ci_credentials.py b/tests/test_ci_credentials.py new file mode 100644 index 00000000..39c533e8 --- /dev/null +++ b/tests/test_ci_credentials.py @@ -0,0 +1,213 @@ +"""Execute the workflow checkout against real disposable HTTP Git repositories.""" +import http.server +import os +from pathlib import Path +import subprocess +import tempfile +import textwrap +import threading +import unittest +from urllib.parse import urlsplit + + +WORKFLOW = Path(__file__).resolve().parents[1] / ".gitea/workflows/ci.yaml" +SERVER = "https://git-home.u1680832.nyat.app:30319" +SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/" +ROOT_TOKEN = "synthetic-root-token-not-a-real-secret" +SUB_TOKEN = "synthetic-component-token-not-a-real-secret" + + +def checkout_script(): + lines = WORKFLOW.read_text().splitlines() + start = next(i for i, line in enumerate(lines) if line.strip() == "- name: Checkout") + start = next(i for i in range(start, len(lines)) if lines[i].strip() == "run: |") + 1 + end = next(i for i in range(start, len(lines)) if lines[i].startswith(" - name:")) + return textwrap.dedent("\n".join(lines[start:end])) + + +class CheckoutTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.base = Path(self.temp.name) + self.published = self.base / "published" + self.published.mkdir() + self.home = self.base / "home" + self.home.mkdir() + self.checkout = self.base / "checkout" + self.checkout.mkdir() + self.env = {k: v for k, v in os.environ.items() + if not k.startswith(("GIT_", "GITHUB_")) and k not in {"TOKEN", "SUBMODULE_TOKEN"}} + self.env.update(HOME=str(self.home), GIT_CONFIG_NOSYSTEM="1", GIT_TERMINAL_PROMPT="0") + self.requests = [] + self.redirect_repo = None + outer = self + + class Handler(http.server.BaseHTTPRequestHandler): + def log_message(self, *args): + pass + + def do_GET(self): + self.respond() + + def do_POST(self): + self.respond() + + def respond(self): + parsed = urlsplit(self.path) + outer.requests.append((parsed.path, self.headers.get("Authorization"))) + if outer.redirect_repo and outer.redirect_repo in parsed.path: + self.send_response(302) + self.send_header("Location", outer.url + "/untrusted/receiver") + self.end_headers() + return + env = outer.env | {"GIT_PROJECT_ROOT": str(outer.published), "GIT_HTTP_EXPORT_ALL": "1", + "PATH_INFO": parsed.path, "QUERY_STRING": parsed.query, + "REQUEST_METHOD": self.command, + "CONTENT_TYPE": self.headers.get("Content-Type", ""), + "CONTENT_LENGTH": self.headers.get("Content-Length", "0")} + body = self.rfile.read(int(env["CONTENT_LENGTH"])) + result = subprocess.run(["git", "http-backend"], input=body, env=env, capture_output=True) + headers, body = result.stdout.split(b"\r\n\r\n", 1) + pairs = [line.decode().split(": ", 1) for line in headers.split(b"\r\n")] + status = next((int(value.split()[0]) for key, value in pairs if key == "Status"), 200) + self.send_response(status) + for key, value in pairs: + if key != "Status": + self.send_header(key, value) + self.end_headers() + self.wfile.write(body) + + self.http = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler) + self.url = f"http://127.0.0.1:{self.http.server_port}" + # Any regression that tries a production HTTPS URL must fail locally. + self.env.update(https_proxy=self.url, HTTPS_PROXY=self.url, + no_proxy="127.0.0.1,localhost", NO_PROXY="127.0.0.1,localhost") + thread = threading.Thread(target=self.http.serve_forever, daemon=True) + thread.start() + self.addCleanup(self.http.server_close) + self.addCleanup(self.http.shutdown) + + def git(self, directory, *args): + return subprocess.run(["git", "-C", str(directory), *args], env=self.env, + check=True, capture_output=True, text=True).stdout.strip() + + def publish(self, name, children=(), extra_gitmodules=""): + source = self.base / name + source.mkdir() + self.git(source, "init", "-q", "-b", "main") + (source / "README").write_text("Fixture repository\n") + # Candidate code tries the channels available after the checkout step. + (source / "probe.py").write_text(textwrap.dedent(f"""\ + import os, pathlib, subprocess + sources = [repr(dict(os.environ)), subprocess.check_output(['git', 'config', '--list'], text=True)] + for path in [pathlib.Path.home() / '.gitconfig', *pathlib.Path('.git').rglob('config')]: + if path.is_file(): sources.append(path.read_text()) + assert not any(secret in source for secret in ({ROOT_TOKEN!r}, {SUB_TOKEN!r}) for source in sources), 'credential readable by candidate' + print('candidate cannot read checkout credentials') + """)) + gitmodules = "" + for path, repo, sha, override in children: + gitmodules += f'[submodule "{path}"]\n\tpath = {path}\n\turl = {override or SSH + repo + ".git"}\n' + if gitmodules or extra_gitmodules: + (source / ".gitmodules").write_text(gitmodules + extra_gitmodules) + self.git(source, "add", ".") + for path, repo, sha, override in children: + self.git(source, "update-index", "--add", "--cacheinfo", f"160000,{sha},{path}") + self.git(source, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", + "commit", "-q", "-m", "Fixture") + sha = self.git(source, "rev-parse", "HEAD") + target = self.published / "weichen" / (name + ".git") + target.parent.mkdir(exist_ok=True) + self.git(self.base, "clone", "-q", "--bare", str(source), str(target)) + return sha + + def run_checkout(self, sha): + # Only the fixed transport endpoint changes; execute the actual workflow + # shell/Python logic, not a test-specific implementation of checkout. + script = checkout_script().replace(SERVER, self.url) + return subprocess.run(["bash", "-c", script], cwd=self.checkout, + env=self.env | {"TOKEN": ROOT_TOKEN, "SUBMODULE_TOKEN": SUB_TOKEN, + "GITHUB_SERVER_URL": self.url, + "GITHUB_REPOSITORY": "weichen/Agent-System-Dev", + "GITHUB_SHA": sha}, + capture_output=True, text=True, timeout=30) + + def test_candidate_cannot_read_checkout_credentials(self): + sha = self.publish("Agent-System-Dev") + result = self.run_checkout(sha) + self.assertEqual(result.returncode, 0, result.stderr) + candidate = subprocess.run(["python3", "probe.py"], cwd=self.checkout, + env=self.env, capture_output=True, text=True) + self.assertEqual(candidate.returncode, 0, candidate.stderr) + self.assertNotIn(ROOT_TOKEN, result.stdout + result.stderr) + self.assertNotIn(SUB_TOKEN, result.stdout + result.stderr) + + def test_recursive_gitlinks_materialize_exact_sha_without_persisting_credentials(self): + core = self.publish("Agent-System-Core") + backend = self.publish("Agent-System-Backend", [("deps/agent-system-core", "Agent-System-Core", core, None)]) + root = self.publish("Agent-System-Dev", [("modules/backend", "Agent-System-Backend", backend, None)]) + result = self.run_checkout(root) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(self.git(self.checkout / "modules/backend", "rev-parse", "HEAD"), backend) + self.assertEqual(self.git(self.checkout / "modules/backend/deps/agent-system-core", "rev-parse", "HEAD"), core) + self.assertNotIn("-", self.git(self.checkout, "submodule", "status", "--recursive")[:1]) + candidate = subprocess.run(["python3", "probe.py"], cwd=self.checkout, + env=self.env, capture_output=True, text=True) + self.assertEqual(candidate.returncode, 0, candidate.stderr) + for path, auth in self.requests: + self.assertEqual(auth, "token " + (ROOT_TOKEN if "Agent-System-Dev.git/" in path else SUB_TOKEN)) + for config in (self.checkout / ".git").rglob("config"): + self.assertNotIn(SUB_TOKEN, config.read_text()) + + def test_external_or_unlisted_urls_are_rejected_before_component_fetch(self): + core = self.publish("Agent-System-Core") + root = self.publish("Agent-System-Dev", [("modules/core", "Agent-System-Core", core, + self.url + "/untrusted/receiver")]) + result = self.run_checkout(root) + self.assertNotEqual(result.returncode, 0) + self.assertFalse(any("untrusted" in path for path, auth in self.requests)) + self.assertFalse((self.checkout / "modules/core/.git").exists()) + + def test_candidate_submodule_update_commands_are_rejected(self): + core = self.publish("Agent-System-Core") + root = self.publish("Agent-System-Dev", [("modules/core", "Agent-System-Core", core, None)], + "\tupdate = !touch /tmp/should-never-execute\n") + result = self.run_checkout(root) + self.assertNotEqual(result.returncode, 0) + self.assertFalse(any("Agent-System-Core.git/" in path for path, auth in self.requests)) + + def test_nested_external_url_is_rejected_before_authenticated_fetch(self): + core = self.publish("Agent-System-Core") + backend = self.publish("Agent-System-Backend", [("deps/agent-system-core", "Agent-System-Core", core, + self.url + "/untrusted/receiver")]) + root = self.publish("Agent-System-Dev", [("modules/backend", "Agent-System-Backend", backend, None)]) + result = self.run_checkout(root) + self.assertNotEqual(result.returncode, 0) + self.assertFalse(any("untrusted" in path for path, auth in self.requests)) + self.assertFalse(any("Agent-System-Core.git/" in path for path, auth in self.requests)) + + def test_inherited_template_hook_does_not_execute_with_credentials(self): + template = self.base / "template" + (template / "hooks").mkdir(parents=True) + marker = self.base / "hook-executed" + hook = template / "hooks/post-checkout" + hook.write_text(f"#!/bin/sh\ntouch '{marker}'\n") + hook.chmod(0o755) + self.git(self.base, "config", "--global", "init.templateDir", str(template)) + root = self.publish("Agent-System-Dev") + result = self.run_checkout(root) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(marker.exists()) + + def test_redirects_do_not_forward_credential_or_fetch_unlisted_target(self): + core = self.publish("Agent-System-Core") + root = self.publish("Agent-System-Dev", [("modules/core", "Agent-System-Core", core, None)]) + self.redirect_repo = "Agent-System-Core.git" + result = self.run_checkout(root) + self.assertNotEqual(result.returncode, 0) + self.assertFalse(any("untrusted" in path for path, auth in self.requests)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_evidence.py b/tests/test_evidence.py new file mode 100644 index 00000000..249d72e7 --- /dev/null +++ b/tests/test_evidence.py @@ -0,0 +1,284 @@ +"""Exercise the evidence recorder against disposable real Git repositories.""" +import hashlib +import json +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + + +SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" + + +class EvidenceTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.base = Path(self.temp.name) + self.repo = self.base / "repo" + self.repo.mkdir() + self.git("init", "-b", "main") + self.git("config", "user.name", "Evidence Test") + self.git("config", "user.email", "evidence@example.invalid") + (self.repo / "tracked").write_text("initial\n") + self.git("add", "tracked") + self.git("commit", "-qm", "Initial") + self.head = self.git("rev-parse", "HEAD") + self.git("update-ref", "refs/remotes/origin/main", self.head) + self.receipt = self.base / "receipt.json" + self.card = self.repo / ".task/main.md" + self.card.parent.mkdir() + self.card.write_text("# Test task\n") + + def git(self, *args, cwd=None): + return subprocess.check_output( + ["git", *args], cwd=cwd or self.repo, text=True, + stderr=subprocess.DEVNULL).strip() + + def run_evidence(self, command=None, cwd=None, **values): + env = os.environ.copy() + for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT"): + env.pop(key, None) + if command is not None: + env["CMD"] = command + env.update({key: str(value) for key, value in values.items()}) + return subprocess.run( + ["bash", str(SCRIPTS / "evidence.sh")], cwd=cwd or self.repo, + env=env, capture_output=True, text=True) + + def capture(self, command="printf 'hello\\n'", **kwargs): + result = self.run_evidence(command, RECEIPT=self.receipt, **kwargs) + self.assertEqual(result.returncode, 0, result.stderr + result.stdout) + self.assertTrue(self.receipt.exists(), "capture must write a receipt") + return json.loads(self.receipt.read_text()) + + def import_receipt(self, **kwargs): + return self.run_evidence(IMPORT_RECEIPT=self.receipt, **kwargs) + + def resign(self, data): + payload = {key: value for key, value in data.items() if key != "receipt_sha256"} + data["receipt_sha256"] = hashlib.sha256(json.dumps( + payload, sort_keys=True, ensure_ascii=False, separators=(",", ":") + ).encode()).hexdigest() + self.receipt.write_text(json.dumps(data)) + + def test_pipeline_failure_is_recorded_and_returned(self): + result = self.run_evidence("false | tail -n 1") + self.assertEqual(result.returncode, 1) + self.assertIn('"exit_code": 1', self.card.read_text()) + + def test_child_failure_code_is_preserved(self): + result = self.run_evidence("exit 7") + self.assertEqual(result.returncode, 7) + self.assertIn('"exit_code": 7', self.card.read_text()) + + def test_capture_without_card_and_import_without_rerun(self): + self.card.unlink() + data = self.capture("printf 'once\\n' >> invocation; cat invocation") + self.assertEqual(data["before"]["head"], self.head) + self.assertEqual(data["output"], "once\n") + self.assertEqual(data["source"], "SELF") + self.assertFalse(self.card.exists()) + self.card.write_text("# Test task\n") + result = self.import_receipt() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual((self.repo / "invocation").read_text(), "once\n") + self.assertIn(data["receipt_sha256"], self.card.read_text()) + + def test_recorder_environment_does_not_leak_into_nested_commands(self): + data = self.capture("printf '%s,%s,%s' \"${CMD-unset}\" \"${RECEIPT-unset}\" \"${SRC-unset}\"", SRC="SELF") + self.assertEqual(data["output"], "unset,unset,unset") + + def test_missing_card_does_not_execute(self): + self.card.unlink() + result = self.run_evidence("touch should-not-exist") + self.assertNotEqual(result.returncode, 0) + self.assertFalse((self.repo / "should-not-exist").exists()) + + def test_import_missing_card_fails_without_losing_receipt(self): + self.capture() + self.card.unlink() + result = self.import_receipt() + self.assertNotEqual(result.returncode, 0) + self.assertTrue(self.receipt.exists()) + + def test_linked_worktree_import_preserves_source_identity(self): + data = self.capture() + other = self.base / "linked" + self.git("worktree", "add", "-qb", "feature/linked", str(other)) + card = other / ".task/feature-linked.md" + card.parent.mkdir() + card.write_text("# Linked task\n") + result = self.import_receipt(cwd=other) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn(str(self.repo), card.read_text()) + self.assertIn(data["before"]["head"], card.read_text()) + + def test_unrelated_repo_cannot_import(self): + self.capture() + other = self.base / "unrelated" + subprocess.check_call(["git", "clone", "-q", str(self.repo), str(other)]) + card = other / ".task/main.md" + card.parent.mkdir() + card.write_text("# Other task\n") + result = self.import_receipt(cwd=other) + self.assertNotEqual(result.returncode, 0) + self.assertEqual(card.read_text(), "# Other task\n") + + def test_all_persisted_and_displayed_text_is_sanitized(self): + secrets = ["syntheticBearerValue", "syntheticEnvValue", "syntheticUrlPassword", + "syntheticQueryValue", "syntheticHeaderValue", "syntheticPlainValue"] + command = "printf '%s\\n' " + " ".join([ + "'Authorization: Bearer syntheticBearerValue'", + "'GITEA_TOKEN=syntheticEnvValue'", + "'https://user:syntheticUrlPassword@example.invalid'", + "'https://example.invalid?access_token=syntheticQueryValue&ok=1'", + "'Authorization: token syntheticHeaderValue'", + "'password: syntheticPlainValue'", + ]) + data = self.capture(command) + imported = self.import_receipt() + all_text = self.receipt.read_text() + self.card.read_text() + imported.stdout + imported.stderr + for secret in secrets: + self.assertNotIn(secret, all_text) + self.assertIn("", data["command"]) + self.assertEqual(data["output_sha256"], hashlib.sha256(data["output"].encode()).hexdigest()) + self.assertEqual(data["command_sha256"], hashlib.sha256(command.encode()).hexdigest()) + + def test_known_secret_environment_value_is_redacted_without_label(self): + data = self.capture("printf %s \"$GITEA_TOKEN\"", GITEA_TOKEN="unlabelledSyntheticCredential") + self.assertNotIn("unlabelledSyntheticCredential", self.receipt.read_text()) + self.assertIn("", data["output"]) + + def test_quoted_credentials_with_spaces_are_fully_redacted(self): + secret = "synthetic phrase with spaces" + data = self.capture('printf \'password="synthetic phrase with spaces"\\n\'') + self.assertNotIn(secret, self.receipt.read_text()) + self.assertNotIn("phrase with spaces", data["command"] + data["output"]) + + def test_command_line_token_arguments_are_redacted(self): + data = self.capture("printf '%s\\n' 'tool --token syntheticArgumentValue'") + self.assertNotIn("syntheticArgumentValue", self.receipt.read_text()) + self.assertIn("", data["command"]) + + def test_source_cannot_be_promoted_by_environment(self): + for source in ("GATE", "REPLAY"): + with self.subTest(source=source): + result = self.run_evidence("touch should-not-exist", SRC=source) + self.assertNotEqual(result.returncode, 0) + self.assertFalse((self.repo / "should-not-exist").exists()) + + def test_tamper_is_rejected_before_card_append(self): + data = self.capture() + data["output"] = "changed output" + self.receipt.write_text(json.dumps(data)) + result = self.import_receipt() + self.assertNotEqual(result.returncode, 0) + self.assertEqual(self.card.read_text(), "# Test task\n") + + def test_invalid_field_types_and_forged_source_are_rejected(self): + original = self.capture() + for field, value in (("exit_code", True), ("source", "GATE"), ("before", [])): + with self.subTest(field=field): + changed = dict(original, **{field: value}) + self.resign(changed) + self.assertNotEqual(self.import_receipt().returncode, 0) + self.assertEqual(self.card.read_text(), "# Test task\n") + + def test_output_digest_checked_even_if_receipt_digest_recomputed(self): + data = self.capture() + data["output_sha256"] = "0" * 64 + self.resign(data) + self.assertNotEqual(self.import_receipt().returncode, 0) + + def test_missing_required_snapshot_fields_are_rejected(self): + original = self.capture() + for field in ("target_head", "status"): + with self.subTest(field=field): + self.card.write_text("# Test task\n") + data = json.loads(json.dumps(original)) + del data["before"][field] + self.resign(data) + result = self.import_receipt() + self.assertNotEqual(result.returncode, 0) + self.assertNotIn("Traceback", result.stderr) + self.assertEqual(self.card.read_text(), "# Test task\n") + + def test_before_after_heads_do_not_relabel_changed_command(self): + data = self.capture("printf changed > tracked; git add tracked; git commit -qm Changed") + self.assertEqual(data["before"]["head"], self.head) + self.assertEqual(data["after"]["head"], self.git("rev-parse", "HEAD")) + self.assertNotEqual(data["before"]["head"], data["after"]["head"]) + + def test_dirty_and_untracked_contents_have_object_digests(self): + (self.repo / "tracked").write_text("modified\n") + (self.repo / "extra").write_text("untracked\n") + data = self.capture() + self.assertIn("tracked", data["before"]["status"]) + self.assertEqual(data["before"]["untracked"]["extra"], hashlib.sha256(b"untracked\n").hexdigest()) + self.assertNotEqual(data["before"]["tracked_diff_sha256"], hashlib.sha256(b"").hexdigest()) + + def test_nested_cwd_records_untracked_files_from_repository_root(self): + nested = self.repo / "nested" + nested.mkdir() + (nested / "extra").write_text("nested source\n") + data = self.capture(cwd=nested) + self.assertEqual(data["before"]["untracked"]["nested/extra"], + hashlib.sha256(b"nested source\n").hexdigest()) + + def test_after_snapshot_failure_keeps_command_exit_and_output(self): + result = self.run_evidence("mv .git unavailable-git; printf captured; exit 7", RECEIPT=self.receipt) + self.assertEqual(result.returncode, 7) + self.assertTrue(self.receipt.exists()) + data = json.loads(self.receipt.read_text()) + self.assertIsNone(data["after"]) + self.assertTrue(data["after_error"]) + self.assertEqual(data["output"], "captured") + + def test_import_retry_is_idempotent(self): + self.capture() + self.assertEqual(self.import_receipt().returncode, 0) + recorded = self.card.read_text() + self.assertEqual(self.import_receipt().returncode, 0) + self.assertEqual(self.card.read_text(), recorded) + + def test_full_output_is_preserved_not_only_tail(self): + data = self.capture("seq 1 30", TAIL=2) + self.assertTrue(data["output"].startswith("1\n2\n")) + self.assertTrue(data["output"].endswith("29\n30\n")) + + def test_receipt_write_failure_cannot_claim_success(self): + bad = self.base / "missing" / "receipt.json" + result = self.run_evidence("printf hello", RECEIPT=bad) + self.assertNotEqual(result.returncode, 0) + + def test_failed_child_exit_survives_receipt_write_failure(self): + bad = self.base / "missing" / "receipt.json" + result = self.run_evidence("exit 7", RECEIPT=bad) + self.assertEqual(result.returncode, 7) + + def test_capture_does_not_overwrite_an_existing_receipt(self): + self.receipt.write_text("preserve me") + result = self.run_evidence("touch should-not-exist", RECEIPT=self.receipt) + self.assertNotEqual(result.returncode, 0) + self.assertEqual(self.receipt.read_text(), "preserve me") + self.assertFalse((self.repo / "should-not-exist").exists()) + + def test_validate_precheck_checks_result_and_current_base(self): + (self.repo / "Makefile").write_text("precheck:\n\t@echo '[verdict] READY — fixture'\n") + self.capture("make precheck") + valid = self.run_evidence(VALIDATE_RECEIPT=self.receipt) + self.assertEqual(valid.returncode, 0, valid.stderr) + self.git("commit", "--allow-empty", "-qm", "Advance base") + stale = self.run_evidence(VALIDATE_RECEIPT=self.receipt) + self.assertNotEqual(stale.returncode, 0) + + def test_validate_precheck_rejects_successful_arbitrary_command(self): + self.capture("printf '[verdict] READY\\n'") + result = self.run_evidence(VALIDATE_RECEIPT=self.receipt) + self.assertNotEqual(result.returncode, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_task.py b/tests/test_task.py new file mode 100644 index 00000000..27ccaecf --- /dev/null +++ b/tests/test_task.py @@ -0,0 +1,406 @@ +"""Lifecycle regressions against real disposable Git repositories and remotes.""" +import os +from pathlib import Path +import shutil +import shlex +import subprocess +import tempfile +import unittest + + +SOURCE = Path(__file__).resolve().parents[1] + + +class TaskLifecycleTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix="task-lifecycle-") + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + self.repo = self.root / "repo" + self.remote = self.root / "remote.git" + self.env = os.environ.copy() + self.env.update({"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": "/dev/null", + "GIT_ALLOW_PROTOCOL": "file", "GIT_TERMINAL_PROMPT": "0", + "GIT_AUTHOR_NAME": "Lifecycle Test", "GIT_AUTHOR_EMAIL": "test@example.invalid", + "GIT_COMMITTER_NAME": "Lifecycle Test", "GIT_COMMITTER_EMAIL": "test@example.invalid"}) + for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GITEA_TOKEN", "PRECHECK_RECEIPT", + "TASK_PARENT_BRANCH", "TASK_PARENT_PR", "FORCE_ABANDON", "TASK_TARGET_BRANCH"): + self.env.pop(key, None) + self.git(self.root, "init", "--bare", "--initial-branch=main", str(self.remote)) + self.git(self.root, "init", "--initial-branch=main", str(self.repo)) + shutil.copytree(SOURCE / "scripts", self.repo / "scripts") + (self.repo / "Makefile").write_text("precheck:\n\t@bash scripts/task.sh precheck\n") + (self.repo / "tracked").write_text("saved\n") + (self.repo / ".gitignore").write_text("cache/\n") + self.git(self.repo, "add", ".") + self.git(self.repo, "commit", "-m", "Create fixture") + self.git(self.repo, "remote", "add", "origin", str(self.remote)) + self.git(self.repo, "push", "-u", "origin", "main") + + def run_cmd(self, cwd, *args, check=True, env=None): + result = subprocess.run(args, cwd=cwd, env=env or self.env, text=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=45) + if check: + self.assertEqual(result.returncode, 0, result.stdout) + return result + + def git(self, cwd, *args, **kwargs): + return self.run_cmd(cwd, "git", *args, **kwargs) + + def task(self, cwd, command, *args, **kwargs): + if command == "finish" and "env" not in kwargs: + kwargs["env"] = self.env | {"TASK_WRITERS_STOPPED": "1"} + return self.run_cmd(cwd, "bash", str(self.repo / "scripts/task.sh"), command, *args, **kwargs) + + def worktree(self, name="feature/test"): + wt = self.root / "worktree" + self.git(self.repo, "worktree", "add", "-b", name, str(wt), "origin/main") + return wt + + def advance_remote(self): + (self.repo / "new-main").write_text("new main\n") + self.git(self.repo, "add", "new-main") + self.git(self.repo, "commit", "-m", "Advance main") + self.git(self.repo, "push", "origin", "main") + + def add_submodule(self): + child = self.root / "component" + self.git(self.root, "init", "--initial-branch=main", str(child)) + (child / "content").write_text("v1\n") + self.git(child, "add", ".") + self.git(child, "commit", "-m", "Component v1") + self.git(self.repo, "submodule", "add", str(child), "component") + self.git(self.repo, "commit", "-m", "Pin component") + self.git(self.repo, "push", "origin", "main") + return child + + def test_precheck_rejects_unreachable_authority(self): + self.git(self.repo, "remote", "set-url", "origin", str(self.root / "missing.git")) + result = self.task(self.repo, "precheck", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("REMOTE_UNVERIFIED", result.stdout) + + def test_precheck_feature_behind_requires_sync(self): + wt = self.worktree() + self.advance_remote() + result = self.task(wt, "precheck", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("SYNC_REQUIRED", result.stdout) + + def test_sync_materializes_updated_component(self): + child = self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + (child / "content").write_text("v2\n") + self.git(child, "commit", "-am", "Component v2") + latest = self.git(child, "rev-parse", "HEAD").stdout.strip() + self.git(self.repo / "component", "fetch", "origin") + self.git(self.repo / "component", "checkout", "--detach", latest) + self.git(self.repo, "commit", "-am", "Update component") + self.git(self.repo, "push", "origin", "main") + result = self.task(wt, "sync") + self.assertIn("[synced]", result.stdout) + self.assertEqual((wt / "component/content").read_text(), "v2\n") + self.assertEqual(self.git(wt / "component", "rev-parse", "HEAD").stdout.strip(), latest) + + def test_sync_preserves_component_feature_branch(self): + child = self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + self.git(wt / "component", "switch", "-c", "feature/owned") + before = self.git(wt / "component", "rev-parse", "HEAD").stdout + (child / "content").write_text("v2\n") + self.git(child, "commit", "-am", "Component v2") + latest = self.git(child, "rev-parse", "HEAD").stdout.strip() + self.git(self.repo / "component", "fetch", "origin") + self.git(self.repo / "component", "checkout", "--detach", latest) + self.git(self.repo, "commit", "-am", "Update component") + self.git(self.repo, "push", "origin", "main") + result = self.task(wt, "sync", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertNotIn("[synced]", result.stdout) + self.assertEqual(self.git(wt / "component", "rev-parse", "HEAD").stdout, before) + self.assertEqual(self.git(wt / "component", "branch", "--show-current").stdout.strip(), "feature/owned") + + def test_sync_keeps_unchanged_component_branch(self): + self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + self.git(wt / "component", "switch", "-c", "feature/owned") + self.task(wt, "sync") + self.assertEqual(self.git(wt / "component", "branch", "--show-current").stdout.strip(), "feature/owned") + + def test_sync_preserves_dirty_child(self): + self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + (wt / "component/content").write_text("unsaved\n") + self.advance_remote() + before = self.git(wt, "rev-parse", "HEAD").stdout + result = self.task(wt, "sync", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertEqual(self.git(wt, "rev-parse", "HEAD").stdout, before) + self.assertEqual((wt / "component/content").read_text(), "unsaved\n") + + def test_finish_preserves_modified_untracked_and_ignored_files(self): + for kind, rel in (("tracked", "tracked"), ("untracked", "unique"), ("ignored", "cache/unique")): + with self.subTest(kind=kind): + wt = self.root / f"worktree-{kind}" + self.git(self.repo, "worktree", "add", "-b", f"feature/{kind}", str(wt), "origin/main") + path = wt / rel + path.parent.mkdir(exist_ok=True) + path.write_text("unique user content\n") + result = self.task(wt, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue(path.exists(), result.stdout) + self.assertEqual(path.read_text(), "unique user content\n") + + def test_finish_rejects_subdirectory_without_deleting_it(self): + wt = self.worktree() + result = self.task(wt / "scripts", "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue((wt / "scripts/task.sh").is_file(), result.stdout) + + def test_finish_preserves_locked_worktree(self): + wt = self.worktree() + self.git(self.repo, "worktree", "lock", "--reason", "active owner", str(wt)) + result = self.task(wt, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue(wt.exists(), result.stdout) + + def test_finish_preserves_component_untracked_file(self): + self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + unique = wt / "component/unique" + unique.write_text("cannot regenerate\n") + result = self.task(wt, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue(unique.is_file(), result.stdout) + + def test_finish_preserves_component_commits_off_the_pinned_head(self): + self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + child = wt / "component" + pinned = self.git(child, "rev-parse", "HEAD").stdout.strip() + self.git(child, "switch", "-c", "feature/unique-history") + (child / "content").write_text("unique committed work\n") + self.git(child, "commit", "-am", "Preserve this commit") + unique = self.git(child, "rev-parse", "HEAD").stdout.strip() + self.git(child, "checkout", "--detach", pinned) + result = self.task(wt, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue(wt.exists(), result.stdout) + self.assertEqual(self.git(child, "rev-parse", "feature/unique-history").stdout.strip(), unique) + + def nested_component_update(self, attached): + child = self.add_submodule() + inner = self.root / "inner" + self.git(self.root, "init", "--initial-branch=main", str(inner)) + (inner / "inner-content").write_text("inner v1\n") + self.git(inner, "add", ".") + self.git(inner, "commit", "-m", "Inner v1") + self.git(child, "submodule", "add", str(inner), "inner") + self.git(child, "commit", "-m", "Pin inner") + self.git(self.repo / "component", "fetch", "origin") + self.git(self.repo / "component", "checkout", "--detach", "origin/main") + self.git(self.repo, "commit", "-am", "Pin nested component") + self.git(self.repo, "push", "origin", "main") + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + nested = wt / "component/inner" + if attached: + self.git(nested, "switch", "-c", "feature/owned-inner") + before = self.git(nested, "rev-parse", "HEAD").stdout.strip() + (inner / "inner-content").write_text("inner v2\n") + self.git(inner, "commit", "-am", "Inner v2") + latest = self.git(inner, "rev-parse", "HEAD").stdout.strip() + self.git(child / "inner", "fetch", "origin") + self.git(child / "inner", "checkout", "--detach", latest) + self.git(child, "commit", "-am", "Update inner pin") + self.git(self.repo / "component", "fetch", "origin") + self.git(self.repo / "component", "checkout", "--detach", "origin/main") + self.git(self.repo, "commit", "-am", "Update nested component") + self.git(self.repo, "push", "origin", "main") + return wt, nested, before, latest + + def test_sync_preserves_nested_feature_branch_when_parent_pin_changes(self): + wt, nested, before, _ = self.nested_component_update(attached=True) + result = self.task(wt, "sync", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertNotIn("[synced]", result.stdout) + self.assertEqual(self.git(nested, "rev-parse", "HEAD").stdout.strip(), before) + self.assertEqual(self.git(nested, "branch", "--show-current").stdout.strip(), "feature/owned-inner") + + def test_sync_materializes_nested_detached_components(self): + wt, nested, _, latest = self.nested_component_update(attached=False) + self.task(wt, "sync") + self.assertEqual(self.git(nested, "rev-parse", "HEAD").stdout.strip(), latest) + self.assertEqual((nested / "inner-content").read_text(), "inner v2\n") + + def test_finish_clean_worktree_has_accurate_remote_result(self): + wt = self.worktree() + # A missing remote branch is not a successful remote deletion. + result = self.task(wt, "finish") + self.assertFalse(wt.exists(), result.stdout) + self.assertNotIn("本地+远端", result.stdout) + + def test_finish_recycles_clean_submodule_without_shared_config_changes(self): + self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + result = self.task(wt, "finish") + self.assertFalse(wt.exists(), result.stdout) + self.assertTrue((self.repo / "component/content").is_file(), result.stdout) + self.assertEqual(self.git(self.repo, "config", "--get", "submodule.component.url").stdout.strip(), str(self.root / "component")) + + def test_finish_requires_writer_exit_confirmation(self): + wt = self.worktree() + result = self.task(wt, "finish", check=False, env=self.env) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue(wt.exists(), result.stdout) + + def test_finish_deletes_only_merged_remote_branch(self): + wt = self.worktree() + self.git(wt, "push", "-u", "origin", "HEAD") + result = self.task(wt, "finish") + self.assertIn("remote=DELETED", result.stdout) + self.assertEqual(self.git(self.repo, "ls-remote", "--heads", "origin", "refs/heads/feature/test").stdout, "") + + def test_finish_preserves_remote_unmerged_tip(self): + wt = self.worktree() + self.git(self.repo, "switch", "-c", "feature/remote-update") + (self.repo / "remote-work").write_text("another writer") + self.git(self.repo, "add", "remote-work") + self.git(self.repo, "commit", "-m", "Unmerged work") + self.git(self.repo, "push", "origin", "HEAD:refs/heads/feature/test") + self.git(self.repo, "switch", "main") + result = self.task(wt, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue(wt.exists(), result.stdout) + self.assertTrue(self.git(self.repo, "ls-remote", "--heads", "origin", "refs/heads/feature/test").stdout) + + def test_finish_remote_deletion_lease_preserves_concurrent_update(self): + wt = self.worktree() + old = self.git(wt, "rev-parse", "HEAD").stdout.strip() + self.git(wt, "push", "-u", "origin", "HEAD") + self.git(self.repo, "switch", "-c", "feature/racing") + (self.repo / "race-work").write_text("concurrent remote work") + self.git(self.repo, "add", "race-work") + self.git(self.repo, "commit", "-m", "Concurrent remote work") + latest = self.git(self.repo, "rev-parse", "HEAD").stdout.strip() + self.git(self.repo, "push", "origin", "feature/racing") + self.git(self.repo, "switch", "main") + hook = self.repo / ".git/hooks/pre-push" + hook.write_text("#!/bin/sh\n" + shlex.join(["git", "--git-dir", str(self.remote), "update-ref", + "refs/heads/feature/test", latest, old]) + "\n") + hook.chmod(0o755) + result = self.task(wt, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("[partial]", result.stdout) + current = self.git(self.repo, "ls-remote", "--heads", "origin", "refs/heads/feature/test").stdout + self.assertTrue(current.startswith(latest), current) + + def test_sync_commit_failure_never_reports_synced(self): + wt = self.worktree() + (wt / ".task").mkdir() + (wt / ".task/feature-test.md").write_text("# Task\n") + self.git(wt, "add", ".task") + self.git(wt, "commit", "-m", "Create task card") + hook = self.repo / ".git/hooks/pre-commit" + hook.write_text("#!/bin/sh\nexit 1\n") + hook.chmod(0o755) + result = self.task(wt, "sync", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertNotIn("[synced]", result.stdout) + self.assertIn(".task/feature-test.md", self.git(wt, "status", "--porcelain").stdout) + + def test_finish_preserves_main_and_active_index_lock(self): + result = self.task(self.repo, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue((self.repo / "tracked").is_file()) + wt = self.worktree() + index_lock = Path(self.git(wt, "rev-parse", "--path-format=absolute", "--git-path", "index.lock").stdout.strip()) + index_lock.write_text("another writer") + result = self.task(wt, "finish", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertTrue(wt.exists(), result.stdout) + self.assertEqual(index_lock.read_text(), "another writer") + + def test_verify_rejects_recursive_drift_even_when_root_has_local_edits(self): + child = self.add_submodule() + wt = self.worktree() + self.git(wt, "submodule", "update", "--init", "--recursive") + (wt / "tracked").write_text("root implementation edit\n") + self.run_cmd(wt, "python3", "scripts/check-worktree.py", "verify") + (child / "content").write_text("v2\n") + self.git(child, "commit", "-am", "Component v2") + self.git(wt / "component", "fetch", "origin") + self.git(wt / "component", "checkout", "--detach", "origin/main") + result = self.run_cmd(wt, "python3", "scripts/check-worktree.py", "verify", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + + def capture_precheck(self, cwd=None): + receipt = self.root / "precheck.json" + env = self.env | {"RECEIPT": str(receipt), "CMD": "make precheck"} + self.run_cmd(cwd or self.repo, "bash", str(self.repo / "scripts/evidence.sh"), env=env) + return receipt + + def test_start_requires_precheck_receipt_before_creating_worktree(self): + result = self.task(self.repo, "start", "feature/new", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertFalse((self.root / "repo-wt-feature-new").exists(), result.stdout) + + def test_start_imports_precheck_and_creates_four_part_commit(self): + receipt = self.capture_precheck() + self.task(self.repo, "start", "feature/new", env=self.env | {"PRECHECK_RECEIPT": str(receipt)}) + wt = self.root / "repo-wt-feature-new" + self.assertTrue((wt / ".task/feature-new.md").is_file()) + self.assertIn("make precheck", (wt / ".task/feature-new.md").read_text()) + body = self.git(wt, "log", "-1", "--format=%B").stdout + for field in ("Why:", "Evidence:", "Impact:", "Refs:"): + self.assertIn(field, body) + + def test_start_stacked_branch_records_parent_and_main_baseline(self): + base = self.git(self.repo, "rev-parse", "origin/main").stdout.strip() + parent = self.worktree("feature/parent") + (parent / "parent-change").write_text("parent feature") + self.git(parent, "add", "parent-change") + self.git(parent, "commit", "-m", "Parent change") + parent_head = self.git(parent, "rev-parse", "HEAD").stdout.strip() + receipt = self.capture_precheck(parent) + env = self.env | {"PRECHECK_RECEIPT": str(receipt), "TASK_PARENT_BRANCH": "feature/parent", + "TASK_PARENT_PR": "https://example.invalid/repo/pulls/42"} + self.task(parent, "start", "feature/parent-child", env=env) + wt = self.root / "repo-wt-feature-parent-child" + card = (wt / ".task/feature-parent-child.md").read_text() + self.assertIn(f"initial_base_sha)= {base}", card) + self.assertIn(f"parent_head_sha={parent_head}", card) + self.assertIn("https://example.invalid/repo/pulls/42", card) + self.assertEqual((wt / "parent-change").read_text(), "parent feature") + + def test_start_offline_does_not_create_worktree(self): + self.git(self.repo, "remote", "set-url", "origin", str(self.root / "missing.git")) + result = self.task(self.repo, "start", "feature/offline", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn("REMOTE_UNVERIFIED", result.stdout) + self.assertFalse((self.root / "repo-wt-feature-offline").exists()) + + def test_start_rejects_receipt_when_authoritative_base_changed(self): + receipt = self.capture_precheck() + self.advance_remote() + result = self.task(self.repo, "start", "feature/stale", check=False, + env=self.env | {"PRECHECK_RECEIPT": str(receipt)}) + self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertFalse((self.root / "repo-wt-feature-stale").exists()) + + def test_verify_rejects_uninitialized_component(self): + self.add_submodule() + wt = self.worktree() + result = self.run_cmd(wt, "python3", "scripts/check-worktree.py", "verify", check=False) + self.assertNotEqual(result.returncode, 0, result.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_write_guard.py b/tests/test_write_guard.py new file mode 100644 index 00000000..b4ba4e97 --- /dev/null +++ b/tests/test_write_guard.py @@ -0,0 +1,100 @@ +"""Exercise the actual hook against disposable repositories; never write on real main.""" +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + + +GUARD = Path(__file__).resolve().parents[1] / "scripts/zcode/write-guard.sh" + + +class WriteGuardTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.base = Path(self.temp.name) + self.repo = self.base / "repo" + self.repo.mkdir() + self.git("init", "-q", "-b", "main") + self.git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", + "commit", "-q", "--allow-empty", "-m", "Initial") + + def git(self, *args): + return subprocess.run(["git", "-C", str(self.repo), *args], check=True, + capture_output=True, text=True).stdout.strip() + + def invoke(self, payload, cwd=None): + return subprocess.run(["bash", str(GUARD)], input=json.dumps(payload), + cwd=cwd or self.repo, capture_output=True, text=True) + + def write(self, path, **kw): + return self.invoke({"tool_name": "Write", "tool_input": {"file_path": str(path)}, **kw}) + + def test_missing_parent_on_main_is_denied(self): + self.assertEqual(self.write(self.repo / "new/deep/file.txt").returncode, 2) + + def test_relative_path_uses_hook_cwd_not_process_cwd(self): + self.assertEqual(self.invoke({"tool_name": "Write", "cwd": str(self.repo), + "tool_input": {"file_path": "new/file.txt"}}, + cwd=self.base).returncode, 2) + + def test_symlink_into_main_with_missing_parents_is_denied(self): + (self.base / "alias").symlink_to(self.repo, target_is_directory=True) + self.assertEqual(self.write(self.base / "alias/new/file.txt").returncode, 2) + + def test_deleting_main_symlink_to_outside_is_denied(self): + (self.repo / "link").symlink_to(self.base / "outside") + patch = "*** Begin Patch\n*** Delete File: link\n*** End Patch" + result = self.invoke({"tool_name": "ApplyPatch", "tool_input": {"patch_text": patch}}) + self.assertEqual(result.returncode, 2, result.stderr) + + def test_feature_missing_parent_is_allowed(self): + self.git("switch", "-q", "-c", "feature/fixture") + self.assertEqual(self.write(self.repo / "new/deep/file.txt").returncode, 0) + + def test_detached_and_bad_branches_are_denied(self): + for ref in ("--detach", "-c"): + self.git("switch", "-q", ref, "HEAD" if ref == "--detach" else "bad-branch") + self.assertEqual(self.write(self.repo / "new.txt").returncode, 2) + + def test_non_git_destination_is_explicitly_outside_scope(self): + result = self.write(self.base / "notes/new.txt") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("outside-git", result.stderr) + + def test_every_patch_file_and_move_destination_is_checked(self): + self.git("switch", "-q", "-c", "feature/fixture") + other = self.base / "protected" + subprocess.run(["git", "init", "-q", "-b", "main", str(other)], check=True) + for operation in (f"*** Add File: {other}/new/deep.txt\n+x", + f"*** Update File: {self.repo}/old.txt\n*** Move to: {other}/new.txt\n@@\n-a\n+b"): + patch = f"*** Begin Patch\n*** Add File: {self.repo}/safe.txt\n+ok\n{operation}\n*** End Patch" + result = self.invoke({"tool_name": "ApplyPatch", "tool_input": {"patch_text": patch}}) + self.assertEqual(result.returncode, 2, result.stderr) + + def test_valid_multi_file_patch_on_feature_is_allowed(self): + self.git("switch", "-q", "-c", "fix/fixture") + patch = "*** Begin Patch\n*** Add File: new/a.txt\n+a\n*** Delete File: b.txt\n*** End Patch" + self.assertEqual(self.invoke({"tool_name": "ApplyPatch", "tool_input": {"patch_text": patch}}).returncode, 0) + + def test_missing_unknown_and_malformed_write_input_are_denied(self): + for payload in ({"tool_name": "Write", "tool_input": {}}, + {"tool_name": "ApplyPatch", "tool_input": {"patch_text": "garbage"}}, + {"tool_name": "ApplyPatch", "tool_input": {"patch_text": "*** Begin Patch\n*** Add File: a\n+a\n*** Unexpected: b\n*** End Patch"}}, + {"tool_name": "Write", "tool_input": {"file_path": 12}}, + {"tool_name": "UnknownWriter", "tool_input": {"file_path": "a"}}): + with self.subTest(payload=payload): + self.assertEqual(self.invoke(payload).returncode, 2) + + def test_invalid_json_is_denied(self): + result = subprocess.run(["bash", str(GUARD)], input="not-json", cwd=self.repo, + capture_output=True, text=True) + self.assertEqual(result.returncode, 2) + + def test_nonwrite_tools_are_outside_scope(self): + self.assertEqual(self.invoke({"tool_name": "Read", "tool_input": {"file_path": "a"}}).returncode, 0) + + +if __name__ == "__main__": + unittest.main()