[WIP] Replace submodule coupling with versioned dependencies and source workspaces #537

Closed
weichen wants to merge 34 commits from feature/dependency-workspace into main
80 changed files with 26043 additions and 814 deletions
+943 -479
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -21,3 +21,16 @@ runtime-backups/
# e2e.sh 运行工件(构建/种子/日志;scripts/e2e.sh 输出)
.tmp/
# Isolated migration candidates and local component workspaces.
/.migration/
/.workspace/
/go.work
/go.work.sum
/.ci-mirrors/
/.ci-mirrors.env
/.ci-rdm-pins
# Legacy checkouts remain for explicit owner transition and metadata preservation.
/modules/
/deps/
-36
View File
@@ -1,36 +0,0 @@
[submodule "modules/core"]
path = modules/core
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Core.git
[submodule "modules/backend"]
path = modules/backend
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Backend.git
[submodule "modules/frontend"]
path = modules/frontend
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Frontend.git
[submodule "modules/documents"]
path = modules/documents
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Documents.git
[submodule "deps/development-guidelines"]
path = deps/development-guidelines
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Guidelines.git
[submodule "deps/agent-native"]
path = deps/agent-native
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Native.git
[submodule "deps/agent-squad"]
path = deps/agent-squad
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Squad.git
[submodule "deps/agent-intent"]
path = deps/agent-intent
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Intent.git
[submodule "deps/error-code-system"]
path = deps/error-code-system
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Error-Code-System.git
[submodule "deps/logging-system"]
path = deps/logging-system
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Logging-System.git
[submodule "modules/ui"]
path = modules/ui
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-UI.git
[submodule "deps/agent-command"]
path = deps/agent-command
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Command.git
File diff suppressed because one or more lines are too long
+10 -4
View File
@@ -1,6 +1,9 @@
# Agent 工作说明(Agent-System-Dev)
本仓开发遵循 Development-Guidelines(见 `deps/development-guidelines`,gitlink 固定版本):
本仓开发遵循固定 Development-Guidelines:新布局从 `components.lock.json` 准备
`.workspace/Development-Guidelines`(启动检查可先提供仓外固定缓存);旧提交仍读
`deps/development-guidelines` 的 gitlink。规则载入、版本权威及分支过渡见
[policy/dependencies.md](policy/dependencies.md),首次准备失败不得跳过必读。
核心规则与术语见其 `core/`,按任务触发阅读 `standards/` 专题;通用交付依据为 `workflows/delivery.md`。
本项目的需求、设计试验、实施调整及交付**必读 [policy/delivery.md](policy/delivery.md)**:
需求、方案、实施与验收默认逐阶段人工评审,阶段内迭代、按影响回退;代码 Review 与 PR
@@ -15,7 +18,7 @@
`@ui/motion.css` 的 `mk-*` 工具类;折叠一律用 `@ui` Collapsible 原语,禁止 `<details>`。
- 守护测试(UI 仓与 Frontend 仓各一份 `design-guard.test.ts`)以棘轮拦截新增裸色值、
私有 `@keyframes` 与行内字号;风格调整须在同一变更集中关联 UI 令牌 PR 与 Dev 的
`DESIGN.md`/gitlink 组合 PR,并验证精确组件 SHA(跨仓不要求两个文件出现在同一 PR)。
`DESIGN.md`/依赖锁组合 PR,并验证精确组件 SHA(跨仓不要求两个文件出现在同一 PR)。
- 异步状态只有一个所有者。事件按 seq 处理,过期事件丢弃。
## 任务启动门禁(强制)
@@ -81,7 +84,10 @@
- 主题行:英文祈使句一行(≤100 列,不以句点结尾);body 写 `Why: / Evidence: / Impact: / Refs:`
四段;任务卡与 PR 描述为同一份文档。
- 跨仓 gitlink 变更须经组合验证(`make test`)后在 Dev 仓推进;受保护分支经 PR 合入。
- 跨仓语言依赖/应用源码锁变更须经固定组合验证(`make test`)后在 Dev 仓推进;
旧布局 gitlink 变更仍按其固定组合核验。受保护分支经 PR 合入。
功能仓源码只提交到所属仓;按需编辑、开发覆盖与旧任务显式过渡遵循
[依赖与过渡入口](policy/dependencies.md)。不得修改其他活动工作区或删除仍被引用的 Git 管理数据。
- **高后果操作双钥匙**:改写历史、解除/变更分支保护、删除归档引用、发布版本——Agent 只准备
方案与命令,由用户确认并亲手执行;Agent 不得自行实施(baseline 硬边界 1 的授权范围要求)。
- 模块边界、公开契约与隔离档位见 `policy/modules/`(如有)与 PROJECT.md。
@@ -102,7 +108,7 @@
- **Web / API(HTTPS)**:`https://git-home.u1680832.nyat.app:30319`(Gitea 1.27.3,
API 前缀 `/api/v1`;偶发 broken pipe / 超时,重试即可)。
- **Git over SSH**:`ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/<Repo>.git`
(.gitmodules 与各仓 origin 均为此格式)。
(components.json 与各仓 origin 使用此传输格式)。
- **API token**:`~/.secrets/gitea`,两行键值 `URL_PREFIX=…` 与
`ACCESS_TOKEN=<40 位 hex>`;取值 `grep ACCESS_TOKEN ~/.secrets/gitea | cut -d= -f2-`,
用作 `Authorization: token <值>` 请求头。**token 值不入仓、不回显、不写进产出物**
+129 -15
View File
@@ -2,18 +2,36 @@
# 部署约束:非容器化;默认产物为单二进制(modules/backend,内嵌前端与嵌入式存储)。
SHELL := /bin/bash
SOURCE_MODE := $(if $(wildcard components.json),workspace,legacy)
ifeq ($(SOURCE_MODE),workspace)
CORE := .workspace/Agent-System-Core
BACKEND := .workspace/Agent-System-Backend
FRONTEND := .workspace/Agent-System-Frontend
UI := .workspace/Agent-System-UI
NATIVE := .workspace/Agent-Native
INTENT := .workspace/Agent-Intent
SQUAD := .workspace/Agent-Squad
COMMAND := .workspace/Agent-Command
DOCUMENTS := .workspace/Agent-System-Documents
else
CORE := modules/core
BACKEND := modules/backend
FRONTEND := modules/frontend
UI := modules/ui
NATIVE := deps/agent-native
INTENT := deps/agent-intent
SQUAD := deps/agent-squad
COMMAND := deps/agent-command
DOCUMENTS := modules/documents
endif
BIN := bin/agent-system
EMBED_DIR := $(BACKEND)/web/dist
PLUGINS := plugins
# 版本溯源(release-operations:运行制品须可对上固定组合)。无 tag 时 --always 给短 SHA。
COMBO := core=$(shell git -C $(CORE) rev-parse --short HEAD 2>/dev/null)+backend=$(shell git -C $(BACKEND) rev-parse --short HEAD 2>/dev/null)+frontend=$(shell git -C $(FRONTEND) rev-parse --short HEAD 2>/dev/null)+ui=$(shell git -C $(UI) rev-parse --short HEAD 2>/dev/null)+agentcmd=$(shell git -C deps/agent-command rev-parse --short HEAD 2>/dev/null)+documents=$(shell git -C modules/documents rev-parse --short HEAD 2>/dev/null)+native=$(shell git -C deps/agent-native rev-parse --short HEAD 2>/dev/null)+squad=$(shell git -C deps/agent-squad rev-parse --short HEAD 2>/dev/null)+intent=$(shell git -C deps/agent-intent rev-parse --short HEAD 2>/dev/null)
COMBO := core=$(shell git -C $(CORE) rev-parse --short HEAD 2>/dev/null)+backend=$(shell git -C $(BACKEND) rev-parse --short HEAD 2>/dev/null)+frontend=$(shell git -C $(FRONTEND) rev-parse --short HEAD 2>/dev/null)+ui=$(shell git -C $(UI) rev-parse --short HEAD 2>/dev/null)+agentcmd=$(shell git -C $(COMMAND) rev-parse --short HEAD 2>/dev/null)+documents=$(shell git -C $(DOCUMENTS) rev-parse --short HEAD 2>/dev/null)+native=$(shell git -C $(NATIVE) rev-parse --short HEAD 2>/dev/null)+squad=$(shell git -C $(SQUAD) rev-parse --short HEAD 2>/dev/null)+intent=$(shell git -C $(INTENT) rev-parse --short HEAD 2>/dev/null)
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null)+$(COMBO)
.PHONY: help sync status build frontend svc svc-list svc-info svc-commits svc-history svc-update svc-recover svc-watchdog test-backend test-backend-pg test-multi-instance test-lifecycle test-frontend test-ui test-core test-agentcmd test-pins test-libs dev pin clean setup precheck task-start task-resume task-sync task-close task-finish facts-verify evidence version
.PHONY: help sync status workspace-prepare component-edit component-unlink workspace-status workspace-transition build frontend svc svc-list svc-info svc-commits svc-history svc-update svc-recover svc-watchdog test-backend test-backend-pg test-multi-instance test-lifecycle test-frontend test-ui test-core test-agentcmd test-pins test-libs dev pin clean setup precheck task-start task-resume task-sync task-close task-finish facts-verify evidence version
version:
@echo "agent-system $(VERSION)"
@@ -57,14 +75,33 @@ task-finish:
evidence:
@SRC='$(SRC)' TAIL='$(TAIL)' bash scripts/evidence.sh
# workspace.py owns source identity, locks and development overlays.
workspace-prepare:
@python3 scripts/workspace.py --root . prepare $(if $(NAME),--name "$(NAME)") $(if $(filter 1,$(REFRESH_FIXED)),--refresh-fixed)
component-edit:
@test -n "$(NAME)" || { echo "usage: make component-edit NAME=<component> [BRANCH=feature/<slug>]"; exit 2; }
@python3 scripts/workspace.py --root . edit --name "$(NAME)" $(if $(BRANCH),--branch "$(BRANCH)")
component-unlink:
@test -n "$(NAME)" || { echo "usage: make component-unlink NAME=<component>"; exit 2; }
@python3 scripts/workspace.py --root . unlink --name "$(NAME)"
workspace-status:
@python3 scripts/workspace.py --root . status $(if $(NAME),--name "$(NAME)")
# Default report is read-only. Only explicit ARGS=--apply copies into new roots.
workspace-transition:
@python3 scripts/workspace.py --root . transition $(if $(NAME),--name "$(NAME)") $(ARGS)
# 外部事实登记表逐条重验(--update 刷新 PASS 项 last_verified)。
facts-verify:
@bash scripts/facts-verify.sh $(ARGS)
help:
@echo "make sync 物化全部 submodule(递归)"
@echo "make build 构建单二进制 bin/agent-system(契约同步→前端构建→embed→静态链接)"
@echo "make test 全量测试(boundaries + pins + libs + core + backend + frontend + ui + agent-command)"
@echo "make sync 准备固定应用/资料/规则源码(旧提交保留 submodule 模式)"
@echo "make build 固定版本隔离构建 bin/agent-system(前端资产→embed→单二进制)"
@echo "make test 固定组合测试(治理 + boundaries/architecture + 组件与前端测试)"
@echo "make test-governance 临时真实仓库中的任务、证据、写守卫与 CI 凭据回归"
@echo "make svc 服务级操作:make svc ENV=<name> [ACTION=deploy|start|stop|restart|status|logs] [ARGS='--from dev@<sha> --tag ...']"
@echo "make svc-list 服务总览:make svc-list [ARGS='--tag core'];数据库运维:scripts/svc.sh db create|dsn|backup|backups|restore|clone|copy|ls|set"
@@ -75,16 +112,32 @@ help:
@echo "make svc-recover 自恢复:拉回 autostart=1 但已死的服务:make svc-recover [ARGS='dev --quiet']"
@echo "make svc-watchdog 自恢复看护:make svc-watchdog ARGS='install|remove|status'(systemd 用户 timer)"
@echo "make dev 开发模式说明(前后端分离热更新)"
@echo "make pin 推进 submodule 指针到各仓远端 main(提交前须 make test 通过)"
@echo "make status submodule 状态"
@echo "make workspace-prepare 准备固定源码;component-edit NAME=<id> 按需编辑"
@echo "make workspace-transition [NAME=<id>] 默认只报告;ARGS=--apply 显式准备新现场"
ifeq ($(SOURCE_MODE),workspace)
sync: workspace-prepare
status: workspace-status
else
sync:
git submodule update --init --recursive
status:
@git submodule status
endif
# 契约同步:Core 的 TS 类型镜像到前端与 UI 组件库(Core 为权威,单一来源)。
ifeq ($(SOURCE_MODE),workspace)
# Contracts are versioned npm package exports; frontend builds materialize them.
contracts: frontend
frontend:
python3 scripts/build-composition.py --root . --target frontend
build:
python3 scripts/build-composition.py --root . --target build
else
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
@@ -103,6 +156,22 @@ build: frontend
cd $(BACKEND) && CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o ../../bin/storage-migrate ./cmd/storage-migrate
@echo "OK: $(BIN)(版本 $(VERSION);随附插件目录 $(PLUGINS)/)+ bin/storage-migrate(协作存储 U4 迁移工具)"
endif
ifeq ($(SOURCE_MODE),workspace)
# Named checks exercise developer sources, prepare selected versions, and keep
# GOWORK=off unless LOCAL_OVERLAY=1 explicitly selects registered component links.
.PHONY: dev-backend dev-frontend
LOCAL_CHECK := python3 scripts/component-check.py --root .
test-lifecycle:
@test -n "$${AGENT_PG_TEST_DSN}" || { echo "test-lifecycle: AGENT_PG_TEST_DSN 未设置(真实 PostgreSQL 必需,不静默跳过)"; exit 1; }
@test -n "$${SVC_RDM_SRC_DIR}" || { echo "test-lifecycle: SVC_RDM_SRC_DIR 未设置(RDM 检出必需,不静默跳过)"; exit 1; }
bash scripts/fetch-rdm-pin.sh tests/integration/lifecycle/rdm.pin >/dev/null
python3 tests/integration/lifecycle/lifecycle_suite.py
test-core test-backend test-backend-pg test-multi-instance test-frontend test-ui test-agentcmd eval-deliverables dev-backend dev-frontend:
$(LOCAL_CHECK) --target $@ $(if $(filter 1,$(LOCAL_OVERLAY)),--overlay)
else
test-core:
cd $(CORE) && go test ./...
@@ -143,24 +212,37 @@ test-ui: contracts
cd $(UI) && npm ci --no-fund --no-audit && npx tsc --noEmit && npx vitest run && node scripts/check-knip.mjs
test-agentcmd:
cd deps/agent-command && go vet ./... && go test -count=1 ./...
cd $(COMMAND) && go vet ./... && go test -count=1 ./...
endif
test-boundaries:
bash scripts/lint-boundaries.sh
# 容器仓 deps/ gitlink 与 Dev 顶层指针一致。读索引中的 gitlink。
ifeq ($(SOURCE_MODE),workspace)
test-pins:
@if [ -n "$${COMPOSITION_SOURCE_MAP:-}" ]; then bash scripts/check-pins.sh; else python3 scripts/workspace.py --root . verify; fi
else
test-pins:
bash scripts/check-pins.sh
endif
# 三个库在各自模块内测试。不用 go.work:工作区 MVS 会抬高 golang.org/x 版本。
ifeq ($(SOURCE_MODE),workspace)
test-libs:
cd deps/agent-intent && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd deps/agent-native && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd deps/agent-squad && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd deps/agent-squad/adapters/intent && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd deps/agent-squad/adapters/native && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd deps/agent-squad/adapters/sqlite && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd deps/agent-squad/cmd/agent-squad && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
$(LOCAL_CHECK) --target $@ $(if $(filter 1,$(LOCAL_OVERLAY)),--overlay)
else
test-libs:
cd $(INTENT) && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd $(NATIVE) && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd $(SQUAD) && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd $(SQUAD)/adapters/intent && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd $(SQUAD)/adapters/native && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd $(SQUAD)/adapters/sqlite && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
cd $(SQUAD)/cmd/agent-squad && GOWORK=off go vet ./... && GOWORK=off go test -count=1 ./...
endif
plugin-new:
@test -n "$(NAME)" || (echo "用法:make plugin-new NAME=<id>"; exit 2)
@@ -175,9 +257,23 @@ test-worktree:
python3 scripts/check-worktree.py verify
# 即使 make -j,也先确认实际被测子仓与固定组合一致。
ifeq ($(SOURCE_MODE),workspace)
test-libs test-core test-backend test-frontend test-ui test-agentcmd: | test-worktree
ifeq ($(COMPOSITION_SOURCE_MAP),)
test-boundaries test-pins: | test-worktree
endif
else
test-boundaries test-pins test-libs test-core test-backend test-frontend test-ui test-agentcmd: | test-worktree
endif
ifeq ($(SOURCE_MODE),workspace)
# Formal composition uses isolated sources and GOWORK=off. Developer overlays
# remain untouched; the composition executor runs the component gates.
test:
python3 scripts/build-composition.py --root . --target test
else
test: test-worktree test-governance test-boundaries test-pins test-libs test-core test-backend test-frontend test-ui test-agentcmd
endif
# 运行演示(mock 模式):单二进制 + 示例插件 + 嵌入式存储,浏览器 http://127.0.0.1:8787
# 环境服务管理(scripts/svc.sh):deploy = 停旧 → 构建 → 启动;dev/prod 相互隔离。
@@ -210,6 +306,10 @@ svc-update:
demo: build
$(BIN) serve --addr 127.0.0.1:8787 --data ./data --plugins $(PLUGINS) --mock
ifeq ($(SOURCE_MODE),workspace)
dev:
$(LOCAL_CHECK) --target dev $(if $(filter 1,$(LOCAL_OVERLAY)),--overlay)
else
dev:
@echo "开发模式(原生,非容器):"
@echo " 1. make sync && make contracts"
@@ -217,7 +317,14 @@ dev:
@echo " 3. 终端 B:cd $(FRONTEND) && npm ci && npm run dev # Vite 代理到 8787"
@echo " 4. mock 演示:设置→运行时→演示模式;或终端 A 加 --mock"
endif
# 推进指针(多仓纪律:先验证后固定)。
ifeq ($(SOURCE_MODE),workspace)
pin:
@echo "pin: update consumer go.mod/package-lock.json or components.lock.json explicitly, then verify the fixed composition" >&2
@exit 2
else
pin:
@for m in modules/core modules/backend modules/frontend modules/ui modules/documents deps/agent-native deps/agent-squad deps/agent-intent deps/error-code-system deps/logging-system deps/development-guidelines deps/agent-command; do \
(git -C $$m fetch -q origin && git -C $$m checkout -q origin/main) || echo "skip $$m"; \
@@ -225,5 +332,12 @@ pin:
@echo "submodule 指针已推进;请 make test 后 git add .gitmodules modules deps && git commit"
@scripts/check-pins.sh || echo "pin: check-pins 未通过,上面列出的容器需要重钉;pin 本身不失败"
endif
ifeq ($(SOURCE_MODE),workspace)
clean:
rm -f $(BIN) bin/storage-migrate
else
clean:
rm -rf $(BIN) $(FRONTEND)/dist $(FRONTEND)/node_modules $(UI)/node_modules data
endif
+39 -27
View File
@@ -1,53 +1,59 @@
# Agent-System-Dev 项目事实
- 用途:开发集成父仓(gitlink 权威);非分发单元。
- 组合:modules/{core,backend,frontend,ui,documents} + deps/{development-guidelines,agent-native,agent-squad,agent-intent,agent-command,error-code-system,logging-system}。
- 用途:独立功能仓的开发与固定组合入口;非分发单元。
- 组合:组件身份见 components.json;应用/规范/资料提交见 components.lock.json,Go/npm 库版本由消费方语言锁决定。按需源码位于被忽略的 .workspace/。
- 部署:非容器;Releases 仓从本仓固定提交导出版本。
## 方向与规划入口
长期方向、目标及跨任务规划从过程仓的 [项目方向与规划索引](modules/documents/docs/proposals/INDEX.md)
查找主记录、里程碑和关联任务;原始正文沿用 [方案与规划目录](modules/documents/docs/proposals/)。
长期方向、目标及跨任务规划从过程仓的 [项目方向与规划索引](.workspace/Agent-System-Documents/docs/proposals/INDEX.md)
查找主记录、里程碑和关联任务;原始正文沿用 [方案与规划目录](.workspace/Agent-System-Documents/docs/proposals/)。
确立或调整具体方向时,在本入口维护其主记录链接;正文、确认依据和进展仍以该主记录为准。
目录中的既有文档不因被收录而自动成为已批准规划。记录、任务关联及收尾建议按
[交付规范的长期方向与任务衔接](policy/delivery.md#长期方向与任务衔接) 执行。
- [用户、职位角色与分层权限统一方案 r1](modules/documents/docs/proposals/2026-10-13-identity-role-permission-plan.md):用户已批准,首批仅 I1(U1–U2);
[权限合同](modules/documents/docs/proposals/2026-10-13-iam-i1-permission-contract.md) 冻结边界,
[I1 实施候选与评审记录](modules/documents/docs/proposals/2026-10-13-iam-i1-review.md) 维护实际结果与剩余限制;后续单元须另行授权。
- [版本化依赖与按需源码工作区改造任务](.task/feature-dependency-workspace.md):
已批准方案和实施计划由固定 Documents 的 `docs/proposals/2026-10-13-versioned-dependencies-and-workspaces.md`
与对应 `-implementation-plan.md` 保存。用户授权重构与自检连续推进,特别要求在途分支平稳过渡;
实施进展和命令证据见任务卡,正式验收、共享部署及发布仍按原审批边界。
[项目依赖与过渡规则](policy/dependencies.md) 是具体工作入口。
- [快慢通道展示与动态工作摘要方案 r1.1](modules/documents/docs/proposals/2026-10-12-fast-slow-channel-display.md):
- [用户、职位角色与分层权限统一方案 r1](.workspace/Agent-System-Documents/docs/proposals/2026-10-13-identity-role-permission-plan.md):用户已批准,首批仅 I1(U1–U2);
[权限合同](.workspace/Agent-System-Documents/docs/proposals/2026-10-13-iam-i1-permission-contract.md) 冻结边界,
[I1 实施候选与评审记录](.workspace/Agent-System-Documents/docs/proposals/2026-10-13-iam-i1-review.md) 维护实际结果与剩余限制;后续单元须另行授权。
- [快慢通道展示与动态工作摘要方案 r1.1](.workspace/Agent-System-Documents/docs/proposals/2026-10-12-fast-slow-channel-display.md):
对话即时答复、后台动态发现与来源、四层阅读、真实状态、并发/恢复及 U1–U4;
2026-10-12 方案设计已批准(阶段 1–4),首批仅 U1;
[U1 实施计划](modules/documents/docs/proposals/2026-10-12-fast-slow-channel-display-u1-plan.md) 待阶段 5 批准后启动业务代码;
[U1 实施计划](.workspace/Agent-System-Documents/docs/proposals/2026-10-12-fast-slow-channel-display-u1-plan.md) 待阶段 5 批准后启动业务代码;
业务实施、模型实验、运行验收与部署 NOT_RUN。
新增方向:[Generative UI 调研与插件方案 r2](modules/documents/docs/proposals/2026-10-10-generative-ui-plugin.md)。
新增方向:[Generative UI 调研与插件方案 r2](.workspace/Agent-System-Documents/docs/proposals/2026-10-10-generative-ui-plugin.md)。
2026-10-11 用户裁定首试点(对话内分析页)、可信编译插件交付与试验参数;U1 受控试验当日完成
(结构分化 6/6、生成走工具参数通道、首期自研 renderer,详见方案「U1 结果与 r2 修订」);
U2 起实施与部署分别评审。
- [多人协作存储与分布式演进方案](modules/documents/docs/proposals/2026-10-11-collaboration-storage-and-distribution.md):
- [多人协作存储与分布式演进方案](.workspace/Agent-System-Documents/docs/proposals/2026-10-11-collaboration-storage-and-distribution.md):
10–100 人、多项目、多会话并行;PostgreSQL 团队档、迁移恢复与多实例执行。方案 r1 已批准
(2026-10-11,用户指示「按方案开始改造」),实施自首选增量 U1+U2 启动。
- [RDM 两树一网集成、完善与追溯审计展示方案](modules/documents/docs/proposals/2026-10-11-rdm-two-trees-trace-audit-integration.md):
- [RDM 两树一网集成、完善与追溯审计展示方案](.workspace/Agent-System-Documents/docs/proposals/2026-10-11-rdm-two-trees-trace-audit-integration.md):
每项目一套笔记树、文档树与追溯网;项目绑定与读取授权闭环、原子事件历史、统一审计读服务
与四入口工作区。方案 r1 已获用户评审通过(2026-10-11,五阶段一并,KTD4 双层管理员裁定);
U1–U5 全部实施合入(RDM PR #195/#197/#201/#213;Dev PR #368,2026-10-11;超管槽待基座能力
接线,申请见 RDM 仓 docs/proposals/2026-10-11-u1-base-extension-requests.md);
U6 团队存储独立后续;运行验收属部署环境。
- [RDM 模板与两树一网对话文档生成方案 r1.1](modules/documents/docs/proposals/2026-10-11-rdm-template-dialog-generation.md):69 类历史资产盘点、模板版本与章节实例化、持久提案、文档级审签及 DOCX;待用户评审,业务实施与运行验收 NOT_RUN。 首批项目建议书/竞品分析、随后扩展全部文档已确认(2026-10-11),其余方案与实施仍待评审。
- [RDM 模板与两树一网对话文档生成方案 r1.1](.workspace/Agent-System-Documents/docs/proposals/2026-10-11-rdm-template-dialog-generation.md):69 类历史资产盘点、模板版本与章节实例化、持久提案、文档级审签及 DOCX;待用户评审,业务实施与运行验收 NOT_RUN。 首批项目建议书/竞品分析、随后扩展全部文档已确认(2026-10-11),其余方案与实施仍待评审。
- [RDM 最终目标核对与完整交付方案 r1](modules/documents/docs/proposals/2026-10-11-rdm-final-target-completion.md):系统级项目、上限内权限覆盖、私域协作、全部文档三权与完整业务链;34 UR/68 SR/10 F 逐条映射;方案已随 #340 合入(2026-10-11),U1 需求基线核对经 RDM #199/#200 落地,U2 起未启动。
- [RDM 最终目标核对与完整交付方案 r1](.workspace/Agent-System-Documents/docs/proposals/2026-10-11-rdm-final-target-completion.md):系统级项目、上限内权限覆盖、私域协作、全部文档三权与完整业务链;34 UR/68 SR/10 F 逐条映射;方案已随 #340 合入(2026-10-11),U1 需求基线核对经 RDM #199/#200 落地,U2 起未启动。
- [服务重启与全量数据恢复方案 r3.1](modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md):
- [服务重启与全量数据恢复方案 r3.1](.workspace/Agent-System-Documents/docs/proposals/2026-10-12-service-restart-data-recovery.md):
svc 启停、异常进程/主机退出后的依赖、配置/数据身份、任务/HITL/副作用、文件与组合备份恢复;
r2 已批准。r3.1 增补 D1 实例身份收尾、D2 可回滚切换、D3 真实组合门禁,并记录 2026-10-12 rdmcand 数据归属:会话不在 dev 库的 rdmcand 窗口内,领域事件为空,不跨实例搬迁。
D1 与 D2 已合入 Backend #184、RDM #291、Dev #485。D3 的 `make test-lifecycle` 跑真实组合用例;登录仍是 NOT_RUN,依赖账户方案 U7/U5,不在本轮公共化账户 CLI。r3 其余未批条款仍待人工评审。
- [svc 统一部署与一次性登录 Key 方案 r1](modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md):
- [svc 统一部署与一次性登录 Key 方案 r1](.workspace/Agent-System-Documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md):
二进制/源码统一部署、环境准备、无初始密码开户与账户恢复;一次性 Key 仅兑换设密受限会话;
2026-10-12 已批准;U1/U4 实施启动中;规格见 `docs/svc-deploy/`。
@@ -55,19 +61,25 @@ U2 起实施与部署分别评审。
## 验证
规范修订:[最终验证与合入调度 r1](policy/merge-queue.md) 及[U1–U6 实施计划](modules/documents/docs/proposals/2026-10-13-merge-queue-implementation-plan.md)已批准。通用规范与计划已分别经 PR 合入,本候选组合固定实际合入 SHA,见[采纳记录](policy/adoption.md)。同步幂等化、显式局部恢复和源码外证据基础已实施;Dev 最新组合验收及平台调度模式启用仍按各自门禁办理。
规范修订:[最终验证与合入调度 r1](policy/merge-queue.md) 及[U1–U6 实施计划](.workspace/Agent-System-Documents/docs/proposals/2026-10-13-merge-queue-implementation-plan.md)已批准。通用规范与计划已分别经 PR 合入,本候选组合固定实际合入 SHA,见[采纳记录](policy/adoption.md)。同步幂等化、显式局部恢复和源码外证据基础已实施;Dev 最新组合验收及平台调度模式启用仍按各自门禁办理。
`make test` 先核对子模块工作树与固定指针,再运行治理脚本、公开边界、跨仓指针、库及各模块
测试。实际目标以根 Makefile 为准。`make test-governance` 可独立检查开发门禁,使用临时
真实仓库,不访问生产数据;操作与证据要求见 [开发门禁](policy/governance.md)。
`make setup` 启用钩子。新 clone 无需递归克隆;启动门禁提供固定规则,
`make workspace-prepare` 准备固定应用与资料。`make component-edit NAME=Agent-Native`
按消费方实际版本创建功能仓编辑分支并生成本地 go.work;解除用 `make component-unlink`。
`make build` 在 backend 模块内编译,replace 指向该模块自己的 `deps/`;
`scripts/check-pins.sh` 核对这些 gitlink 与 Dev 顶层指针,`make test-worktree` 另核对
真正被测的组件工作树。两者分别验证固定组合与实际检出,不能相互替代。
`make test` / `make build` 在隔离目录使用已提交的清单、应用源码、语言依赖和前端包,
关闭 Go 开发覆盖,拒绝本地 replace/link,构建 frontend dist 后装配 backend embed。
输出 bin/resolved-manifest.json,含实际来源、解析版本及资产摘要;工作区本地覆盖不会改变正式候选。
未加 go.work:工作区最小版本选择会抬高 backend 单独构建使用的部分 `golang.org/x` 版本。
发布消费方须从固定 Dev 提交递归物化后运行 `make build`,再核验制品与实际运行状态。
`make test-governance` 在临时真实仓库验证启动、任务同步、证据、迁移保留和拒绝路径;
`make test-pins` 在新布局核验固定来源,历史提交仍执行旧 gitlink 检查。
公开边界与架构检查使用组合器的固定来源映射,详细操作见 [开发门禁](policy/governance.md)。
- [渐进工作台界面重设计 r0.2](modules/documents/docs/proposals/2026-10-12-work-companion-ui-design.md):用户已授权连续实施;[计划 r1](modules/documents/docs/proposals/2026-10-12-work-companion-ui-plan.md)覆盖任务摘要、对话/输入、窄屏和真实状态投影;进展见对应任务卡。
RDM 从自身 go.mod 消费远端 Backend,`COMPOSITION_ROOT=<已提交 Dev 根目录> make product`
隔离 vendor 并注入固定前端资产,随产物记录其实际 Backend 版本;不改模块缓存。
`product-smoke` 的临时运行验证与正式验收、共享部署分开记录。真实 PG/Redis、生命周期等
专项门禁保持原环境要求;未提供环境不会由本地组合测试冒充通过。
- [系统项目、成员工作入口与私域协作闭环 r1](modules/documents/docs/proposals/2026-10-13-system-project-membership.md):2026-10-13 用户批准阶段 1–9 连续推进;统一全局项目身份与名称、成员个人会话、每项目两树一网和私域权限;当前组合验证/CI/tag 消费推进中,部署与线上修复未授权。
- [渐进工作台界面重设计 r0.2](.workspace/Agent-System-Documents/docs/proposals/2026-10-12-work-companion-ui-design.md):用户已授权连续实施;[计划 r1](.workspace/Agent-System-Documents/docs/proposals/2026-10-12-work-companion-ui-plan.md)覆盖任务摘要、对话/输入、窄屏和真实状态投影;进展见对应任务卡。
- [系统项目、成员工作入口与私域协作闭环 r1](.workspace/Agent-System-Documents/docs/proposals/2026-10-13-system-project-membership.md):2026-10-13 用户批准阶段 1–9 连续推进;统一全局项目身份与名称、成员个人会话、每项目两树一网和私域权限;当前组合验证/CI/tag 消费推进中,部署与线上修复未授权。
+20 -30
View File
@@ -1,40 +1,30 @@
# Agent-System-Dev
Agent-System 的开发集成仓(父仓):以 submodule 固定全部模块组合,承载跨仓构建、测试编排与原生(非容器)开发环境。**非分发单元**——发布物在 Agent-System-Releases。
Agent-System 的开发集成入口,组织独立功能仓、固定依赖和跨仓构建。只需手动 clone 本仓;应用与资料由源码清单准备,Go/npm 库由消费方下载。发布物仍在 Agent-System-Releases,默认采用原生单二进制。
## 组合结构
```
modules/core Agent-System-Core 核心契约(领域模型/事件协议/插件 API/设置与 UI 词汇)
modules/backend Agent-System-Backend 后端单二进制(内嵌前端与嵌入式 SQLite)
modules/frontend Agent-System-Frontend 前端(React 19 + Vite + TS)
modules/documents Agent-System-Documents 系统文档与过程记录
modules/ui Agent-System-UI UI 组件库(词汇渲染器/设计令牌/收件箱卡片工作区组件)
deps/development-guidelines 开发规范(gitlink 固定版本)
deps/agent-native deps/agent-squad deps/agent-intent 生态执行/编排/意图
deps/agent-command Agent-Command 命令总线基础库(spec/注册表/危险分级/审计信封)
deps/error-code-system deps/logging-system 基础设施
plugins/ 示例插件与插件开发参考(P4 起填充)
```
父仓 gitlink 是各组件源码版本的权威;构建、审核与发布固定提交,不执行 `submodule update --remote`。
## 常用命令
## 开始开发
```bash
make sync # 物化全部 submodule(递归)
make build # 构建单二进制 bin/agent-system
make test # 全量测试
make dev # 开发模式说明(前后端分离热更新)
make setup
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
# 在返回的任务 worktree 中执行
make workspace-prepare
make component-edit NAME=Agent-Native BRANCH=feature/my-native-change
make workspace-status
make component-unlink NAME=Agent-Native
```
## 约束(全局)
按需源码是 .workspace 下的普通独立 Git 仓。功能修改提交到所属仓;消费方通过依赖 PR 采用新版本,不需要为了保存源码提交而同步推进全部仓库。
- 非容器化部署:默认单二进制,必要时拆分多个原生二进制;不发布容器镜像。
- 依赖方向:frontend→core←backend;插件只依赖 core;生态仓依赖纪律沿用各仓既有约定。
- 开发规范:`deps/development-guidelines`(见各仓 AGENTS.md)。
`make test` 与 `make build` 使用已提交的固定组合,在隔离目录关闭开发覆盖,生成 bin/agent-system、storage-migrate 与 resolved-manifest.json。源码身份见 components.json;应用/规范/资料提交见 components.lock.json;库的实际版本见消费方 go.mod/go.sum、package-lock.json。
## 文档
## 规范与过渡
设计方案:`modules/documents/docs/proposals/2026-09-29-system-skeleton-design-and-plan.md`(含 D1–D16 决策记录)。
本机服务管理(后端/前端/数据库部署、生命周期、备份克隆):`docs/svc.md`(`scripts/svc.sh`)。
- [项目事实与验证入口](PROJECT.md)
- [版本依赖、按需编辑和旧分支过渡](policy/dependencies.md)
- [阶段交付](policy/delivery.md)与[开发门禁](policy/governance.md)
旧提交继续采用自己的 submodule 模式;旧任务同步新主干前由 owner 保存现场并显式过渡。迁移工具默认只报告,不改动其他活动工作区。规范来自固定 Development-Guidelines,过程文档来自固定 Agent-System-Documents;新项目通过独立采纳采用通用规则。
本机服务管理见 [docs/svc.md](docs/svc.md)。依赖改造不授权共享服务部署、数据库操作或发布。
+198
View File
@@ -0,0 +1,198 @@
{
"schema": 1,
"components": [
{
"id": "Logging-System",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Logging-System.git",
"path": ".workspace/Logging-System",
"legacy_path": "deps/logging-system",
"role": "go-library",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Logging-System.git",
"legacy": "logging-system",
"directory": "."
}
],
"target": "main"
},
{
"id": "Error-Code-System",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Error-Code-System.git",
"path": ".workspace/Error-Code-System",
"legacy_path": "deps/error-code-system",
"role": "go-library",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Error-Code-System.git",
"legacy": "error-code-system",
"directory": "."
}
],
"target": "main"
},
{
"id": "Agent-System-Core",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Core.git",
"path": ".workspace/Agent-System-Core",
"legacy_path": "modules/core",
"role": "go-library",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git",
"legacy": "agent-system-core",
"directory": "."
}
],
"target": "main"
},
{
"id": "Agent-System-UI",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-UI.git",
"path": ".workspace/Agent-System-UI",
"legacy_path": "modules/ui",
"role": "npm-library",
"modules": [],
"target": "main",
"package_name": "@agent-system/ui"
},
{
"id": "Agent-System-Frontend",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Frontend.git",
"path": ".workspace/Agent-System-Frontend",
"legacy_path": "modules/frontend",
"role": "application",
"modules": [],
"target": "main"
},
{
"id": "Agent-Native",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Native.git",
"path": ".workspace/Agent-Native",
"legacy_path": "deps/agent-native",
"role": "go-library",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Native.git",
"legacy": "agent-native",
"directory": "."
}
],
"target": "main"
},
{
"id": "Agent-Intent",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Intent.git",
"path": ".workspace/Agent-Intent",
"legacy_path": "deps/agent-intent",
"role": "go-library",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Intent.git",
"legacy": "agent-intent",
"directory": "."
}
],
"target": "main"
},
{
"id": "Agent-Command",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Command.git",
"path": ".workspace/Agent-Command",
"legacy_path": "deps/agent-command",
"role": "go-library",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Command.git",
"legacy": "agent-command",
"directory": "."
}
],
"target": "main"
},
{
"id": "Agent-Squad",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Squad.git",
"path": ".workspace/Agent-Squad",
"legacy_path": "deps/agent-squad",
"role": "go-library",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Squad.git",
"legacy": "agent-squad",
"directory": "."
},
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Squad.git/adapters/native",
"legacy": "agent-squad/adapters/native",
"directory": "adapters/native"
},
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Squad.git/adapters/intent",
"legacy": "agent-squad/adapters/intent",
"directory": "adapters/intent"
},
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Squad.git/adapters/sqlite",
"legacy": "agent-squad/adapters/sqlite",
"directory": "adapters/sqlite"
},
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Squad.git/cmd/agent-squad",
"legacy": "agent-squad/cmd/agent-squad",
"directory": "cmd/agent-squad"
},
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-Squad.git/adapters/a2a",
"legacy": "agent-squad/adapters/a2a",
"directory": "adapters/a2a"
}
],
"target": "main"
},
{
"id": "Agent-System-Backend",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Backend.git",
"path": ".workspace/Agent-System-Backend",
"legacy_path": "modules/backend",
"role": "application",
"modules": [
{
"path": "git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git",
"legacy": "agent-system-backend",
"directory": "."
}
],
"target": "main"
},
{
"id": "Development-Guidelines",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Guidelines.git",
"path": ".workspace/Development-Guidelines",
"legacy_path": "deps/development-guidelines",
"role": "guidelines",
"modules": [],
"target": "main"
},
{
"id": "Agent-System-Documents",
"repo": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Documents.git",
"path": ".workspace/Agent-System-Documents",
"legacy_path": "modules/documents",
"role": "documentation",
"modules": [],
"target": "main"
}
],
"transport": {
"go_private": [
"git-ssh.u1680832.nyat.app/weichen"
],
"git_rewrites": [
{
"from": "https://git-ssh.u1680832.nyat.app/weichen/",
"to": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
}
]
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"schema": 1,
"components": {
"Agent-System-Frontend": {
"commit": "74511db9a6fb12e17954cb0c3f8e8846a6ecf831"
},
"Agent-System-Backend": {
"commit": "48649a37ee92373c639a384a2c5a9dfaa2ec4bbb"
},
"Development-Guidelines": {
"commit": "db3824bfdbfdcad91d0941495719f04dbeaca053"
},
"Agent-System-Documents": {
"commit": "1de7d17110efaa5ed2ed30a44d415f28987f89f3"
}
}
}
Submodule deps/agent-command deleted from 4f583ca3ee
Submodule deps/agent-intent deleted from 343b1e6d9a
Submodule deps/agent-native deleted from c348441691
-1
Submodule deps/agent-squad deleted from 490b0b2dfa
Submodule deps/logging-system deleted from 49b5d8be60
+3 -7
View File
@@ -1,9 +1,5 @@
# 下游薄切片
# 独立下游扩展示例
本目录模拟独立仓库 `Agent-System-Plugin-RDM`。它只 import `agent-system-backend/pkg/extension`,不改基座。
本目录模拟独立功能仓,通过 Backend 的公开 pkg/extension 注册扩展。go.mod/go.sum 固定远端依赖,普通 clone 即可下载和测试,不要求相邻源码目录或复制 replace。需要完整产品时空白导入公开 pkg/extension/link,并通过 Dev 的固定组合器装配前端资产,具体见 policy/dependencies.md。
把本目录搬到新仓库时,由 owner 创建 Git 仓库。`replace` 改成基座检出路径或已发布版本。需要拉起完整二进制时再空白导入 `agent-system-backend/pkg/extension/link`,并复制基座 `go.mod` 的 replace(下游模块不会继承它们)。
```
go test .
```
版本采用由 Go 生成的提交版本;更新消费方依赖后独立测试,再对实际固定组合审阅。本地源码联调用工作区覆盖,正式构建使用 GOWORK=off。
+2 -9
View File
@@ -3,13 +3,6 @@ module example.com/agent-system-plugin-rdm
go 1.27
require (
agent-system-backend v0.0.0
agent-system-core v0.0.0
git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git v0.4.1-0.20261013154716-f1302ed5b395
git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git v0.4.1-0.20261013151137-f32d4317846f
)
// 声明式路径(仅 pkg/extension 根包)只需下面两条 replace。
// 空白导入 pkg/extension/link 拉起完整后端时,须复制基座 go.mod 的
// 全部 8 条 deps replace——见 README 与 extension-compat-policy §4。
replace agent-system-backend => ../../modules/backend
replace agent-system-core => ../../modules/backend/deps/agent-system-core
+4
View File
@@ -0,0 +1,4 @@
git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git v0.4.1-0.20261013154716-f1302ed5b395 h1:Ij/SYy1bNdlteFZeCSBFUEEFbhwd3srpAOwSIjhbpsI=
git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git v0.4.1-0.20261013154716-f1302ed5b395/go.mod h1:ipD03jF1IpqWwU7m+verjzgnOYVc7Tau9xz9mJKKn8s=
git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git v0.4.1-0.20261013151137-f32d4317846f h1:39RW0R6zmtXLNbLQaqFCmtZ6zjEs2lhDLb9YLhudIiM=
git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git v0.4.1-0.20261013151137-f32d4317846f/go.mod h1:T3FU9NsngaxF/1C2ISi02fV7+aqNepOEvX0QhswA70Y=
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"agent-system-backend/pkg/extension"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/pkg/extension"
)
func init() {
+7 -3
View File
@@ -6,8 +6,8 @@ import (
"errors"
"testing"
"agent-system-backend/pkg/extension"
core "agent-system-core"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/pkg/extension"
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
func TestCompiledSkillRegistration(t *testing.T) {
@@ -24,6 +24,7 @@ func TestCompiledSkillRegistration(t *testing.T) {
// fakeRegistry 记录 register() 的登记动作,不拉起基座。
type fakeRegistry struct {
extension.Registry // Unused capabilities fail if this example starts calling them.
skills []string
commands []string
handlers int
@@ -64,7 +65,10 @@ func TestRegisterBindsPingHandler(t *testing.T) {
}
// fakeHost 是下游测试里的最小句柄替身:只实现 rdm.ping 用到的能力。
type fakeHost struct{ owner string }
type fakeHost struct {
extension.Host
owner string
}
func (f fakeHost) PluginID() string { return "rdm" }
func (f fakeHost) Owner() string { return f.owner }
Submodule modules/backend deleted from 8e80e2618e
Submodule modules/core deleted from 5773e74086
Submodule modules/ui deleted from 2c4322ef56
+17 -7
View File
@@ -2,12 +2,12 @@
- 项目/负责人:Agent-System 组合 / weichen
- 状态:active(2026-09-30 经 PR weichen/Development-Guidelines#1 采纳 draft.7;机制经 Dev PR#1 合入 ef89fc8)
- 规范位置:deps/development-guidelines(gitlink 固定版本)
- 过程文档位置:modules/documents(docs/records/)
- 版本权威:父仓 gitlink
- 规范位置:.workspace/Development-Guidelines(components.lock.json 固定源码;首次启动可读仓外固定缓存)
- 过程文档位置:.workspace/Agent-System-Documents(docs/records/)
- 版本权威:components.lock.json;规则内容版本由该提交的 VERSION 标识,历史提交保留原 gitlink
- 批准/生效依据:Dev PR#1(19/19 正负例 + make test);guidelines PR#1
- 本次升级候选:`0.1.0-draft.11`,实际合入提交 `2f5b19872be2a4597838a4131ad75d1be089ee5e`(Guidelines PR #5);本候选父仓固定该源码版本。基础门禁继续执行,合入调度流程单独记 approved/pending-activation;Dev 组合验收、平台正负例和全部写入者覆盖完成前不切换交付模式。
- 过程计划固定:Documents PR #96 实际合入 `129cc3579d9c9e3edfb46c99270d65f3e1dad0a0`,包含既有主干文档及获批 U1–U6 计划;当前进展以任务/PR 记录为准。
- 合入调度采纳基线:`0.1.0-draft.11`,实际合入提交 `2f5b19872be2a4597838a4131ad75d1be089ee5e`(Guidelines PR #5);本次依赖候选的固定规范包含该提交;最终来源仅由 components.lock.json 决定。基础门禁继续执行,合入调度流程单独记 approved/pending-activation;Dev 组合验收、平台正负例和全部写入者覆盖完成前不切换交付模式。
- 合入调度过程计划来源:Documents PR #96 实际合入 `129cc3579d9c9e3edfb46c99270d65f3e1dad0a0`,包含既有主干文档及获批 U1–U6 计划;当前进展以任务/PR 记录为准。
| 规范/工作流 | 适用条件与范围 | 执行方式/证据 |
|---|---|---|
@@ -16,7 +16,7 @@
| standards/collaboration.md | 并行/同步节点 | **enforced(本地命令)**:分支、现场、组件检查;task-sync 实际合并时记录,同基线只核验。最终平台与阶段授权另验 |
| 提交格式(主题行/四段 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) |
| standards/multi-repo.md | 跨仓/版本依赖 | manual;受控历史迁移通道已于 2026-09-30 行使一次(documents/records/2026-09-30-history-migration.md) |
| 工作区回收 | 交付终点 | advisory(task-finish);负例:合入前 finish 拒绝输出 |
| policy/delivery.md(项目收紧) | 需求、长期方向、试验、实施迭代及交付 | manual(人工/Agent 执行):前序人工决策与有范围的连续推进授权;智能体完成 Review、冲突处置和当前 HEAD 的 CI 核验后合入;任务卡提供记录,post-merge 默认只提示,行为回归见 tests/test_task.py |
| standards/tool-adaptation.md(draft.8) | 为工具创建规则/钩子实例 | 本组合 CLI/git 级实例已登记:policy/tool-adaptation-matrix.md(A1–A7 逐行定级+正负例证据) |
@@ -32,4 +32,14 @@
| delivery.md §2、design-verification.md 的轻量流程默认做法 | 按 [policy/delivery.md](delivery.md) 保留需求、方案、实施与验收的人工节点;代码 Review 与 PR 合入交由智能体,冲突解决且当前 HEAD 的 CI 通过后合入;小任务简化篇幅,阶段内可迭代 | 2026-10-12 用户明确要求智能体 Review 后合入,合入前解决冲突且 CI 通过;其他人工决策与高后果双钥匙边界保留 | weichen;Review、CI 或平台准入条件变化时复查 |
- 历史记录(2026-09-30)报告 11 个组合仓 main 禁直推、拒绝陈旧候选;当前平台能力须独立重验,本地脚本测试不替代远端证据。
- 规范源码以 gitlink 及其 VERSION 为准,流程启用以本采纳状态及项目条件为准;固定源码版本不代替实际能力证明。治理可靠性修复的命令与证据约定见 [开发门禁](governance.md),本地与客户端/平台能力分别登记;本轮不改变分支保护或发布授权。
- 规范源码以 components.lock.json 固定内容及其 VERSION 为准;流程启用以本采纳状态及项目条件为准,固定源码不代替实际能力证明。治理可靠性修复的命令与证据约定见 [开发门禁](governance.md),本地与客户端/平台能力分别登记;本轮不改变分支保护、发布授权或通用库版本。
## 2026-10-13 版本依赖采纳增量
用户批准版本依赖与按需工作区方案 r1,并授权重构与自检连续推进、保护在途分支。
固定 draft.12 内容延续已固定上游规则;本次采纳将 Go/npm 库的版本权威交给消费方,
应用、规范与资料交给源码锁。具体组件、命令、旧任务同步及回退见 [依赖规则](dependencies.md)。
启动取源、组件编辑/解除、固定组合与旧现场保留由本地工具和真实仓库回归验证,证据为 SELF。
平台 runner 隔离、token scope、受信工作流来源、分支保护和精确 HEAD CI 仍须 GATE/REPLAY 核验;
本文件不宣称平台合并队列或发布能力已就位。其他项目须独立采纳,不因本轮升级自动改变规则。
+3 -3
View File
@@ -4,10 +4,10 @@
# it queries store metadata and does not depend on either transport/composition root.
# Approved membership proposal r1 adds app/server -> projectscope -> store.
backend:
root: modules/backend/internal
import_prefix: agent-system-backend/internal/
root: .workspace/Agent-System-Backend/internal
import_prefix: git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/
frontend:
root: modules/frontend/src
root: .workspace/Agent-System-Frontend/src
groups:
- name: app
dir: .
+1 -1
View File
@@ -1,7 +1,7 @@
# 需求、试验与迭代交付
适用:本项目的需求、修复、规范修改和其他交付任务。依据固定版本的
[通用交付流程](../deps/development-guidelines/workflows/delivery.md),本项目收紧为
[通用交付流程](../.workspace/Development-Guidelines/workflows/delivery.md),本项目收紧为
**需求、方案、实施与验收默认逐阶段人工评审;代码 Review 与 PR 合入由智能体完成;
阶段内迭代,按影响回退,按增量交付**。
本文件是阶段与评审的权威表述;启动、同步、命令证据和回收操作见
+54
View File
@@ -0,0 +1,54 @@
# 版本依赖、源码编辑与分支过渡
适用:组件版本升级、功能仓编辑、新 clone、旧任务同步和源码候选构建。通用原则见固定 Development-Guidelines 的 multi-repo/collaboration/adoption;本文件只规定项目映射、命令和过渡入口。
## 版本权威
- `components.json`:逻辑组件、批准的仓库、职责、模块/包身份与消费路径,不存凭据。
- `components.lock.json`:应用、规范和过程资料的完整提交;Go/npm 库不另设可竞争的源码锁。
- Go:消费方 go.mod 的实际模块图决定版本,go.sum 校验内容。模块路径为 `git-ssh.u1680832.nyat.app/weichen/<Repo>.git`,子模块追加目录;端口仅属于传输配置。
- npm:消费方 package-lock 固定 Git 提交及包完整性信息;契约由 Core 单一 TS 源码产生,UI 与 Frontend 消费 `@agent-system/contracts`,Frontend 消费 `@agent-system/ui`。
- 正式产物:隔离固定源码装配 frontend dist/backend embed,自动导出解析清单与摘要。RDM 使用隔离 vendor 构建注入固定前端资产,不改 Go module cache,不写本地路径 replace。
采用的提交在功能仓合入后仍须从批准来源获取。合入方式须保留已锁定的提交对象;回收功能分支前核对这些提交已在主干或受保护归档中可达,不用 squash 或删除唯一引用使现有语言版本失效。发布 tag 与归档删除仍保留原双钥匙边界。
新模式是否存在以该提交的 `components.json`/锁为准。历史提交没有这两份文件时继续按照其旧 gitlinks 与脚本构建;不能套用最新来源改变历史候选。
## 从 Dev 按需编辑
1. 从普通 clone 开始,按项目启动门禁执行 precheck/task-start;新布局由固定清单准备应用和规范,普通库按语言依赖下载。
2. `make component-edit NAME=Agent-Native` 取得消费方实际使用的源版本,创建所属功能仓的独立编辑分支并接入本地 go.work。
3. 在返回的目录修改、局部验证、提交及创建功能仓 PR;Dev 不提交复制的功能仓源码。
4. `make component-unlink NAME=Agent-Native` 解除开发覆盖,保留源码和分支;采用新版本由消费方依赖 PR 决定。
`make test-backend LOCAL_OVERLAY=1` 可明确使用已登记 Go 开发覆盖做局部检查,默认局部入口 GOWORK=off;结果仍为开发对象,不能代替固定组合。
`make workspace-status` 是只读状态;`make workspace-prepare` 准备固定来源。正式 build/test 与局部开发验证分开,不能把存在 go.work/npm 链接的工作树当发布候选。Go 正式消费 `GOWORK=off`,候选代码执行环境没有 checkout token;源码获取和执行阶段分离。
依赖锁变更后,owner 可用 `make workspace-prepare NAME=Agent-System-Backend REFRESH_FIXED=1`
显式刷新已登记、干净且处于原基线的 detached 检出,旧提交仍由本地保存引用保留。编辑分支、
未保存或 ignored 唯一文件、Git 操作和共享归属均拒绝刷新;不会切换已编辑组件。
UI 源码编辑从 Frontend 锁中解析实际 Git 提交;其包构建是显式命令。当前工作区工具的源码编辑/版本解析与 Go 覆盖分别验证,不能仅凭 UI 仓被检出就声称 npm 开发链接已生效。
## 在途分支与迁移
本轮切换不 checkout/reset/stash/clean 其他活动工作区,不批量 rebase,不重写旧提交和 tag,不删除旧物理目录或 Git 管理数据。
- 未同步的旧任务继续用旧提交、旧脚本和固定 gitlinks;新功能分支从最新权威 main 起步。
- 旧任务纳入新 main 前先核对父仓与组件状态;有当前写者、Git 锁、未提交或唯一文件时由 owner 保存并安排安全同步点。文本和 API 冲突分别处理,不把旧 pin/锁批量覆盖新组合。
- `make workspace-transition` 默认只报告迁移计划;`NAME=<组件>` 可限定范围。确认归属后显式 `ARGS=--apply` 才在新工作区准备独立源码。原仓、refs、reflog、暂存/工作树/未跟踪和 ignored 唯一文件保留。
- 已登记嵌套组件独立保存;相同内容的重复来源可去重,但引用仍按来源保存。未知归属、冲突的重复组件、外借对象库、逃逸 symlink 或目标已有他人现场拒绝并报告,不能称迁移成功。
- 组件分支仍是所属仓的代码;来源/命名变化后按已合入接口进行正常合并和审阅。原分支或数据不被自动删除;不能将“已复制保存”解释为语义迁移已完成。
迁移后的库版本以消费方实际解析为准,主干前进时不隐式切换已编辑组件。旧组件目录的 .git common-dir 可能供外部 worktree 使用;task-finish 必须核对依赖及唯一成果,删除被拒就保留现场。
## 验证与回退
真实 Git/Go fixture 验证固定取源、编辑/解除、嵌套组件、dirty/index/ignored/ref 保留、冲突、锁和越界拒绝。新 clone 的固定产品构建、RDM 完整前端二进制、当前组合和历史组合分别重放;文档检查不能替代这些能力。
回退用普通 PR 恢复已批准依赖、源码锁和构建规则,并重建同一解析组合;不改历史、不推断业务数据库可以逆迁移。正式验收与合入仍按 policy/delivery.md,SELF 不提升为 GATE/REPLAY,发布与共享部署保留原授权边界。
## 后续项目采纳
通用规则只维护在 Development-Guidelines;新项目固定审阅过的规范来源并填写采纳模板,选择自己的仓库映射、读取目录、编辑命令和正式构建入口。现存项目不会因上游规则合入或新包可用而自动升级、自动移除 submodule 或自动改变审批权限。
+9 -7
View File
@@ -1,7 +1,7 @@
# 开发门禁的执行与证据
本文件细化根 AGENTS.md 的启动、验证和回收规则。通用语义以固定的
`deps/development-guidelines` 为准;门禁实现及回归用例随本仓版本管理。
`components.lock.json` 固定的 Development-Guidelines 为准;门禁实现及回归用例随本仓版本管理。
需求表、设计试验、迭代调整、阶段产物、人工决策及智能体 Review/合入按
[迭代交付](delivery.md) 执行。任务卡分别记录人工批准、智能体审阅者及对象版本;
本地 evidence 仅记录 SELF 命令,不能证明人工批准或提升为 CI/独立审阅证据。
@@ -42,13 +42,15 @@ LOCAL_READY 只用于局部恢复;新任务入口始终校验严格 READY,
## 被测对象
`make task-sync` 在父仓合并后按固定 gitlink 物化未修改组件,并核对实际组件工作树。
组件 feature 分支若与新指针不同,先保留其分支,交给 owner 显式同步并更新组合;
不能用自动 checkout 丢弃组件分支现场。任何失败都保留现场并阻止同步成功声明。
`make task-sync` 在父仓合并后按该提交的模式准备来源:新布局读取固定清单,
历史布局读取原 gitlink。主干前进不会自动切换已编辑组件;来源变化、未保存修改或 Git 操作
由 owner 先处理,任何失败保留现场并阻止同步成功声明。旧目录仍可能支撑其他 worktree,
显式过渡与保存要求见 [依赖规则](dependencies.md)。
`make test-worktree` 核对递归子模块:未初始化、冲突、指针漂移及子仓未保存改动均拒绝。
它是组合测试和构建的前置检查。父仓未提交改动可以局部测试,但证据必须记录实际对象状态;
正式验收使用已提交的固定对象,不能仅写一个与工作树不同的 HEAD。
`make test-worktree` 验证当前模式中的已登记来源与实际工作树;新布局的本地编辑状态可记录,
但正式 `make test/build` 从已提交的固定源码与语言图隔离物化。evidence 同时记录忽略目录中
已登记组件的 HEAD、分支、dirty/差异与唯一内容摘要,不能只用父仓 HEAD 代表开发组合。
历史布局继续拒绝未初始化、冲突、指针漂移及未保存子仓改动。
`task-sync` 对同一基线重复调用只核验,保留 HEAD、索引和卡片;实际合并新目标才追加一次记录。
输出完整 sync_base、记录完成后的 tested_head、checked_at 及 changed;changed 表示是否实际纳入新目标,组件仍须核对。
+4 -4
View File
@@ -2,7 +2,7 @@
状态:approved(r1 条文作为实施计划基线);执行模式:manual 受控试行;更新日期:2026-10-14(Asia/Shanghai);规范采纳负责人:weichen。实际启用时间、对象和窗口以调度记录 #518 为准,候选文件不代替当前平台准入。
r1 条文来源提交为 913b214f2826a50b3dab65f79d9ebf83e84f2519;Documents 实施与测试计划 r1@ba4044d57e38d0887dd425d052abf26b20a28bf4 已于 2026-10-13 获用户「批准」。阶段结论与执行进展按 [迭代交付](delivery.md) 记录在任务主记录,候选固定后的结论保存在关联 PR 与源码外收据中;规范及计划批准不等于模式已启用。
现行规则仍以根 AGENTS.md、[迭代交付](delivery.md)、[开发门禁](governance.md) 和已采纳 gitlink 为准。
现行规则仍以根 AGENTS.md、[迭代交付](delivery.md)、[开发门禁](governance.md) 和已采纳的固定规范来源为准。
本分支已实施 CLI 同步/恢复及源码外证据基础,自检依据见任务主记录及固定后记录;通用条文和实施计划已经各自 PR 合入,本候选组合固定实际合入版本。用户已于 2026-10-14 明确授权当前智能体接管 #489/#488/#486,完成平台验证、试行、实际合入与清理。平台所有者操作已实际回读,隔离正反例及独立重放已完成;本轮使用单一智能体协调的 manual 窗口,原 CI 触发和全部必需作业保持,未建设自动调度。
## 通用规范与项目职责
@@ -10,10 +10,10 @@ r1 条文来源提交为 913b214f2826a50b3dab65f79d9ebf83e84f2519;Documents
通用正文的唯一候选来源是 Development-Guidelines 的 `standards/merge-queue.md`,
配套模板为该仓 `templates/merge-queue.md`,候选内容版本 `0.1.0-draft.11`。
本仓仅细化目标、入口、阶段、预算和采纳条件,不复制通用调度正文。
通用候选已经 [Guidelines PR #5](https://git-home.u1680832.nyat.app:30319/weichen/Development-Guidelines/pulls/5) 合入为 `2f5b19872be2a4597838a4131ad75d1be089ee5e`;本候选 `deps/development-guidelines` 已物化该固定版本及上述文件。Dev 已经 #519/#536 固定采纳并通过其精确 HEAD 的全部必需 CI。平台机制与手动写者覆盖的实际依据见 #518;受控试行的当前候选仍须独立组合测试、当前 HEAD CI 与平台准入,不把已采纳规范标成自动 enforced。
通用候选已经 [Guidelines PR #5](https://git-home.u1680832.nyat.app:30319/weichen/Development-Guidelines/pulls/5) 合入为 `2f5b19872be2a4597838a4131ad75d1be089ee5e`;本候选 components.lock.json 固定的规范来源包含该版本及上述文件。Dev 已经 #519/#536 固定采纳并通过其精确 HEAD 的全部必需 CI。平台机制与手动写者覆盖的实际依据见 #518;受控试行的当前候选仍须独立组合测试、当前 HEAD CI 与平台准入,不把已采纳规范标成自动 enforced。
Dev 目标身份使用实际托管实例、仓库 ID 与 `refs/heads/main`;其他组件仓各有自己的目标队列。
跨仓组合按任务中的依赖关系先交付组件,再固定父仓组合;保留其他任务已经合入的 gitlink 能力。
跨仓组合按任务中的依赖关系先交付组件,再固定父仓组合;保留其他任务已经合入的依赖和治理能力。
队列仅安排工作,受保护分支最终准入继续由 Gitea 或等价受信入口负责。
## 项目调度记录
@@ -113,7 +113,7 @@ SYNC_REQUIRED 不整体降级为 READY;local 与 delivery 用独立结果和
2. 在通用仓完成候选修订 Review 与其文档验证,按授权经 PR 合入;宿主采纳完整固定 SHA。
3. 另行提交实施计划,覆盖 task-sync 幂等化、恢复/写守卫兼容、源码外收据、CI 分配和能力矩阵。
4. 在隔离场景完成治理脚本正负例;涉及脚本必须运行 `make test-governance`。
5. 跨仓 gitlink 正式推进前运行 Dev `make test`;最终对象的 CI/构建及平台准入遵循现行规则。
5. 跨仓语言依赖或应用源码锁正式推进前运行 Dev `make test`;最终对象的 CI/构建及平台准入遵循现行规则。
6. 完成所有者平台操作和对应验证,登记真实协调者与调度 Issue,才切换新交付模式。
7. 用三个就绪任务试行,记录同步轮数、CI 次数/耗时、废弃运行、排队和恢复,复盘后决定扩大。
+624
View File
@@ -0,0 +1,624 @@
#!/usr/bin/env python3
"""Build immutable application commits without developer workspace overlays.
Go versions remain authoritative in go.mod; npm versions in package-lock.json.
The output manifest records the selected graph rather than creating another lock.
"""
import argparse
import hashlib
import importlib.util
import io
import json
import os
from pathlib import Path, PurePosixPath
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
from urllib.parse import urlsplit
SHA = re.compile(r"[0-9a-f]{40}\Z")
IDS = re.compile(r"[A-Za-z0-9][A-Za-z0-9_-]*\Z")
BACKEND = "Agent-System-Backend"
FRONTEND = "Agent-System-Frontend"
NPM_NAME = re.compile(r"(?:@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*\Z")
NPM_VERSION = re.compile(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?\Z")
class CompositionError(Exception):
pass
def run(command, cwd, env, *, binary=False, quiet=False):
if not quiet:
print("[composition] " + " ".join(map(str, command)), flush=True)
result = subprocess.run(command, cwd=cwd, env=env, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=not binary)
if not quiet and not binary:
sys.stdout.write(result.stdout)
sys.stderr.write(result.stderr)
if result.returncode:
detail = result.stderr.decode(errors="replace") if binary else result.stderr
raise CompositionError(f"command failed ({result.returncode}): {command[0]} {detail.strip()}")
return result.stdout
def digest(path):
# The composer also supports standalone fixed-root wrapper fixtures.
return hashlib.sha256(path.read_bytes()).hexdigest()
def tree_digest(root):
if root.is_symlink():
raise CompositionError(f"artifact root symlink is forbidden: {root.name}")
entries = []
for path in sorted(root.rglob("*")):
if path.is_symlink():
raise CompositionError(f"artifact symlink is forbidden: {path.name}")
if path.is_file():
entries.append({"path": path.relative_to(root).as_posix(), "sha256": digest(path), "size": path.stat().st_size})
encoded = json.dumps(entries, sort_keys=True, separators=(",", ":")).encode()
return {"sha256": hashlib.sha256(encoded).hexdigest(), "files": entries}
def safe_relative(root, value):
if not isinstance(value, str) or not value or "\\" in value:
raise CompositionError("invalid component path")
relative = PurePosixPath(value)
if relative.is_absolute() or ".." in relative.parts or (root / value).resolve().is_relative_to(root) is False:
raise CompositionError("component path escapes root")
def validate_repo(repo):
if not isinstance(repo, str) or not repo or repo.startswith("-") or any(c.isspace() for c in repo):
raise CompositionError("invalid repository source")
if "://" not in repo:
if not Path(repo).is_absolute():
raise CompositionError("repository must be an approved URL or absolute local path")
return
parsed = urlsplit(repo)
if parsed.scheme not in {"ssh", "https", "file"} or parsed.password or parsed.query or parsed.fragment:
raise CompositionError("repository URL contains credentials or unsupported transport")
if parsed.scheme == "https" and parsed.username:
raise CompositionError("repository credentials are forbidden")
if parsed.scheme == "file" and parsed.netloc not in {"", "localhost"}:
raise CompositionError("file repository must be local")
def tooling_identity(root, environment=None):
"""Bind executed tools before importing acquisition configuration."""
env = (os.environ if environment is None else environment).copy()
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
env.pop(key, None)
commit = run(["git", "rev-parse", "HEAD"], root, env, quiet=True).strip()
result = {"root_commit": commit, "files": {}}
for relative in ("scripts/build-composition.py", "scripts/workspace/transport.py"):
executing = Path(__file__).resolve().parent / relative.removeprefix("scripts/")
fixed = subprocess.run(["git", "show", f"{commit}:{relative}"], cwd=root, env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if fixed.returncode == 0:
if executing.is_symlink() or executing.read_bytes() != fixed.stdout:
raise CompositionError(f"executing tooling differs from committed source: {relative}")
mode = "committed-root"
elif (root / relative).exists():
raise CompositionError(f"root tooling has no committed identity: {relative}")
else:
# A standalone consumer fixture can use an external composer. Its
# exact tool bytes are a separate identity, never the consumer SHA.
mode = "external-tool"
result["files"][relative] = {"sha256": digest(executing), "source": mode}
return result
def inputs(root, env):
commit = run(["git", "rev-parse", "HEAD"], root, env, quiet=True).strip()
if not SHA.fullmatch(commit):
raise CompositionError("root must have a committed Git revision")
documents = {}
hashes = {}
for name in ("components.json", "components.lock.json"):
committed = run(["git", "show", f"{commit}:{name}"], root, env, binary=True, quiet=True)
path = root / name
if path.is_symlink() or path.read_bytes() != committed:
raise CompositionError(f"{name} differs from committed root {commit}")
documents[name] = json.loads(committed)
hashes[name] = hashlib.sha256(committed).hexdigest()
manifest, lock = documents["components.json"], documents["components.lock.json"]
if manifest.get("schema") != 1 or not isinstance(manifest.get("components"), list):
raise CompositionError("unsupported components schema")
if lock.get("schema") != 1 or not isinstance(lock.get("components"), dict):
raise CompositionError("unsupported component lock schema")
components = {}
for item in manifest["components"]:
name = item.get("id")
if not isinstance(name, str) or not IDS.fullmatch(name) or name in components:
raise CompositionError("invalid or duplicate component id")
if item.get("path") != f".workspace/{name}":
raise CompositionError(f"invalid component path: {name}")
safe_relative(root, item["path"])
if item.get("legacy_path"):
safe_relative(root, item["legacy_path"])
validate_repo(item.get("repo"))
for module in item.get("modules", []):
safe_relative(root, module.get("directory", "."))
components[name] = item
for name, value in lock["components"].items():
if name not in components or not isinstance(value, dict) or not SHA.fullmatch(value.get("commit", "")):
raise CompositionError(f"invalid locked component: {name}")
if components[name].get("role") in {"go-library", "npm-library"}:
raise CompositionError(f"language library cannot have a competing source lock: {name}")
for name in (BACKEND, FRONTEND):
if name not in components or components[name].get("role") != "application" or name not in lock["components"]:
raise CompositionError(f"missing fixed application source: {name}")
return commit, components, lock["components"], hashes
def execution_environment(env):
"""Acquisition-only authentication/transport never reaches npm/Go candidate code."""
clean = env.copy()
for key in list(clean):
upper = key.upper()
if (any(word in upper for word in ("TOKEN", "SECRET", "PASSWORD", "CREDENTIAL", "API_KEY", "ACCESS_KEY"))
or upper.startswith(("GIT_CONFIG_", "GIT_SSH", "NPM_CONFIG_", "BASH_FUNC_"))
or upper in {"SSH_AUTH_SOCK", "SSH_AGENT_PID", "NODE_OPTIONS", "BASH_ENV", "ENV", "GOFLAGS", "GIT_ASKPASS", "SSH_ASKPASS", "CONTRACTS_TARBALL", "UI_TARBALL"}):
del clean[key]
clean.update(GOWORK="off", GOTOOLCHAIN="local", GOFLAGS="", CGO_ENABLED="0")
# Cache location is transport-independent state, shared by acquisition and
# offline consumers. Other npm settings can carry credentials or hooks.
for key in ("npm_config_cache", "NPM_CONFIG_CACHE"):
if env.get(key):
clean[key] = env[key]
return clean
def export_source(repo, commit, destination, temporary, env, *, abbreviated=False):
validate_repo(repo)
clone = temporary / (destination.name + "-git")
run(["git", "clone", "--no-checkout", "--no-hardlinks", "--", repo, str(clone)], temporary, env)
exists = subprocess.run(["git", "cat-file", "-e", commit + "^{commit}"], cwd=clone, env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
if not exists:
run(["git", "fetch", "--no-tags", "origin", commit], clone, env)
actual = run(["git", "rev-parse", commit + "^{commit}"], clone, env, quiet=True).strip()
if not SHA.fullmatch(actual) or (actual != commit and not (abbreviated and len(commit) == 12 and actual.startswith(commit))):
raise CompositionError("application revision is not an immutable full commit")
archive = run(["git", "archive", "--format=tar", actual], clone, env, binary=True, quiet=True)
extract_archive(archive, destination)
return {"repo": repo, "commit": actual, "archive_sha256": hashlib.sha256(archive).hexdigest()}
def extract_archive(archive, destination):
destination.mkdir()
with tarfile.open(fileobj=io.BytesIO(archive)) as tar:
for entry in tar.getmembers():
safe_relative(destination, entry.name)
if not (entry.isfile() or entry.isdir()):
raise CompositionError(f"source archive contains an unsupported link: {entry.name}")
tar.extractall(destination, filter="data")
def version(manifest, lock, *, backend_commit=None, resolved_go=None):
resolved = json.dumps({"go": manifest["go"] if resolved_go is None else resolved_go,
"npm_lock": manifest["npm"]["lock_sha256"]},
sort_keys=True, separators=(",", ":")).encode()
return (manifest["root"]["commit"][:12] + "+backend=" + (backend_commit or lock[BACKEND]["commit"])[:12]
+ "+frontend=" + lock[FRONTEND]["commit"][:12]
+ "+deps=" + hashlib.sha256(resolved).hexdigest()[:12])
def json_stream(output):
decoder = json.JSONDecoder()
result = []
while output.strip():
value, end = decoder.raw_decode(output.lstrip())
result.append(value)
output = output.lstrip()[end:]
return result
def go_dependencies(source, fetch_env):
document = json.loads(run(["go", "mod", "edit", "-json"], source, fetch_env, quiet=True))
for replacement in document.get("Replace") or []:
if not replacement["New"].get("Version"):
raise CompositionError("formal Go build forbids local directory replace")
original = {name: (source / name).read_bytes() if (source / name).exists() else None for name in ("go.mod", "go.sum")}
# Tidy locks need sums for consumed packages, not unused transitive zip files.
# Load actual build/test metadata before ending acquisition; no tests execute.
# Download may normalize sum lines even for a valid readonly consumer. Keep
# that package-manager bookkeeping in an identical disposable manifest.
with tempfile.TemporaryDirectory(prefix="composition-go-prefetch-") as directory:
isolated = Path(directory)
for name, content in original.items():
if content is not None:
(isolated / name).write_bytes(content)
downloads = json_stream(run(["go", "mod", "download", "-json"], isolated, fetch_env, quiet=True))
run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], source,
fetch_env | {"CGO_ENABLED": "0"}, quiet=True)
run(["go", "mod", "verify"], source, fetch_env)
modules = json_stream(run(["go", "list", "-mod=readonly", "-m", "-json", "all"], source, fetch_env, quiet=True))
selected = []
for module in modules:
item = {key: module[key] for key in ("Path", "Version", "Main", "Sum", "GoModSum", "GoVersion") if key in module}
if "Replace" in module:
replacement = module["Replace"]
if not replacement.get("Version"):
raise CompositionError("resolved Go graph contains a local replace")
item["Replace"] = {key: replacement[key] for key in ("Path", "Version", "Sum", "GoModSum") if key in replacement}
selected.append(item)
for name, content in original.items():
actual = (source / name).read_bytes() if (source / name).exists() else None
if actual != content:
raise CompositionError(f"Go acquisition changed committed {name}; update the consumer first")
return {"modules": selected, "go_mod_sha256": digest(source / "go.mod"),
"go_sum_sha256": digest(source / "go.sum") if (source / "go.sum").exists() else None,
"downloads": [{key: value[key] for key in ("Path", "Version", "Sum", "GoModSum", "Origin") if key in value} for value in downloads]}
def npm_registry_package(location, package):
resolved = package.get("resolved", "")
if not isinstance(resolved, str):
raise CompositionError("invalid npm lock dependency")
if not resolved.startswith(("https://", "http://")):
return None
registry = urlsplit(resolved)
if (registry.scheme != "https" or registry.netloc != "registry.npmjs.org" or
registry.username or registry.password or registry.query or registry.fragment):
raise CompositionError("npm tarball source is outside the reviewed public registry")
name = package.get("name") or location.rsplit("node_modules/", 1)[-1]
version = package.get("version", "")
if (not isinstance(name, str) or not NPM_NAME.fullmatch(name) or
not isinstance(version, str) or not NPM_VERSION.fullmatch(version)):
raise CompositionError("invalid locked registry package name or version")
return name + "@" + version
def npm_dependencies(source, components, fetch_env):
path = source / "package-lock.json"
lock = json.loads(path.read_text())
if lock.get("lockfileVersion", 0) < 2 or not isinstance(lock.get("packages"), dict):
raise CompositionError("frontend requires a complete npm lock")
approved = {component["repo"] for component in components.values()}
packages = {}
registry_packages = []
for name, package in lock["packages"].items():
if name:
safe_relative(source, name)
registry_package = npm_registry_package(name, package)
if registry_package:
registry_packages.append((name, registry_package))
if package.get("link"):
raise CompositionError("formal npm build forbids workspace links")
for value in [package.get("resolved", ""), *package.get("dependencies", {}).values()]:
if not isinstance(value, str):
raise CompositionError("invalid npm lock dependency")
if value.startswith(("file:", "link:", "workspace:")):
raise CompositionError("formal npm build forbids local dependency overlays")
if value.startswith(("git+", "git:", "ssh:")):
repo, sep, revision = value.removeprefix("git+").rpartition("#")
if not sep or not SHA.fullmatch(revision) or repo not in approved:
raise CompositionError("npm Git dependency must use an approved source and full commit")
packages[name] = {key: package[key] for key in ("version", "resolved", "integrity") if key in package}
before = digest(path)
run(["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund"], source, fetch_env)
# npm ci normally stores tarballs without the packuments required when a
# disposable packed-package consumer re-resolves peers offline. Fetch the
# installed locked versions before candidate execution, from a fixed public
# registry with empty config and no acquisition credentials or candidate rc.
metadata_env = execution_environment(fetch_env)
with tempfile.TemporaryDirectory(prefix="composition-npm-metadata-") as directory:
isolated = Path(directory)
global_config = isolated / "empty-global.npmrc"
global_config.write_text("")
selected = {spec for location, spec in registry_packages if (source / location).is_dir()}
if selected:
run(["npm", "cache", "add", *sorted(selected), "--ignore-scripts",
"--registry=https://registry.npmjs.org", "--userconfig=/dev/null",
"--globalconfig=" + str(global_config)], isolated, metadata_env)
if digest(path) != before:
raise CompositionError("npm acquisition changed committed lock")
result = {"lock_sha256": before, "packages": packages}
contract = source / "node_modules/@agent-system/contracts"
if contract.is_dir():
result["contracts"] = tree_digest(contract)
return result
def frontend_assets(source, clean_env):
dist = source / "dist"
if dist.exists():
shutil.rmtree(dist)
run(["npm", "run", "build"], source, clean_env)
if not (dist / "index.html").is_file() or not (dist / "index.html").stat().st_size:
raise CompositionError("frontend dist requires nonempty index.html")
if not (dist / "assets").is_dir() or not any(p.is_file() and p.stat().st_size for p in (dist / "assets").rglob("*")):
raise CompositionError("frontend dist requires nonempty assets/")
return dist, tree_digest(dist)
def inject(dist, destination):
if destination.is_symlink():
raise CompositionError("embed destination must not be a symlink")
if destination.exists():
shutil.rmtree(destination)
shutil.copytree(dist, destination)
(destination / ".gitkeep").touch()
def provider_revision(module, downloads):
metadata = next((entry for entry in downloads if entry["Path"] == module["Path"] and entry["Version"] == module["Version"]), {})
origin = metadata.get("Origin", {})
commit = origin.get("Hash", "")
pseudo = re.search(r"-([0-9a-f]{12})\Z", module["Version"])
if SHA.fullmatch(commit):
if pseudo and not commit.startswith(pseudo.group(1)):
raise CompositionError(f"Go Origin.Hash differs from selected version: {module['Path']}")
return commit, False
if pseudo:
return pseudo.group(1), True
raise CompositionError(f"cannot prove source commit for selected Go module: {module['Path']}@{module['Version']}")
def prepare_provider_tests(root, root_commit, components, sources, manifest, temporary,
fetch_env, rdm=None, rdm_admin=None, source_map_path=None):
"""Acquire every provider and cache before constructing the execution plan."""
graph = manifest["go"]["backend"]
source_records = {name: {"path": str(path), "commit": manifest["applications"][name]["commit"], "repo": components[name]["repo"]}
for name, path in sources.items()}
module_sources = {}
manifest["test_sources"] = {}
manifest["checks"] = []
go_checks = [{"component": BACKEND, "path": sources[BACKEND], "mode": "readonly"}]
npm_checks = [{"component": FRONTEND, "path": sources[FRONTEND], "commands": [["npm", "test"]]}]
for name, component in components.items():
if component.get("role") != "go-library":
continue
selected = [(mapping, next((m for m in graph["modules"] if m["Path"] == mapping["path"]), None))
for mapping in component.get("modules", [])]
selected = [(mapping, module) for mapping, module in selected if module is not None]
if not selected:
continue
primary = next((pair for pair in selected if pair[0].get("directory", ".") == "."), selected[0])
revision, abbreviated = provider_revision(primary[1], graph["downloads"])
source = temporary / name
record = export_source(component["repo"], revision, source, temporary, fetch_env, abbreviated=abbreviated)
manifest["test_sources"][name] = record
source_records[name] = {"path": str(source), "commit": record["commit"], "repo": component["repo"]}
sources[name] = source
for mapping in component.get("modules", []):
module = next((m for m in graph["modules"] if m["Path"] == mapping["path"]), None)
module_root = source
module_commit = record["commit"]
if module:
module_revision, module_abbreviated = provider_revision(module, graph["downloads"])
if not module_commit.startswith(module_revision):
module_root = temporary / (name + "-" + mapping["directory"].replace("/", "-"))
alternate = export_source(component["repo"], module_revision, module_root, temporary, fetch_env, abbreviated=module_abbreviated)
module_commit = alternate["commit"]
manifest["test_sources"][mapping["path"]] = alternate
location = module_root / mapping.get("directory", ".")
if not (location / "go.mod").is_file():
raise CompositionError(f"declared provider module is missing: {mapping['path']}")
module_sources[mapping["path"]] = {"path": str(location), "commit": module_commit, "repo": component["repo"]}
manifest["go"][mapping["path"]] = go_dependencies(location, fetch_env)
go_checks.append({"component": name, "module": mapping["path"], "path": location, "mode": "readonly"})
# UI is a package provider; its source identity belongs to the npm lock.
ui_component = components.get("Agent-System-UI")
if ui_component:
package_name = ui_component.get("package_name", "@agent-system/ui")
resolved = manifest["npm"]["packages"].get("node_modules/" + package_name, {}).get("resolved", "")
repo, _, commit = resolved.removeprefix("git+").rpartition("#")
if repo != ui_component["repo"] or not SHA.fullmatch(commit):
raise CompositionError("cannot prove UI source from the Frontend npm lock")
ui = temporary / "Agent-System-UI"
record = export_source(repo, commit, ui, temporary, fetch_env)
sources["Agent-System-UI"] = ui
manifest["test_sources"]["Agent-System-UI"] = record
source_records["Agent-System-UI"] = {"path": str(ui), "commit": commit, "repo": repo}
for name, source in sources.items():
if name == FRONTEND or not (source / "package.json").is_file():
continue
manifest.setdefault("provider_npm", {})[name] = npm_dependencies(source, components, fetch_env)
scripts = json.loads((source / "package.json").read_text()).get("scripts", {})
commands = [["npm", "run", action] for action in ("typecheck", "test") if action in scripts]
npm_checks.append({"component": name, "path": source, "commands": commands})
if rdm:
source_records["Agent-System-RDM"] = {"path": str(rdm), **{key: manifest["applications"]["Agent-System-RDM"][key] for key in ("commit", "repo")}}
go_checks.extend([
{"component": "Agent-System-RDM", "path": rdm, "mode": "vendor"},
{"component": "RDM-Backend", "path": rdm_admin, "mode": "readonly", "commit": manifest["go"]["rdm"]["backend_source"]["commit"]},
])
formal_root = temporary / "Dev-test-root"
archive = run(["git", "archive", "--format=tar", root_commit], root, fetch_env, binary=True, quiet=True)
extract_archive(archive, formal_root)
source_map = {"schema": 1, "root_commit": root_commit, "components": source_records, "modules": module_sources}
map_file = source_map_path or temporary / "composition-sources.json"
map_file.parent.mkdir(parents=True, exist_ok=True)
map_file.write_text(json.dumps(source_map, indent=2) + "\n")
gates = ((["bash", "scripts/lint-boundaries.sh"], "scripts/lint-boundaries.sh"),
(["bash", "scripts/check-pins.sh"], "scripts/check-pins.sh"),
(["make", "test-governance"], "Makefile"))
project_gates = any((formal_root / required_path).is_file() for _, required_path in gates)
for command, required_path in gates:
if project_gates and not (formal_root / required_path).is_file():
raise CompositionError(f"fixed root is missing required test gate: {required_path}")
return {"go": go_checks, "npm": npm_checks, "root": formal_root, "root_commit": root_commit,
"map_file": map_file, "rdm": rdm,
"gates": [command for command, required_path in gates if (formal_root / required_path).is_file()]}
def run_provider_tests(prepared, manifest, clean_env):
"""Execute a completed plan; this phase has no acquisition environment."""
for check in prepared["npm"][:1]:
for command in check["commands"]:
run(command, check["path"], clean_env)
manifest["checks"].append({"component": check["component"], "command": command, "result": "PASS"})
for check in prepared["go"]:
for action in ("test", "vet"):
command = ["go", action, "-mod=" + check["mode"], "./..."]
run(command, check["path"], clean_env)
identity = {key: check[key] for key in ("component", "module", "commit") if key in check}
manifest["checks"].append({**identity, "command": command, "result": "PASS"})
for check in prepared["npm"][1:]:
for command in check["commands"]:
run(command, check["path"], clean_env)
manifest["checks"].append({"component": check["component"], "command": command, "result": "PASS"})
gate_env = clean_env | {"COMPOSITION_SOURCE_MAP": str(prepared["map_file"])}
if prepared["rdm"]:
gate_env["COMPOSITION_RDM_SOURCE"] = str(prepared["rdm"])
for command in prepared["gates"]:
run(command, prepared["root"], gate_env)
manifest["checks"].append({"component": "Agent-System-Dev", "commit": prepared["root_commit"], "command": command, "result": "PASS"})
def compose(root, target, output, rdm_source=None, source_map_path=None):
tooling = tooling_identity(root)
transport_path = Path(__file__).resolve().parent / "workspace/transport.py"
spec = importlib.util.spec_from_file_location("composition_transport", transport_path)
transport = importlib.util.module_from_spec(spec)
spec.loader.exec_module(transport)
fetch_env = transport.environment(root)
fetch_env.update(GOWORK="off", GOFLAGS="", GOTOOLCHAIN="local")
root_commit, components, lock, input_hashes = inputs(root, fetch_env)
if root_commit != tooling["root_commit"]:
raise CompositionError("root identity changed during tooling validation")
clean_env = execution_environment(fetch_env)
manifest = {"schema": 1, "kind": "agent-system-composition/1", "target": target,
"root": {"commit": root_commit, "inputs": input_hashes}, "tooling": tooling,
"applications": {}, "go": {}, "artifacts": {}, "assets": {}}
with tempfile.TemporaryDirectory(prefix="agent-system-composition-") as directory:
temporary = Path(directory)
sources = {}
for name in (FRONTEND, BACKEND):
if target == "frontend" and name == BACKEND:
continue
source = temporary / name
manifest["applications"][name] = export_source(components[name]["repo"], lock[name]["commit"], source, temporary, fetch_env)
sources[name] = source
frontend = sources[FRONTEND]
manifest["npm"] = npm_dependencies(frontend, components, fetch_env)
products = temporary / "products"
products.mkdir()
rdm = None
admin_source = None
vendored_backend = None
test_plan = None
manifest["version"] = version(manifest, lock)
if target != "frontend":
backend = sources[BACKEND]
manifest["go"]["backend"] = go_dependencies(backend, fetch_env)
manifest["version"] = version(manifest, lock)
if rdm_source:
rdm_commit = run(["git", "rev-parse", "HEAD"], rdm_source, fetch_env, quiet=True).strip()
rdm = temporary / "Agent-System-RDM"
manifest["applications"]["Agent-System-RDM"] = export_source(str(rdm_source), rdm_commit, rdm, temporary, fetch_env)
graph = go_dependencies(rdm, fetch_env)
manifest["go"]["rdm"] = graph
backend_module = next((m["path"] for m in components[BACKEND]["modules"] if m.get("directory", ".") == "."), None)
selected = next((m for m in graph["modules"] if m["Path"] == backend_module), None)
if not selected:
raise CompositionError("RDM must consume the fixed Backend as an ordinary versioned Go module")
effective = selected.get("Replace", selected)
if effective.get("Path") != backend_module or not effective.get("Version"):
raise CompositionError("RDM Backend must resolve to the approved versioned module")
# RDM's module graph is its dependency authority. The Dev app
# source lock fixes a separate application and may differ.
revision, abbreviated = provider_revision(effective, graph["downloads"])
admin_source = temporary / "RDM-Backend"
backend_record = export_source(components[BACKEND]["repo"], revision, admin_source,
temporary, fetch_env, abbreviated=abbreviated)
graph["backend_source"] = {**backend_record, "module": backend_module, "version": effective["Version"]}
manifest["go"]["rdm_admin"] = go_dependencies(admin_source, fetch_env)
run(["go", "mod", "vendor"], rdm, fetch_env)
vendored_backend = rdm / "vendor" / backend_module
if not (vendored_backend / "web/embed.go").is_file():
raise CompositionError("RDM vendored Backend does not contain the embed package")
graph["vendor_modules_sha256"] = digest(rdm / "vendor/modules.txt")
graph["vendor_backend_source_sha256"] = tree_digest(vendored_backend)["sha256"]
if target == "test":
test_plan = prepare_provider_tests(root, root_commit, components, sources, manifest, temporary,
fetch_env, rdm, admin_source, source_map_path)
if target == "build" and run(["git", "ls-tree", root_commit, "--", "plugins"], root, fetch_env, quiet=True).strip():
archive = run(["git", "archive", "--format=tar", root_commit, "plugins"], root, fetch_env, binary=True, quiet=True)
root_assets = temporary / "root-assets"
extract_archive(archive, root_assets)
manifest["assets"]["plugins"] = tree_digest(root_assets / "plugins")
manifest["assets"]["plugins"]["root_commit"] = root_commit
# All fixed sources, package caches, vendor files and root assets are
# acquired above. Candidate commands below receive only clean_env;
# there is no acquisition environment in the execution plan.
del fetch_env
dist, manifest["frontend"] = frontend_assets(frontend, clean_env)
manifest["toolchains"] = {"go": run(["go", "version"], temporary, clean_env, quiet=True).strip(),
"node": run(["node", "--version"], temporary, clean_env, quiet=True).strip(),
"npm": run(["npm", "--version"], temporary, clean_env, quiet=True).strip()}
if target != "frontend":
inject(dist, backend / "web/dist")
manifest["go"]["backend"]["embed_sha256"] = tree_digest(backend / "web/dist")["sha256"]
if rdm:
graph = manifest["go"]["rdm"]
inject(dist, admin_source / "web/dist")
manifest["go"]["rdm_admin"]["embed_sha256"] = tree_digest(admin_source / "web/dist")["sha256"]
graph["admin_version"] = version(manifest, lock, backend_commit=graph["backend_source"]["commit"],
resolved_go={"rdm": graph["modules"], "admin": manifest["go"]["rdm_admin"]})
inject(dist, vendored_backend / "web/dist")
graph["vendor_backend_dist_sha256"] = tree_digest(vendored_backend / "web/dist")["sha256"]
if target == "test":
run_provider_tests(test_plan, manifest, clean_env)
else:
for name, package in (("agent-system", "./cmd/agent-system"), ("storage-migrate", "./cmd/storage-migrate")):
flags = "-s -w" + (" -X main.version=" + manifest["version"] if name == "agent-system" else "")
run(["go", "build", "-mod=readonly", "-trimpath", "-buildvcs=false", "-ldflags=" + flags, "-o", str(products / name), package], backend, clean_env)
if rdm:
run(["go", "build", "-mod=vendor", "-trimpath", "-buildvcs=false", "-ldflags=-s -w", "-o", str(products / "agent-system-rdm-product"), "."], rdm, clean_env)
run(["go", "build", "-mod=readonly", "-trimpath", "-buildvcs=false",
"-ldflags=-s -w -X main.version=" + graph["admin_version"],
"-o", str(products / "agent-system-admin"), "./cmd/agent-system"], admin_source, clean_env)
for artifact in products.iterdir():
manifest["artifacts"][artifact.name] = {"sha256": digest(artifact), "size": artifact.stat().st_size}
if tooling_identity(root, clean_env) != tooling:
raise CompositionError("executing tooling changed during composition")
# Publish only after all required build/test commands succeeded.
output.mkdir(parents=True, exist_ok=True)
for artifact in products.iterdir():
shutil.copy2(artifact, output / artifact.name)
if "plugins" in manifest["assets"]:
destination = output / "plugins"
if destination.exists():
shutil.rmtree(destination)
shutil.copytree(root_assets / "plugins", destination)
if target == "frontend":
destination = output / "frontend-dist"
if destination.exists():
shutil.rmtree(destination)
shutil.copytree(dist, destination)
pending = output / ".resolved-manifest.json.tmp"
pending.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
pending.replace(output / "resolved-manifest.json")
print(f"[composition] complete: {target}; manifest={output / 'resolved-manifest.json'}", flush=True)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--target", required=True, choices=("frontend", "build", "test"))
parser.add_argument("--output", type=Path)
parser.add_argument("--rdm-source", type=Path)
parser.add_argument("--source-map", type=Path, help="Write source identity map used by test gates; paths exist only during this run")
args = parser.parse_args()
root = args.root.resolve()
try:
compose(root, args.target, (args.output or root / "bin").resolve(), args.rdm_source.resolve() if args.rdm_source else None,
args.source_map.resolve() if args.source_map else None)
except (CompositionError, OSError, ValueError, KeyError, TypeError) as error:
print(f"composition: {error}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())
+6
View File
@@ -43,6 +43,12 @@ def card_path(path):
def check_snapshot(snapshot):
evidence.validate_snapshot(snapshot)
stale = check_head(snapshot["head"])
current = evidence.component_snapshot(Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip()))
if current is not None and snapshot.get('components') != current:
raise ValueError('recorded ordinary component/source-lock objects differ from the current objects')
for item in snapshot.get('components', {}).get('components', {}).values():
if item.get('status') or item.get('untracked'):
raise ValueError('recorded ordinary component includes uncommitted changes')
for line in snapshot["status"].splitlines():
if len(line) < 4 or line[2] != " ":
raise ValueError("invalid recorded worktree status")
+48
View File
@@ -5,6 +5,54 @@ set -euo pipefail
root=$(cd "$(dirname "$0")/.." && pwd)
cd "$root"
# New layouts have one version authority per dependency kind. Inspect the formal
# source map when supplied, otherwise verify prepared fixed application sources.
if [ -f components.json ]; then
python3 - "$root" <<'PYCODE'
import importlib.util, json, os, pathlib, subprocess, sys
root = pathlib.Path(sys.argv[1])
sys.path.insert(0, str(root / "scripts"))
spec = importlib.util.spec_from_file_location("component_source", root / "scripts/component-source.py")
resolver = importlib.util.module_from_spec(spec); spec.loader.exec_module(resolver)
try:
document = json.loads((root / "components.json").read_text())
components = {entry["id"]: entry for entry in document["components"]}
marker = os.environ.get("COMPOSITION_SOURCE_MAP")
if marker:
names = [name for name in json.loads(pathlib.Path(marker).read_text())["components"] if name in components]
else:
names = [name for name, entry in components.items() if entry["role"] == "application"]
for name in names:
component = components[name]
path = resolver.source_path(root, name)
if component["role"] == "application" and (path / ".gitmodules").exists():
raise ValueError("legacy gitlink declaration remains in fixed source: " + name)
for module in component.get("modules", []):
directory = path / module.get("directory", ".")
if not (directory / "go.mod").is_file():
raise ValueError("declared module source is missing: " + name)
env = os.environ | {"GOWORK": "off", "GOTOOLCHAIN": "local"}
result = subprocess.run(["go", "mod", "edit", "-json"], cwd=directory, env=env, capture_output=True, text=True)
if result.returncode: raise ValueError("Go module cannot be parsed: " + name)
parsed = json.loads(result.stdout)
if parsed["Module"]["Path"] != module["path"]:
raise ValueError("module identity differs from approved mapping: " + name)
if any(not replacement["New"].get("Version") for replacement in parsed.get("Replace") or []):
raise ValueError("local replace in fixed source: " + name)
npm_lock = path / "package-lock.json"
if npm_lock.exists():
for entry in json.loads(npm_lock.read_text()).get("packages", {}).values():
if entry.get("link") or str(entry.get("resolved", "")).startswith(("file:", "link:")):
raise ValueError("local npm overlay in fixed source: " + name)
print("ok fixed source " + name)
if not names: raise ValueError("fixed application sources are missing")
except (ValueError, KeyError, TypeError, OSError) as error:
print("pin: " + str(error), file=sys.stderr); sys.exit(1)
print("version authorities ok (source lock / go.mod / npm lock; no gitlink alignment)")
PYCODE
exit $?
fi
fail=0
pin_of() {
+173 -9
View File
@@ -1,14 +1,26 @@
#!/usr/bin/env python3
"""Fail closed before task synchronization, verification, or worktree disposal."""
import os
import hashlib
from pathlib import Path
import subprocess
import sys
from workspace.common import WorkspaceError
def git(repo, *args):
result = subprocess.run(["git", "-C", str(repo), *args], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
command = ["git", "-C", str(repo)]
# Retired absorbed metadata may outlive its configured work tree. Inspect
# its refs/registrations without chdir to that absent checkout.
if (repo / "HEAD").is_file() and (repo / "config").is_file() and not (repo / ".git").exists():
command += ["--git-dir=" + str(repo), "--work-tree=" + str(repo)]
environment = os.environ.copy()
environment["GIT_OPTIONAL_LOCKS"] = "0"
for variable in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
environment.pop(variable, None)
result = subprocess.run([*command, *args], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=environment)
if result.returncode:
raise RuntimeError(f"Git check failed in {repo}: {' '.join(args)}")
return result.stdout
@@ -103,6 +115,143 @@ def materialize(repo):
materialize(child)
def component_manifest(repo):
from workspace.manifest import Manifest
return Manifest(repo) if (repo / "components.json").exists() else None
def prepare_rules(repo):
"""Resolve startup reading before task writes, using an external fixed cache."""
from workspace.cli import process_environment
from workspace.git import Git
manifest = component_manifest(repo)
if manifest is None:
return
rules = [entry for entry in manifest.select() if entry["role"] == "guidelines"]
if len(rules) != 1 or rules[0]["id"] not in manifest.lock:
raise RuntimeError("exactly one fixed guidelines source is required before READY")
component = rules[0]
commit = manifest.lock[component["id"]]["commit"]
cache = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "agent-system" / "rules"
if not cache.is_absolute() or cache.resolve().is_relative_to(repo):
raise RuntimeError("rules cache must be an absolute external directory")
for parent in (cache, *cache.parents):
if parent.is_symlink():
raise RuntimeError("rules cache must not traverse symbolic links")
cache.mkdir(parents=True, exist_ok=True)
identity = hashlib.sha256((component["repo"] + "\n" + commit).encode()).hexdigest()
source = cache / identity
lock = cache / (identity + ".lock")
try:
descriptor = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
except FileExistsError as error:
raise RuntimeError("fixed rules cache has an active owner; retry without removing its lock") from error
os.close(descriptor)
try:
helper = Git(process_environment(repo))
if source.exists():
actual = helper.safe(source, component["repo"])
if actual["head"] != commit or actual["branch"]:
raise RuntimeError("fixed rules cache differs from its declared source; preserve it for its owner")
else:
helper.call(cache, "clone", "--no-checkout", "--", component["repo"], str(source))
helper.call(source, "fetch", "--no-tags", "origin", commit)
helper.call(source, "checkout", "--detach", commit)
helper.safe(source, component["repo"])
for required in ("core/baseline.md", "workflows/delivery.md"):
path = source / required
if not path.is_file() or path.is_symlink() or not path.resolve().is_relative_to(source):
raise RuntimeError(f"fixed rules source is missing required reading: {required}")
print(f"[rules] path={source} commit={commit}; required-reading root verified")
finally:
lock.unlink()
def prepare_sources(repo):
result = subprocess.run([sys.executable, str(repo / "scripts/workspace.py"), "--root", str(repo), "prepare"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode:
raise RuntimeError(result.stderr.strip() or "fixed source preparation failed; owner must reconcile")
print("[workspace] fixed application, documentation and guidelines sources prepared; edit branches preserved")
def inspect_source(child, mode, removal_roots):
if child.is_symlink() or (child / ".git").is_symlink():
raise RuntimeError(f"component Git identity is a symbolic link: {child}")
if not (child / ".git").exists() or Path(text(child, "rev-parse", "--show-toplevel")).resolve() != child:
raise RuntimeError(f"component has borrowed or missing Git identity: {child}")
check_operations(child)
if mode == "finish":
check_component_worktrees(child, removal_roots)
check_clean(child, ignored=True)
if local_work_outside(child, text(child, "rev-parse", "HEAD")):
raise RuntimeError(f"component has unpreserved local refs: {child}; preserve its source before removal")
def inspect_source_tree(root, mode, removal_roots, seen):
for current, directories, files in os.walk(root, followlinks=False):
child = Path(current)
directories[:] = [name for name in directories if name != ".git"]
if ".git" in files or (child / ".git").exists():
if child not in seen:
seen.add(child)
inspect_source(child, mode, removal_roots)
def legacy_components(repo, manifest, mode, removal_roots):
"""Keep retired physical roots and nested repositories visible after gitlink removal."""
from workspace.common import safe_path
seen = set()
for component in manifest.select():
relative = component.get("legacy_path")
if not relative:
continue
root = safe_path(repo, relative)
if not root.exists():
continue
if mode == "materialize":
print(f"[transition] retained legacy source {relative}; inspect make workspace-transition NAME={component['id']} before explicit ARGS=--apply")
inspect_source_tree(root, mode, removal_roots, seen)
def check_retained_metadata(repo, removal_roots):
"""An absent legacy checkout can still own external worktrees via absorbed metadata."""
modules = Path(text(repo, "rev-parse", "--absolute-git-dir")) / "modules"
if not modules.exists():
return
for current, directories, files in os.walk(modules, followlinks=False):
directories[:] = [name for name in directories if name not in
{"objects", "refs", "logs", "hooks", "worktrees", "lfs", "info"}]
metadata = Path(current)
if "HEAD" not in files or "config" not in files:
continue
for record in worktrees(metadata):
other = Path(os.fsdecode(record[0][9:])).resolve()
if not any(other == root or root in other.parents for root in removal_roots):
raise RuntimeError(f"registered component worktree {other} depends on retained metadata in {metadata}; preserve it before removal")
check_operations(metadata)
if local_work_outside(metadata, text(metadata, "rev-parse", "HEAD")):
raise RuntimeError(f"retained component metadata has unpreserved local refs: {metadata}")
def ordinary_components(repo, mode, removal_roots=()):
manifest = component_manifest(repo)
if manifest is None:
return
from workspace.common import safe_path
operation = safe_path(repo, ".workspace/.operation.lock")
if operation.exists():
raise RuntimeError("active workspace operation lock; preserve it for its owner")
seen = set()
for item in manifest.select():
child = safe_path(repo, item["path"])
if child.exists():
inspect_source(child, mode, removal_roots)
seen.add(child)
inspect_source_tree(child, mode, removal_roots, seen)
legacy_components(repo, manifest, mode, removal_roots)
def local_work_outside(child, expected):
"""2026-10-05 修复:pinned-history 检查此前用 --all --reflog,把 refs/remotes 也
计入——pin 在 feature 顶端的子仓因 origin/main 不在 pin 历史内而永远无法 finish
@@ -110,6 +259,10 @@ def local_work_outside(child, expected):
不在任何远端跟踪引用历史内的提交才算。克隆默认分支停在 origin/main 顶端、
reflog 里的检出轨迹等「远端有副本」的状态一律不算;stash 恒为本地独有。"""
candidates = git(child, "rev-list", "--branches", "--not", expected).split()
for row in git(child, "for-each-ref", "--format=%(objectname) %(refname)").splitlines():
tip, ref = row.split(b" ", 1)
if not ref.startswith(b"refs/remotes/"):
candidates.append(tip)
for line in git(child, "reflog", "show", "HEAD", "--format=%H").splitlines():
tip = line.strip()
if tip:
@@ -151,26 +304,37 @@ def check_finish_root(repo):
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]")
if mode not in ("rules", "verify", "before-sync", "before-update", "materialize", "after-sync", "finish"):
raise RuntimeError("usage: check-worktree.py [rules|verify|before-sync|before-update|materialize|after-sync|finish]")
repo = Path(text(Path.cwd(), "rev-parse", "--show-toplevel")).resolve()
if mode == "rules":
prepare_rules(repo)
return
check_operations(repo)
removal_roots = ()
if mode == "finish":
check_finish_root(repo)
removal_roots = (repo, Path(text(repo, "rev-parse", "--absolute-git-dir")).resolve())
if mode in ("before-sync", "after-sync", "finish"):
check_clean(repo, ignored=(mode == "finish"))
if mode in ("before-sync", "after-sync"):
check_clean(repo)
manifest = component_manifest(repo)
if mode == "materialize":
materialize(repo)
else:
if manifest is None:
materialize(repo)
else:
prepare_sources(repo)
elif manifest is None:
check_children(repo, mode, removal_roots)
ordinary_components(repo, mode, removal_roots)
if mode == "finish":
check_retained_metadata(repo, removal_roots)
check_clean(repo, ignored=True)
print(f"[worktree] {mode}: recursive component checks passed")
if __name__ == "__main__":
try:
main()
except RuntimeError as exc:
except (RuntimeError, WorkspaceError, ValueError, OSError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
sys.exit(1)
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""Prepare selected fixed sources and run explicitly local component checks."""
import argparse
from contextlib import contextmanager
import os
from pathlib import Path
import subprocess
import sys
import tempfile
from workspace.cli import Workspace, process_environment
from workspace.common import WorkspaceError
from workspace.state import State
SINGLE = {"test-backend": "Agent-System-Backend", "test-backend-pg": "Agent-System-Backend",
"test-core": "Agent-System-Core", "test-agentcmd": "Agent-Command",
"test-frontend": "Agent-System-Frontend", "test-ui": "Agent-System-UI",
"test-multi-instance": "Agent-System-Backend", "eval-deliverables": "Agent-System-Backend",
"dev-backend": "Agent-System-Backend", "dev-frontend": "Agent-System-Frontend"}
GO_CHECKS = {"test-backend", "test-backend-pg", "test-core", "test-agentcmd",
"test-libs", "test-multi-instance", "eval-deliverables"}
def run(command, cwd, env):
print("[component-check] " + " ".join(map(str, command)), flush=True)
result = subprocess.run(command, cwd=cwd, env=env)
if result.returncode:
raise WorkspaceError(f"local component command failed ({result.returncode})")
def prepare_local_source(workspace, component):
"""Repeated local npm checks may reuse only recognized generated directories."""
record = workspace.state.data["components"].get(component["id"])
path = workspace.root / component["path"]
if record and record.get("mode") == "versioned" and (path / "package.json").is_file():
actual = workspace.git.safe(path, component["repo"], clean=False)
commit, source = workspace.baseline(component)
generated = ("node_modules/", "dist/")
if actual["head"] != commit or actual["branch"] or actual["changes"] or any(not name.startswith(generated) for name in actual["ignored"]):
raise WorkspaceError(f"local source or ignored unique files require owner coordination: {component['id']}")
record.update(baseline=commit, source=source)
workspace.state.save()
return
workspace.prepare_one(component)
@contextmanager
def check_sources(workspace, selected, target, overlay):
paths = {component["id"]: workspace.root / component["path"] for component in selected}
fixed = [component for component in selected
if workspace.state.data["components"][component["id"]].get("mode") == "versioned"]
if overlay or target not in GO_CHECKS or not fixed:
yield paths
return
# Go tests can create ignored outputs (including Python bytecode). Keep
# strict source guards and execute fixed commits in owned disposable clones.
# Editable sources and explicit overlays still exercise the owner's code.
with tempfile.TemporaryDirectory(prefix="component-check-") as temporary:
area = Path(temporary)
for component in fixed:
name = component["id"]
source = paths[name]
actual = workspace.git.safe(source, component["repo"])
commit = workspace.state.data["components"][name]["baseline"]
if actual["head"] != commit or actual["branch"]:
raise WorkspaceError(f"fixed source changed before local check: {name}")
destination = area / name
workspace.git.call(area, "clone", "--no-checkout", "--no-hardlinks", "--", str(source), str(destination))
workspace.git.call(destination, "checkout", "--detach", commit)
paths[name] = destination
print(f"[component-check] fixed check snapshot {name} commit={commit}", flush=True)
yield paths
def check(root, target, overlay):
root = root.resolve()
if target in ("test-backend-pg", "test-multi-instance") and not os.environ.get("AGENT_PG_TEST_DSN"):
raise WorkspaceError("AGENT_PG_TEST_DSN required; real PostgreSQL gate is not skipped")
if target == "test-backend-pg" and not os.environ.get("AGENT_REDIS_TEST_ADDR"):
raise WorkspaceError("AGENT_REDIS_TEST_ADDR required; real Redis gate is not skipped")
workspace = Workspace(root)
names = [SINGLE[target]] if target in SINGLE else (["Agent-Native", "Agent-Intent", "Agent-Squad"] if target == "test-libs" else ["Agent-System-Backend", "Agent-System-Frontend"])
selected = [workspace.manifest.select(name)[0] for name in names]
with workspace.state.operation():
workspace.state = State(root)
if any(component["role"] in ("go-library", "npm-library") for component in selected):
for component in workspace.manifest.select(fixed=True):
if component["role"] == "application":
prepare_local_source(workspace, component)
for component in selected:
prepare_local_source(workspace, component)
workspace.snapshot()
env = process_environment(root)
if overlay:
workspace.go.check_owned()
if not workspace.state.data.get("go_work") or not (root / "go.work").is_file():
raise WorkspaceError("LOCAL_OVERLAY=1 requires a registered go.work; use component-edit first")
env["GOWORK"] = str(root / "go.work")
else:
env["GOWORK"] = "off"
print(f"[component-check] local developer sources; GOWORK={env['GOWORK']}; registered edit branches and unsaved code retained", flush=True)
with check_sources(workspace, selected, target, overlay) as paths:
run_checks(root, target, selected, names, paths, env)
def run_checks(root, target, selected, names, paths, env):
if target == "dev":
print("Backend: make dev-backend [LOCAL_OVERLAY=1]\nFrontend: make dev-frontend\nPrivate module transport is scoped to those processes.")
return
path = paths[names[0]]
if target == "dev-backend":
run(["go", "run", "./cmd/agent-system", "serve", "--addr", "127.0.0.1:8787", "--data", str(root / "data"), "--plugins", str(root / "plugins")], path, env)
elif target == "dev-frontend":
run(["npm", "ci", "--ignore-scripts", "--no-fund", "--no-audit"], path, env)
run(["npm", "run", "dev"], path, env)
elif target == "test-multi-instance":
suite = root / "tests/integration/multi-instance"
binary = suite / ".build/agent-system"
binary.parent.mkdir(parents=True, exist_ok=True)
run(["go", "build", "-mod=readonly", "-o", str(binary), "./cmd/agent-system"], path, env | {"CGO_ENABLED": "0"})
run(["go", "test", "-mod=readonly", "-count=1", "-timeout=15m", "."], suite, env | {"AGENT_BIN": str(binary)})
elif target == "eval-deliverables":
run(["go", "test", "-mod=readonly", "./eval/", "./internal/quality/", "-count=1"], path, env)
elif target in ("test-frontend", "test-ui"):
run(["npm", "ci", "--ignore-scripts", "--no-fund", "--no-audit"], path, env)
if target == "test-ui":
run(["npm", "exec", "--offline", "--", "tsc", "--noEmit"], path, env)
run(["npm", "exec", "--offline", "--", "vitest", "run"], path, env)
run(["node", "scripts/check-knip.mjs"], path, env)
else:
for component in selected:
source = paths[component["id"]]
modules = component.get("modules", []) if target == "test-libs" else [{"directory": "."}]
for module in modules:
directory = source / module.get("directory", ".")
if target in ("test-libs", "test-agentcmd"):
run(["go", "vet", "-mod=readonly", "./..."], directory, env)
run(["go", "test", "-mod=readonly", "-count=1", "./..."], directory, env)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--target", choices=(*SINGLE, "test-libs", "dev"), required=True)
parser.add_argument("--overlay", action="store_true")
args = parser.parse_args()
try:
check(args.root, args.target, args.overlay)
except (WorkspaceError, OSError, ValueError) as error:
print(f"component-check: {error}", file=sys.stderr)
raise SystemExit(1)
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""Resolve governance inputs from a formal source map or the owner's checkout."""
import json
import os
from pathlib import Path
import re
import sys
LEGACY = {"Agent-System-Backend": "modules/backend", "Agent-System-Frontend": "modules/frontend",
"Agent-System-Core": "modules/core", "Agent-System-UI": "modules/ui",
"Agent-Native": "deps/agent-native", "Agent-Intent": "deps/agent-intent", "Agent-Squad": "deps/agent-squad"}
SHA = re.compile(r"[0-9a-f]{40}\Z")
def source_path(root, name):
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]*", name):
raise ValueError("invalid component identifier")
marker = os.environ.get("COMPOSITION_SOURCE_MAP")
if not marker:
relative = ".workspace/" + name if (root / "components.json").exists() else LEGACY[name]
path = root / relative
else:
source_map = Path(marker)
if not source_map.is_absolute() or source_map.is_symlink() or not source_map.is_file():
raise ValueError("composition source map must be an absolute regular file")
document = json.loads(source_map.read_text())
if document.get("schema") != 1 or not SHA.fullmatch(str(document.get("root_commit", ""))):
raise ValueError("invalid composition source map identity")
manifest = json.loads((root / "components.json").read_text())
declared = [entry for entry in manifest["components"] if entry.get("id") == name]
item = document.get("components", {}).get(name)
if len(declared) != 1 or not isinstance(item, dict) or item.get("repo") != declared[0].get("repo") or not SHA.fullmatch(str(item.get("commit", ""))):
raise ValueError("invalid isolated component identity: " + name)
locked = json.loads((root / "components.lock.json").read_text())["components"].get(name)
if locked and item["commit"] != locked["commit"]:
raise ValueError("isolated component differs from locked source: " + name)
path = Path(item.get("path", ""))
if not path.is_absolute():
raise ValueError("isolated component path must be absolute: " + name)
if not path.is_dir() or path.resolve() != path.absolute():
raise ValueError("component source is missing or traverses a symbolic link: " + name)
return path
if __name__ == "__main__":
try:
if len(sys.argv) != 2:
raise ValueError("usage: component-source.py <component-id>")
print(source_path(Path(__file__).resolve().parents[1], sys.argv[1]))
except (OSError, ValueError, KeyError, TypeError) as error:
print("component-source: " + str(error), file=sys.stderr)
raise SystemExit(1)
+100 -48
View File
@@ -1,22 +1,22 @@
#!/usr/bin/env bash
# e2e.sh —— U4 运行验收一条龙(真实浏览器:Playwright × RDM 审计工作区)。
#
# 动线(与 frontend/e2e/rdm-audit.spec.ts 的环境契约一一对应):
# 1. 构建:RDM 二进制(RDM 仓 origin/main 临时 worktree,pins 物化)
# + 基座 CLI(本 worktree modules/backend,建号用)
# + frontend dist(本 worktree modules/frontend)
# 动线(与 Frontend e2e/rdm-audit.playwright.ts 的环境契约一一对应):
# 1. 构建:版本化候选从固定 Dev 组合取得 RDM、同源基座 CLI 与 Frontend dist;
# 历史候选继续按原 gitlink 和 pins 路径物化。
# 2. 种子:RDM -bootstrap-admin(域管理员)→ 基座 user create(登录账号)
# → serve(auth on)→ API rdm.project.create×2 + rdm.ingest(主项目)
# 3. 验收:vite preview(dist+/api 代理)→ npx playwright test
# 4. 收尾:停 serve/preview,保存认证/失败工件到 $E2E_OUT
#
# 用法:bash scripts/e2e.sh [--seed-only]
# 用法:bash scripts/e2e.sh [--seed-only|--build-only]
# --seed-only 只做构建+种子+起服务(留端口与路径在 stdout,供人工/调试)
# --build-only 只构建固定产物;不建号、不起进程、不运行浏览器或模型验收
# 环境变量:
# RDM_REPO RDM 仓路径(默认 <本仓上级>/Agent-System-RDM)
# E2E_API_PORT/E2E_UI_PORT 服务端口(默认 18791/4173)
# E2E_OWNER/E2E_PASSWORD 登录账号(默认 e2e-auditor/E2eAudit#2026)
# E2E_PASSWORD2 首次登录后的设密结果(默认 E2eAudit#2026b;种子命令用它)
# E2E_PASSWORD2 首次登录后的设密结果(默认 E2eAudit#2026b)
# E2E_PROJECT_MAIN/E2E_PROJECT_ISOLATED 项目名(默认 审计演示项目/隔离检查项目)
# E2E_OUT 工件目录(默认 .tmp/e2e/<ts>;.gitignore 已覆盖 .tmp)
# 依赖:go、node/npm、curl;首次运行会 npx playwright install chromium。
@@ -26,7 +26,13 @@ DEV_ROOT=$(git -C "$(dirname "$0")/.." rev-parse --show-toplevel)
cd "$DEV_ROOT"
SEED_ONLY=0
[ "${1:-}" = "--seed-only" ] && SEED_ONLY=1
BUILD_ONLY=0
case "${1:-}" in
--seed-only) SEED_ONLY=1 ;;
--build-only) BUILD_ONLY=1 ;;
"") ;;
*) echo "usage: scripts/e2e.sh [--seed-only|--build-only]" >&2; exit 2 ;;
esac
RDM_REPO=${RDM_REPO:-"$DEV_ROOT/../Agent-System-RDM"}
API_PORT=${E2E_API_PORT:-18791}
@@ -51,50 +57,101 @@ command -v curl >/dev/null || die "curl not found"
[ "$(git -C "$RDM_REPO" rev-parse --is-inside-work-tree 2>/dev/null)" = true ] \
|| die "RDM repo not found at $RDM_REPO (set RDM_REPO)"
if [ "$BUILD_ONLY" != 1 ]; then
for p in "$API_PORT" "$UI_PORT"; do
if timeout 2 bash -c "echo > /dev/tcp/127.0.0.1/$p" 2>/dev/null; then
die "port $p already in use (set E2E_API_PORT/E2E_UI_PORT)"
fi
done
fi
# ── 1. 构建 ─────────────────────────────────────────────
# RDM 源码经 git archive 从本地对象导出(不建 worktree、不触远端——Gitea 抖动时
# submodule 克隆会挂死);pins/* 从 RDM 主检出的已物化 submodule 快照拷贝(cp 排除
# .git,39MB 级),构建输入由此固定为 RDM origin/main + 其 gitlink 指向的本地内容。
log "build RDM binary (archive origin/main + snapshot pins from local checkout)"
RDM_WT="$OUT/rdm-src"
RDM_SHA=$(git -C "$RDM_REPO" rev-parse origin/main) || die "no origin/main in RDM repo"
mkdir -p "$RDM_WT"
git -C "$RDM_REPO" archive "$RDM_SHA" | tar -x -C "$RDM_WT"
# 主检出 pins 对齐 gitlink(本地对象已有时秒回;此时才需要网络 fetch 一次)。
git -C "$RDM_REPO" submodule update --init --recursive --quiet \
|| die "materialize RDM pins failed (is the RDM checkout reachable?)"
PIN_SHA_MAIN=$(git -C "$RDM_REPO/pins/agent-system-backend" rev-parse HEAD)
PIN_SHA_WANT=$(git -C "$RDM_REPO" ls-tree "$RDM_SHA" pins/agent-system-backend | awk '{print $3}')
[ "$PIN_SHA_MAIN" = "$PIN_SHA_WANT" ] \
|| die "RDM checkout pins/backend ($PIN_SHA_MAIN) != origin/main pin ($PIN_SHA_WANT); git -C $RDM_REPO submodule update first"
mkdir -p "$RDM_WT/pins"
tar -C "$RDM_REPO/pins" --exclude=.git -cf - agent-system-backend | tar -C "$RDM_WT/pins" -xf -
API_PID=""
UI_PID=""
cleanup() {
[ -n "${API_PID:-}" ] && { kill "$API_PID" 2>/dev/null || true; wait "$API_PID" 2>/dev/null || true; }
# Preview runs directly as one owned Vite process. Verify its command before
# signaling: a recycled PID must never stop another worktree's process.
if [ -n "${UI_PID:-}" ] && ps -p "$UI_PID" -o args= 2>/dev/null | rg -Fq -- "vite preview --port $UI_PORT"; then
if [ -n "${API_PID:-}" ]; then kill "$API_PID" 2>/dev/null || true; wait "$API_PID" 2>/dev/null || true; fi
# Preview is a single owned Vite process. Check its command before signaling
# so a recycled PID cannot stop a process from another worktree.
if [ -n "${UI_PID:-}" ] && ps -p "$UI_PID" -o args= 2>/dev/null | rg -Fq -- "vite preview --outDir $FRONTEND_DIST --port $UI_PORT"; then
kill "$UI_PID" 2>/dev/null || true
fi
}
trap cleanup EXIT
(cd "$RDM_WT" && go build -o "$OUT/agent-system-rdm" .) || die "rdm build failed"
log "rdm binary: $OUT/agent-system-rdm (RDM@${RDM_SHA:0:12}, pins@${PIN_SHA_WANT:0:12})"
log "build base CLI (user create) from the same pinned backend as the RDM binary"
# 版本一致性:建号 CLI 必须与被测 RDM 二进制共用同一基座 pin——用 Dev 顶层
# backend(main 最新)建号会把库迁到更高 schema,RDM pins(较旧)打不开。
(cd "$RDM_WT/pins/agent-system-backend" && go build -o "$OUT/agent-system" ./cmd/agent-system) \
|| die "backend cli build failed"
# ── 1. 构建 ─────────────────────────────────────────────
RDM_SHA=$(git -C "$RDM_REPO" rev-parse origin/main) || die "no origin/main in RDM repo"
RDM_MODULE=$(git -C "$RDM_REPO" show "$RDM_SHA:go.mod" | awk '$1 == "module" { print $2; exit }')
if [ -f "$DEV_ROOT/components.json" ] && [ "$RDM_MODULE" = "git-ssh.u1680832.nyat.app/weichen/Agent-System-RDM.git" ]; then
log "build versioned RDM and matching base CLI through the fixed Dev composition"
source "$DEV_ROOT/scripts/svc.sh"
RUNTIME="$OUT"
ENV_NAME=e2e-build
RDM_SRC_DIR="$RDM_REPO"
COMPOSITION_ROOT="$DEV_ROOT"
RDM_WT=$(materialize_rdm_commit "$RDM_SHA" "${RDM_SHA:0:12}") || die "versioned RDM composition failed"
cp "$RDM_WT/bin/agent-system-rdm" "$OUT/agent-system-rdm"
cp "$RDM_WT/bin/agent-system-admin" "$OUT/agent-system"
# Prepare the owner-controlled fixed source, then keep npm/cache/browser files
# in this run's independent archive, rather than modifying its .workspace.
python3 "$DEV_ROOT/scripts/workspace.py" --root "$DEV_ROOT" prepare --name Agent-System-Frontend >/dev/null || die "fixed Frontend preparation failed"
python3 "$DEV_ROOT/scripts/workspace.py" --root "$DEV_ROOT" verify --name Agent-System-Frontend >/dev/null || die "Frontend differs from fixed source"
FRONTEND_PREPARED=$(python3 "$DEV_ROOT/scripts/component-source.py" Agent-System-Frontend) || die "Frontend source identity unavailable"
FRONTEND_DIR="$OUT/frontend-src"
mkdir -p "$FRONTEND_DIR"
git -C "$FRONTEND_PREPARED" archive HEAD | tar -x -C "$FRONTEND_DIR"
python3 - "$DEV_ROOT" "$FRONTEND_DIR" <<'NPM_ENV'
import importlib.util, pathlib, subprocess, sys
root = pathlib.Path(sys.argv[1]); sys.path.insert(0, str(root / "scripts"))
from workspace.cli import process_environment
spec = importlib.util.spec_from_file_location("composer", root / "scripts/build-composition.py")
composer = importlib.util.module_from_spec(spec); spec.loader.exec_module(composer)
environment = composer.execution_environment(process_environment(root))
sys.exit(subprocess.run(["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund"], cwd=sys.argv[2], env=environment).returncode)
NPM_ENV
[ "$?" = 0 ] || die "credential-free npm acquisition failed"
make -C "$DEV_ROOT" frontend >/dev/null || die "fixed frontend build failed"
FRONTEND_DIST="$DEV_ROOT/bin/frontend-dist"
else
# Historical RDM pins and the old Dev layout retain their former build route.
if [ -f "$DEV_ROOT/components.json" ]; then
die "historical RDM e2e requires its historical Dev candidate; no mixed-layout success is claimed"
fi
FRONTEND_DIR="$DEV_ROOT/modules/frontend"
FRONTEND_DIST="$FRONTEND_DIR/dist"
# ── 1. 构建 ─────────────────────────────────────────────
# RDM 源码经 git archive 从本地对象导出(不建 worktree、不触远端——Gitea 抖动时
# submodule 克隆会挂死);pins/* 从 RDM 主检出的已物化 submodule 快照拷贝(cp 排除
# .git,39MB 级),构建输入由此固定为 RDM origin/main + 其 gitlink 指向的本地内容。
log "build RDM binary (archive origin/main + snapshot pins from local checkout)"
RDM_WT="$OUT/rdm-src"
RDM_SHA=$(git -C "$RDM_REPO" rev-parse origin/main) || die "no origin/main in RDM repo"
mkdir -p "$RDM_WT"
git -C "$RDM_REPO" archive "$RDM_SHA" | tar -x -C "$RDM_WT"
# 主检出 pins 对齐 gitlink(本地对象已有时秒回;此时才需要网络 fetch 一次)。
git -C "$RDM_REPO" submodule update --init --recursive --quiet \
|| die "materialize RDM pins failed (is the RDM checkout reachable?)"
PIN_SHA_MAIN=$(git -C "$RDM_REPO/pins/agent-system-backend" rev-parse HEAD)
PIN_SHA_WANT=$(git -C "$RDM_REPO" ls-tree "$RDM_SHA" pins/agent-system-backend | awk '{print $3}')
[ "$PIN_SHA_MAIN" = "$PIN_SHA_WANT" ] \
|| die "RDM checkout pins/backend ($PIN_SHA_MAIN) != origin/main pin ($PIN_SHA_WANT); git -C $RDM_REPO submodule update first"
mkdir -p "$RDM_WT/pins"
tar -C "$RDM_REPO/pins" --exclude=.git -cf - agent-system-backend | tar -C "$RDM_WT/pins" -xf -
(cd "$RDM_WT" && go build -o "$OUT/agent-system-rdm" .) || die "rdm build failed"
log "rdm binary: $OUT/agent-system-rdm (RDM@${RDM_SHA:0:12}, pins@${PIN_SHA_WANT:0:12})"
log "build frontend dist (contracts + ui deps + bundle, via make frontend)"
make -C "$DEV_ROOT" frontend >/dev/null || die "frontend build failed"
log "build base CLI (user create) from the same pinned backend as the RDM binary"
# 版本一致性:建号 CLI 必须与被测 RDM 二进制共用同一基座 pin——用 Dev 顶层
# backend(main 最新)建号会把库迁到更高 schema,RDM pins(较旧)打不开。
(cd "$RDM_WT/pins/agent-system-backend" && go build -o "$OUT/agent-system" ./cmd/agent-system) \
|| die "backend cli build failed"
log "build frontend dist (contracts + ui deps + bundle, via make frontend)"
make -C "$DEV_ROOT" frontend >/dev/null || die "frontend build failed"
fi
[ -x "$OUT/agent-system-rdm" ] && [ -x "$OUT/agent-system" ] && [ -f "$FRONTEND_DIST/index.html" ] || die "complete e2e build artifacts are missing"
if [ "$BUILD_ONLY" = 1 ]; then
log "build-only complete; runtime/browser/model acceptance NOT_RUN; artifacts=$OUT"
exit 0
fi
# ── 2. 种子 ─────────────────────────────────────────────
DATA="$OUT/data"
@@ -136,8 +193,6 @@ login() {
done
die "login failed"
}
# CLI 建号留下 must_change_password。会话在改密前不能调用业务命令。
# 强制设密会作废当前会话,所以设密后用新密码再登录。
finish_password_gate() {
curl -sf -b "$JAR" -c "$JAR" -X POST "$API_BASE/api/auth/password" \
-H 'Content-Type: application/json' \
@@ -164,8 +219,6 @@ PID_MAIN=$(cmd rdm.project.create "{\"name\":\"$PROJ_MAIN\"}" | python3 -c 'impo
PID_ISO=$(cmd rdm.project.create "{\"name\":\"$PROJ_ISO\"}" | python3 -c 'import json,sys; print(json.load(sys.stdin)["projectId"])')
log "seed: projects main=$PID_MAIN iso=$PID_ISO"
# I1 requires a current business grant even for a project creator or system
# administrator. Both projects are ordinary test fixtures in this private data root.
log "seed: grant explicit audit.read for the fixture owner"
cmd rdm.perm.cap '{"action":"audit.read","allow":true}' >/dev/null
cmd rdm.perm.set "{\"projectId\":\"$PID_MAIN\",\"member\":\"$OWNER\",\"action\":\"audit.read\",\"allow\":true}" >/dev/null
@@ -216,7 +269,7 @@ log "seed done"
# ── 3. 验收 ─────────────────────────────────────────────
if [ "$SEED_ONLY" = 1 ]; then
log "seed-only mode: api=$API_BASE ui=next;cookie jar=$JAR;数据=$DATA"
log "preview: (cd modules/frontend && PLAYWRIGHT_API_TARGET=$API_BASE npx vite preview --port $UI_PORT)"
log "preview: (cd $FRONTEND_DIR && PLAYWRIGHT_API_TARGET=$API_BASE npx vite preview --port $UI_PORT)"
log "inspect: curl -b $JAR $API_BASE/api/commands/rdm.inspect.summary -X POST -H 'Content-Type: application/json' -d '{\"params\":{\"projectId\":\"$PID_MAIN\"}}'"
wait
exit 0
@@ -224,9 +277,9 @@ fi
log "start vite preview at http://127.0.0.1:$UI_PORT"
(
cd "$DEV_ROOT/modules/frontend"
cd "$FRONTEND_DIR"
export PLAYWRIGHT_API_TARGET="$API_BASE"
exec ./node_modules/.bin/vite preview --port "$UI_PORT" --strictPort
exec ./node_modules/.bin/vite preview --outDir "$FRONTEND_DIST" --port "$UI_PORT" --strictPort
) >"$OUT/preview.log" 2>&1 &
UI_PID=$!
for i in $(seq 1 40); do
@@ -236,15 +289,14 @@ for i in $(seq 1 40); do
done
log "playwright (install chromium if missing)"
(cd "$DEV_ROOT/modules/frontend" && npx playwright install chromium >/dev/null 2>&1 || true)
(cd "$FRONTEND_DIR" && npx playwright install chromium >/dev/null 2>&1 || true)
log "run specs"
cd "$DEV_ROOT/modules/frontend"
cd "$FRONTEND_DIR"
export PLAYWRIGHT_BASE_URL="http://127.0.0.1:$UI_PORT" E2E_OWNER="$OWNER" \
E2E_PASSWORD="${E2E_PASSWORD:-E2eAudit#2026}" E2E_PASSWORD2="$PASSWORD2" \
E2E_PROJECT_MAIN="$PROJ_MAIN" E2E_PROJECT_ISOLATED="$PROJ_ISO"
set +e
# 本脚本的种子只覆盖审计工作区。u7 现场验收用例依赖另一套入口,不在这里跑。
npx playwright test e2e/rdm-audit.playwright.ts --reporter=list >"$OUT/playwright.log" 2>&1
PW_EXIT=$?
set -e
+57 -2
View File
@@ -82,6 +82,52 @@ def common_dir():
return str(Path(git_text("rev-parse", "--git-common-dir")).resolve())
def component_snapshot(root):
"""Record ignored ordinary checkouts without resolving or changing versions."""
manifest = root / 'components.json'
if not manifest.exists():
return None
try:
config = json.loads(manifest.read_text())
if config.get('schema') != 1 or not isinstance(config.get('components'), list):
raise EvidenceError('Invalid component manifest')
result = {'manifest_sha256': digest(manifest.read_bytes()), 'components': {}}
lock = root / 'components.lock.json'
result['lock_sha256'] = digest(lock.read_bytes()) if lock.exists() else None
state = root / '.workspace/state.json'
result['state_sha256'] = digest(state.read_bytes()) if state.exists() else None
for item in config['components']:
name, relative = item.get('id'), item.get('path')
if not isinstance(name, str) or not isinstance(relative, str) or relative != '.workspace/' + name:
raise EvidenceError('Invalid component identity/path')
path = root / relative
if path.resolve() != path.absolute() or root.resolve() not in path.resolve().parents:
raise EvidenceError('Component path escapes its owner')
if not path.exists():
result['components'][name] = {'state': 'missing'}
continue
if not (path / '.git').exists():
raise EvidenceError('Component source has no Git identity')
if Path(git_text('-C', str(path), 'rev-parse', '--show-toplevel')).resolve() != path.resolve():
raise EvidenceError('Component Git identity is borrowed')
untracked = {}
for raw in git('-C', str(path), 'ls-files', '--others', '--exclude-standard', '-z').split(b'\0'):
if raw:
filename = os.fsdecode(raw)
entry = path / filename
untracked[filename] = digest(os.fsencode(os.readlink(entry))) if entry.is_symlink() else (digest(entry.read_bytes()) if entry.is_file() else None)
result['components'][name] = {
'head': git_text('-C', str(path), 'rev-parse', 'HEAD'),
'branch': git_text('-C', str(path), 'branch', '--show-current'),
'status': git('-C', str(path), 'status', '--porcelain', '--untracked-files=all').decode('utf-8', 'replace'),
'tracked_diff_sha256': digest(git('-C', str(path), 'diff', '--binary', 'HEAD', '--')),
'untracked': untracked,
}
return result
except (OSError, ValueError, TypeError, AttributeError) as error:
raise EvidenceError('Component object could not be recorded') from error
def snapshot():
untracked = {}
root = Path(git_text("rev-parse", "--show-toplevel"))
@@ -98,7 +144,7 @@ def snapshot():
else:
untracked[name] = None
target = os.environ.get("TASK_TARGET_BRANCH", "main")
return {
result = {
"head": git_text("rev-parse", "HEAD"),
"branch": git_text("rev-parse", "--abbrev-ref", "HEAD"),
"target_branch": target,
@@ -109,6 +155,10 @@ def snapshot():
"untracked": untracked,
"submodules": git("submodule", "status", "--recursive").decode("utf-8", "replace"),
}
components = component_snapshot(root)
if components is not None:
result['components'] = components
return result
def create_receipt(command, output, exit_code, before, after, *, timestamp=None,
@@ -135,8 +185,10 @@ def validate_snapshot(value):
raise EvidenceError("Invalid receipt object snapshot")
expected = {"head", "branch", "target_branch", "target_head", "status",
"tracked_diff_sha256", "submodules", "untracked"}
if set(value) != expected:
if set(value) not in (expected, expected | {'components'}):
raise EvidenceError("Invalid receipt object fields")
if 'components' in value and (not isinstance(value['components'], dict) or not isinstance(value['components'].get('components'), dict)):
raise EvidenceError('Invalid component snapshot')
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)
@@ -252,6 +304,9 @@ def validate_precheck(receipt, purpose="start"):
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 current.get("components") is not None and any(
obj.get("components") != current["components"] for obj in (receipt["before"], after)):
raise EvidenceError("Precheck ordinary component objects have changed or are missing")
if after["target_branch"] != current["target_branch"] or not after["target_head"]:
raise EvidenceError("Precheck authoritative target has changed")
if purpose != "resume-local" and after["target_head"] != current["target_head"]:
+49 -5
View File
@@ -11,6 +11,7 @@ import (
"go/parser"
"go/token"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
@@ -115,7 +116,11 @@ func checkPolicy(root string) error {
return err
}
text := string(raw)
for _, need := range []string{"modules/backend/internal", "modules/frontend/src", "components"} {
backend, frontend := "modules/backend/internal", "modules/frontend/src"
if _, err := os.Stat(filepath.Join(root, "components.json")); err == nil {
backend, frontend = ".workspace/Agent-System-Backend/internal", ".workspace/Agent-System-Frontend/src"
}
for _, need := range []string{backend, frontend, "components"} {
if !strings.Contains(text, need) {
return fmt.Errorf("policy/architecture.yaml missing %s", need)
}
@@ -123,14 +128,36 @@ func checkPolicy(root string) error {
return nil
}
func componentRoot(root, id, legacy string) (string, error) {
if os.Getenv("COMPOSITION_SOURCE_MAP") != "" {
output, err := exec.Command("python3", filepath.Join(root, "scripts/component-source.py"), id).CombinedOutput()
if err != nil {
return "", fmt.Errorf("isolated component lookup failed: %s", strings.TrimSpace(string(output)))
}
return strings.TrimSpace(string(output)), nil
}
if _, err := os.Stat(filepath.Join(root, "components.json")); err == nil {
return filepath.Join(root, ".workspace", id), nil
}
return filepath.Join(root, legacy), nil
}
func collect(root string) ([]string, error) {
var edges []string
be, err := backendEdges(filepath.Join(root, "modules/backend/internal"))
backend, err := componentRoot(root, "Agent-System-Backend", "modules/backend")
if err != nil {
return nil, err
}
frontend, err := componentRoot(root, "Agent-System-Frontend", "modules/frontend")
if err != nil {
return nil, err
}
be, err := backendEdges(filepath.Join(backend, "internal"))
if err != nil {
return nil, err
}
edges = append(edges, be...)
fe, err := frontendEdges(filepath.Join(root, "modules/frontend/src"))
fe, err := frontendEdges(filepath.Join(frontend, "src"))
if err != nil {
return nil, err
}
@@ -139,8 +166,26 @@ func collect(root string) ([]string, error) {
}
func backendEdges(dir string) ([]string, error) {
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
return nil, fmt.Errorf("backend source directory is unavailable: %s", dir)
}
raw, err := os.ReadFile(filepath.Join(filepath.Dir(dir), "go.mod"))
if err != nil {
return nil, err
}
prefix := ""
for _, line := range strings.Split(string(raw), "\n") {
fields := strings.Fields(line)
if len(fields) >= 2 && fields[0] == "module" {
prefix = fields[1] + "/internal/"
break
}
}
if prefix == "" {
return nil, fmt.Errorf("backend module identity is unavailable")
}
var edges []string
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
@@ -158,7 +203,6 @@ func backendEdges(dir string) ([]string, error) {
}
for _, imp := range f.Imports {
p := strings.Trim(imp.Path.Value, `"`)
const prefix = "agent-system-backend/internal/"
if !strings.HasPrefix(p, prefix) {
continue
}
+7 -6
View File
@@ -4,23 +4,24 @@
set -euo pipefail
root=$(cd "$(dirname "$0")/.." && pwd)
base="$root/policy/architecture-baseline.json"
tool=(go run "$root/scripts/lint-architecture.go" -root "$root" -baseline "$base")
tool=(env GOWORK=off go run "$root/scripts/lint-architecture.go" -root "$root" -baseline "$base")
( cd "$root/scripts" && "${tool[@]}" )
probe=$(mktemp)
trap 'rm -f "$probe"' EXIT
set +e
( cd "$root/scripts" && "${tool[@]}" -inject 'backend/store->backend/__architecture_probe__' ) >/tmp/architecture-probe.out 2>&1
( cd "$root/scripts" && "${tool[@]}" -inject 'backend/store->backend/__architecture_probe__' ) >"$probe" 2>&1
code=$?
set -e
if [ "$code" -eq 0 ]; then
echo "architecture: injected edge was accepted" >&2
cat /tmp/architecture-probe.out >&2
cat "$probe" >&2
exit 1
fi
if ! grep -q '__architecture_probe__' /tmp/architecture-probe.out; then
if ! grep -q '__architecture_probe__' "$probe"; then
echo "architecture: injected edge did not fail for the expected reason" >&2
cat /tmp/architecture-probe.out >&2
cat "$probe" >&2
exit 1
fi
rm -f /tmp/architecture-probe.out
echo "architecture negative ok"
+71 -53
View File
@@ -1,60 +1,85 @@
#!/usr/bin/env bash
# 依赖边界:公开扩展 SDK 与示例不得导入后端 internal。
# pkg/extension/link 是唯一例外:下游空白导入它才能把 extension.Main 接到产品组合根。
# Public SDK isolation and sibling-library ratchets use the fixed composition map
# when supplied; a manual invocation uses sources prepared by the checkout owner.
set -euo pipefail
command -v rg >/dev/null 2>&1 || { echo "boundary: requires ripgrep (rg)" >&2; exit 2; }
root=$(cd "$(dirname "$0")/.." && pwd)
fail=0
source_path() { python3 "$root/scripts/component-source.py" "$1"; }
module_path() {
local mod
mod=$(awk '/^module /{print $2; exit}' "$1/go.mod")
[ -n "$mod" ] || { echo "boundary: source lacks a module identity: $1" >&2; return 1; }
printf '%s\n' "$mod"
}
backend=$(source_path Agent-System-Backend)
intent=$(source_path Agent-Intent)
native=$(source_path Agent-Native)
squad=$(source_path Agent-Squad)
backend_mod=$(module_path "$backend")
intent_mod=$(module_path "$intent")
native_mod=$(module_path "$native")
squad_mod=$(module_path "$squad")
check() {
local path="$1"
if [ -d "$path" ] && grep -R -n --include='*.go' --include='*.ts' --include='*.tsx' 'agent-system-backend/internal' "$path" >/dev/null; then
echo "boundary: $path imports agent-system-backend/internal" >&2
grep -R -n --include='*.go' --include='*.ts' --include='*.tsx' 'agent-system-backend/internal' "$path" >&2
if [ -d "$path" ] && rg -n --glob '*.go' --glob '*.ts' --glob '*.tsx' -F \
-e "$backend_mod/internal" -e 'agent-system-backend/internal' "$path" >/dev/null; then
echo "boundary: $path imports backend internal" >&2
rg -n --glob '*.go' --glob '*.ts' --glob '*.tsx' -F \
-e "$backend_mod/internal" -e 'agent-system-backend/internal' "$path" >&2
fail=1
fi
}
sdk="$root/modules/backend/pkg/extension"
sdk="$backend/pkg/extension"
if [ -d "$sdk" ]; then
hits=$(grep -R -n --include='*.go' 'agent-system-backend/internal' "$sdk" | grep -v '/link/' || true)
hits=$(rg -n --glob '*.go' --glob '!link/**' -F -e "$backend_mod/internal" \
-e 'agent-system-backend/internal' "$sdk" | { grep -v '/link/' || true; } || true)
if [ -n "$hits" ]; then
echo "boundary: pkg/extension imports agent-system-backend/internal" >&2
echo "boundary: pkg/extension imports backend internal" >&2
echo "$hits" >&2
fail=1
fi
fi
check "$root/examples"
# 库间依赖棘轮:三个库的包导入兄弟库的配对,相对白名单只许减少、不许新增。
# Preserve the allowlist's semantic package names when public module prefixes
# change. No baseline is regenerated during dependency migration.
normalize() {
local value="$1" idx
for idx in 0 1 2; do
case "$value" in
"${canonical[$idx]}") printf '%s' "${legacy[$idx]}"; return ;;
"${canonical[$idx]}"/*) printf '%s%s' "${legacy[$idx]}" "${value#"${canonical[$idx]}"}"; return ;;
esac
done
printf '%s' "$value"
}
canonical=("$intent_mod" "$native_mod" "$squad_mod")
legacy=(agent-intent agent-native agent-squad)
siblings=("${canonical[@]}" "${legacy[@]}")
allow="$root/policy/lib-deps-allowlist.txt"
observed=$(mktemp)
recorded=$(mktemp)
trap 'rm -f "$observed" "$recorded"' EXIT
siblings=(agent-intent agent-native agent-squad)
for dir in deps/agent-intent deps/agent-native deps/agent-squad; do
mod=$(awk '/^module /{print $2; exit}' "$root/$dir/go.mod")
(
cd "$root/scripts"
go run lint-imports.go "$root/$dir"
) | while read -r importer rest; do
imports=$(mktemp)
trap 'rm -f "$observed" "$recorded" "$imports"' EXIT
for dir in "$intent" "$native" "$squad"; do
mod=$(module_path "$dir")
(cd "$root/scripts" && GOWORK=off go run lint-imports.go "$dir") > "$imports"
while read -r importer rest; do
for imp in $rest; do
for sib in "${siblings[@]}"; do
case "$imp" in
"$sib"|"$sib"/*) ;;
*) continue ;;
esac
case "$importer" in
"$mod"|"$mod"/*) ;;
*) continue ;;
esac
if [ "$sib" != "$mod" ]; then
echo "$importer $imp"
case "$imp" in "$sib"|"$sib"/*) ;; *) continue ;; esac
case "$importer" in "$mod"|"$mod"/*) ;; *) continue ;; esac
if [ "$(normalize "$sib")" != "$(normalize "$mod")" ]; then
printf '%s %s\n' "$(normalize "$importer")" "$(normalize "$imp")"
fi
done
done
done
done | sort -u >"$observed"
grep -v -e '^#' -e '^$' "$allow" | sort -u >"$recorded" || true
done < "$imports"
done | sort -u > "$observed"
grep -v -e '^#' -e '^$' "$allow" | sort -u > "$recorded" || true
new_pairs=$(comm -13 "$recorded" "$observed" || true)
if [ -n "$new_pairs" ]; then
echo "boundary: new sibling-library import pairs" >&2
@@ -67,34 +92,27 @@ if [ -n "$stale_pairs" ]; then
echo "$stale_pairs" >&2
fi
# 核心模块的依赖图不得出现兄弟库。go list ./... 不进入带 go.mod 的子目录。
for dir in deps/agent-intent deps/agent-native deps/agent-squad; do
mod=$(awk '/^module /{print $2; exit}' "$root/$dir/go.mod")
deps_out=$(cd "$root/$dir" && GOWORK=off go list -deps ./...) || fail=1
for dir in "$intent" "$native" "$squad"; do
mod=$(module_path "$dir")
deps_out=$(cd "$dir" && GOWORK=off go list -deps ./...) || fail=1
for sib in "${siblings[@]}"; do
[ "$sib" = "$mod" ] && continue
if printf '%s\n' "$deps_out" | grep -E -q "^(${sib}|${sib}/)"; then
echo "boundary: $mod core depends on $sib" >&2
fail=1
fi
[ "$(normalize "$sib")" = "$(normalize "$mod")" ] && continue
while IFS= read -r dep; do
case "$dep" in
"$sib"|"$sib"/*) echo "boundary: $mod core depends on $sib" >&2; fail=1 ;;
esac
done <<< "$deps_out"
done
done
# agent-intent 只许被小队意图适配器和后端识别适配器导入。
while IFS= read -r f; do
grep -q '"agent-intent/' "$f" || continue
case "$f" in
*/deps/agent-squad/adapters/intent/*|*/modules/backend/internal/intentgate/*|*/modules/backend/internal/intentsem/*) ;;
*)
echo "boundary: $f imports agent-intent" >&2
fail=1
;;
while IFS= read -r file; do
case "$file" in
"$squad"/adapters/intent/*|"$backend"/internal/intentgate/*|"$backend"/internal/intentsem/*) ;;
*) echo "boundary: $file imports agent-intent" >&2; fail=1 ;;
esac
done < <(find "$root/deps/agent-native" "$root/deps/agent-squad" "$root/modules/backend" \
-path '*/deps/*' -prune -o -name '*.go' -print)
done < <(rg -l --glob '*.go' --glob '!**/deps/**' -F -e "\"$intent_mod\"" \
-e "\"$intent_mod/" -e '"agent-intent"' -e '"agent-intent/' "$native" "$squad" "$backend" || true)
if [ "$fail" -ne 0 ]; then
exit 1
fi
[ "$fail" -eq 0 ] || exit 1
bash "$root/scripts/lint-architecture.sh"
echo "boundaries ok"
+114 -22
View File
@@ -2247,6 +2247,68 @@ svc_deploy_recycle_wt() {
return 0
}
# Source format belongs to the candidate being built, not the caller's checkout.
svc_versioned_rdm() {
[ -f "$1/go.mod" ] && [ "$(awk '$1 == "module" { print $2; exit }' "$1/go.mod")" = "git-ssh.u1680832.nyat.app/weichen/Agent-System-RDM.git" ]
}
svc_check_build_worktree() {
local wt="$1" expected="$2" actual marker path
actual=$(git -C "$wt" rev-parse HEAD) || return 1
[ "$actual" = "$expected" ] || { c_err "candidate SHA mismatch: expected=$expected actual=$actual; cached worktree retained"; return 1; }
[ "$(git -C "$wt" rev-parse --show-toplevel)" = "$wt" ] || { c_err "candidate worktree identity mismatch; preserve its owner"; return 1; }
for marker in index.lock HEAD.lock MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD rebase-merge rebase-apply BISECT_LOG; do
path=$(git -C "$wt" rev-parse --path-format=absolute --git-path "$marker") || return 1
[ ! -e "$path" ] || { c_err "candidate Git operation/lock active: $marker; preserve its owner"; return 1; }
done
if [ -f "$wt/components.json" ] || svc_versioned_rdm "$wt"; then
[ -z "$(GIT_OPTIONAL_LOCKS=0 git -C "$wt" status --porcelain --untracked-files=all --ignore-submodules=all)" ] \
|| { c_err "candidate source is dirty; preserve it before retrying the fixed build"; return 1; }
fi
}
svc_frontend_dist() {
local root="$1"
if [ -f "$root/components.json" ]; then
python3 - "$root/bin/resolved-manifest.json" "$(git -C "$root" rev-parse HEAD)" <<'PY'
import json, sys
try:
record = json.load(open(sys.argv[1]))
if record.get("target") != "frontend" or record.get("root", {}).get("commit") != sys.argv[2]:
raise ValueError("frontend composition identity mismatch")
except (OSError, ValueError) as error:
print("svc: " + str(error), file=sys.stderr); sys.exit(1)
PY
[ "$?" = 0 ] || return 1
printf '%s\n' "$root/bin/frontend-dist"
else
printf '%s\n' "$root/modules/frontend/dist"
fi
}
svc_composition_plugins() {
local root="$1" present
present=$(python3 - "$root/bin/resolved-manifest.json" "$(git -C "$root" rev-parse HEAD)" <<'PY'
import json, sys
try:
record = json.load(open(sys.argv[1]))
if record.get("target") != "build" or record.get("root", {}).get("commit") != sys.argv[2]:
raise ValueError("build composition identity mismatch")
print("yes" if "plugins" in record.get("assets", {}) else "no")
except (OSError, ValueError) as error:
print("svc: " + str(error), file=sys.stderr); sys.exit(1)
PY
) || return 1
if [ "$present" = yes ]; then
[ -d "$root/bin/plugins" ] || { c_err "composed plugins are missing"; return 1; }
printf '%s\n' "$root/bin/plugins"
else
mkdir -p "$root/.svc-empty-plugins"
[ -z "$(find "$root/.svc-empty-plugins" -mindepth 1 -print -quit)" ] || { c_err "empty plugin source contains unique files; preserve it"; return 1; }
printf '%s\n' "$root/.svc-empty-plugins"
fi
}
# materialize_dev_commit FULL SHA TARGET:隔离 worktree 物化+构建;stdout 只回显 worktree
# 路径(提示信息走 stderr)。TARGET=build(agent)| frontend(static)。
materialize_dev_commit() {
@@ -2258,12 +2320,16 @@ materialize_dev_commit() {
c_info "[$ENV_NAME] 物化 Dev 提交 $sha → 隔离 worktree(源:独立部署源仓 $SRC_DIR,不触碰当前工作区)"
git -C "$SRC_DIR" worktree add --detach "$wt" "$full" >/dev/null || return 1
fi
c_info "[$ENV_NAME] 物化子模块组合(git submodule update --init --recursive;缺对象时需网络)"
# stdout 只留给最后的 worktree 路径(调用方 $() 捕获)——子模块/构建输出全部走 stderr。
git -C "$wt" submodule update --init --recursive >&2 || {
c_err "[$ENV_NAME] 子模块物化失败(网络或对象缺失;重试会自动 fetch 部署源仓)"
return 1
}
svc_check_build_worktree "$wt" "$full" || return 1
if [ -f "$wt/components.json" ]; then
c_info "[$ENV_NAME] 固定源码清单组合(隔离装配;不初始化旧 gitlinks)"
else
c_info "[$ENV_NAME] 物化历史子模块组合(git submodule update --init --recursive)"
git -C "$wt" submodule update --init --recursive >&2 || {
c_err "[$ENV_NAME] 历史子模块物化失败;构建 worktree 保留供重试"
return 1
}
fi
c_info "[$ENV_NAME] 构建(make $target:npm ci + 构建,需网络)"
make -C "$wt" "$target" >&2 || return 1
echo "$wt"
@@ -2273,9 +2339,19 @@ materialize_dev_commit() {
recycle_worktree() {
local repo="${2:-$SRC_DIR}"
local wt="$RUNTIME/.worktrees/$1"
c_info "[$ENV_NAME] 回收构建 worktree"
git -C "$repo" worktree remove --force "$wt" >/dev/null 2>&1 \
|| c_warn "[$ENV_NAME] worktree 回收失败,可手动:git -C $repo worktree remove --force $wt"
[ -d "$wt" ] || return 0
if [ -n "$(git -C "$wt" symbolic-ref --quiet --short HEAD 2>/dev/null || true)" ] \
|| ! svc_check_build_worktree "$wt" "$(git -C "$wt" rev-parse HEAD 2>/dev/null)"; then
c_warn "[$ENV_NAME] 构建 worktree 属于分支或有活动操作,保留现场:$wt"
return 0
fi
if [ -n "$(GIT_OPTIONAL_LOCKS=0 git -C "$wt" status --porcelain --untracked-files=all --ignored=matching 2>/dev/null)" ]; then
c_warn "[$ENV_NAME] 构建 worktree 含未保存或 ignored 产物,保留现场供 owner 检查:$wt"
return 0
fi
c_info "[$ENV_NAME] 回收干净构建 worktree(无 force)"
git -C "$repo" worktree remove "$wt" >/dev/null 2>&1 \
|| c_warn "[$ENV_NAME] Git 拒绝回收;保留 worktree 与元数据供 owner 检查:$wt"
}
# materialize_rdm_commit FULL SHA:RDM 源的隔离 worktree 物化+构建;stdout 只回显
@@ -2290,13 +2366,25 @@ materialize_rdm_commit() {
c_info "[$ENV_NAME] 物化 RDM 提交 $sha → 隔离 worktree(源:独立 RDM 源仓 $RDM_SRC_DIR,不触碰当前工作区)"
git -C "$RDM_SRC_DIR" worktree add --detach "$wt" "$full" >/dev/null || return 1
fi
c_info "[$ENV_NAME] 物化基座 pins(git submodule update --init --recursive;缺对象时需网络)"
git -C "$wt" submodule update --init --recursive >&2 || {
c_err "[$ENV_NAME] pins 子模块物化失败(网络或对象缺失;重试会自动 fetch RDM 源仓)"
return 1
}
c_info "[$ENV_NAME] 构建(make build:纯 Go,无需 npm)"
make -C "$wt" build >&2 || return 1
svc_check_build_worktree "$wt" "$full" || return 1
if svc_versioned_rdm "$wt"; then
local composition_root="${COMPOSITION_ROOT:-$ROOT}"
composition_root=$(realpath -e -- "$composition_root") || { c_err "COMPOSITION_ROOT is unavailable"; return 1; }
[ -f "$composition_root/components.json" ] && [ -f "$composition_root/components.lock.json" ] && [ -f "$composition_root/scripts/build-composition.py" ] || {
c_err "[$ENV_NAME] 版本化 RDM 需要 COMPOSITION_ROOT 指向固定 Dev 候选;不回落旧 pins"
return 1
}
c_info "[$ENV_NAME] 构建固定 RDM 产品(COMPOSITION_ROOT=$composition_root;不初始化旧 pins)"
make -C "$wt" COMPOSITION_ROOT="$composition_root" build >&2 || return 1
else
c_info "[$ENV_NAME] 物化历史基座 pins(git submodule update --init --recursive)"
git -C "$wt" submodule update --init --recursive >&2 || {
c_err "[$ENV_NAME] 历史 pins 物化失败;构建 worktree 保留供重试"
return 1
}
c_info "[$ENV_NAME] 构建历史 RDM 候选(make build)"
make -C "$wt" build >&2 || return 1
fi
[ -x "$wt/bin/agent-system-rdm" ] || {
c_err "[$ENV_NAME] 构建产物缺失:$wt/bin/agent-system-rdm(RDM Makefile 的 build 目标应产出该名称)"
return 1
@@ -2678,11 +2766,15 @@ agent_do_deploy() {
hist_event deploy fail "$(read_pid)" "构建失败($SRC_LABEL)"
return 1
}
srcbin="$BIN"; srcplug="$ROOT/plugins" ;;
srcbin="$BIN"
if [ -f "$ROOT/components.json" ]; then srcplug=$(svc_composition_plugins "$ROOT") || return 1
else srcplug="$ROOT/plugins"; fi ;;
dev)
wt="$(materialize_dev_commit "$SRC_FULL" "$SRC_SHA" build)" \
|| { hist_event deploy fail "$(read_pid)" "物化/构建失败($SRC_LABEL)"; return 1; }
srcbin="$wt/bin/agent-system"; srcplug="$wt/plugins" ;;
srcbin="$wt/bin/agent-system"
if [ -f "$wt/components.json" ]; then srcplug=$(svc_composition_plugins "$wt") || return 1
else srcplug="$wt/plugins"; fi ;;
release)
local relbin="$REL_ROOT/build/$REL_VERSION/agent-system"
if [ ! -f "$relbin" ]; then
@@ -2803,7 +2895,7 @@ print_agent_plan() {
printf ' 产物:%s ← %s + %s/plugins\n' "$ENV_BIN" "$BIN" "$ROOT" ;;
dev)
printf ' 来源:%s(完整提交 %s;独立部署源仓 %s)\n' "$SRC_LABEL" "$SRC_FULL" "$SRC_DIR"
printf ' 物化:worktree %s(submodule update --init --recursive + make build)\n' "$RUNTIME/.worktrees/$SRC_SHA" ;;
printf ' 物化:worktree %s(按候选格式选择历史 gitlinks 或固定源码装配 + make build)\n' "$RUNTIME/.worktrees/$SRC_SHA" ;;
release)
printf ' 来源:%s(Releases 提交 %s)\n' "$SRC_LABEL" "$(git -C "$REL_ROOT" rev-parse --short "$REL_COMMIT")"
[ -n "${SRC_DEVCOMMIT:-}" ] && printf ' 对应 Dev 提交:%s\n' "$SRC_DEVCOMMIT"
@@ -2812,7 +2904,7 @@ print_agent_plan() {
else printf ' 制品:%s(缺失!需先在 Releases 仓构建,或换版本)\n' "$art"; fi ;;
rdm)
printf ' 来源:%s(完整提交 %s;独立 RDM 源仓 %s)\n' "$SRC_LABEL" "$SRC_FULL" "$RDM_SRC_DIR"
printf ' 物化:worktree %s(pins submodule update --init --recursive + make build)\n' "$RUNTIME/.worktrees/rdm-$SRC_SHA"
printf ' 物化:worktree %s(按候选格式选择历史 pins 或 COMPOSITION_ROOT 固定产品装配)\n' "$RUNTIME/.worktrees/rdm-$SRC_SHA"
printf ' 产物:%s ← bin/agent-system-rdm + 空插件目录(组合插件编译进二进制)\n' "$ENV_BIN"
printf ' 工作台:web/ → %s/htdocs;自动伴生:scripts/svc.sh %s deploy --kind static --dir 该目录\n' \
"$ENV_DIR" "$(rdm_companion_name "$ENV_NAME")" ;;
@@ -3359,12 +3451,12 @@ static_do_deploy() {
local)
c_info "[$ENV_NAME] 构建当前工作区前端(make frontend)——写主检出构建产物(gitignore 目录)"
make -C "$ROOT" frontend || { hist_event deploy fail "$(read_pid)" "make frontend 失败"; return 1; }
src_dist="$ROOT/modules/frontend/dist"
src_dist=$(svc_frontend_dist "$ROOT") || return 1
;;
dev)
wt="$(materialize_dev_commit "$SRC_FULL" "$SRC_SHA" frontend)" \
|| { hist_event deploy fail "$(read_pid)" "物化/构建失败($SRC_LABEL)"; return 1; }
src_dist="$wt/modules/frontend/dist"
src_dist=$(svc_frontend_dist "$wt") || return 1
;;
release)
c_err "[$ENV_NAME] release 制品内嵌前端(无独立 dist);static 请用 --from dev|local 或 --dir"
+22 -9
View File
@@ -19,6 +19,13 @@ fetch_target() {
|| { echo "[verdict] REMOTE_UNVERIFIED — target commit missing" >&2; return 6; }
}
check_worktree() { python3 "${SCRIPT_DIR}/check-worktree.py" "$1"; }
check_startup_rules() {
if ! check_worktree rules; then
echo "[verdict] RULES_UNVERIFIED — fixed startup reading unavailable; preserve the workspace and retry" >&2
return 7
fi
}
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:]))'; }
work_mode() {
case "${TASK_WORK_MODE:-delivery}" in
@@ -121,6 +128,7 @@ cmd_precheck() {
fi
if [ "$mode" = local ]; then
python3 "${SCRIPT_DIR}/evidence.py" --local-context || return 2
check_startup_rules || return $?
echo "[verdict] LOCAL_READY — existing task may continue authorized local work; final delivery still requires latest-target sync and checks"
return 0
fi
@@ -132,6 +140,7 @@ cmd_precheck() {
echo "[verdict] SYNC_REQUIRED — ${branch} 缺少 origin/${TARGET_BRANCH} 的 ${behind} 个提交;主检出 pull --ff-only,任务分支 make task-sync"
exit 4
fi
check_startup_rules || return $?
echo "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch"
}
@@ -176,14 +185,17 @@ cmd_start() {
wt="$(dirname "$mainroot")/$(basename "$mainroot")-wt-${slug}"
[ -e "$wt" ] && die "worktree path exists: $wt"
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) \
|| echo "[warn] submodule update 非零退出,见下方状态核对"
local sub_drift
sub_drift=$(git -C "$wt" submodule status --recursive 2>/dev/null | grep -cE '^[-+U]' || true)
[ "${sub_drift:-0}" -gt 0 ] \
&& echo "[warn] worktree 子模块物化不完整:${sub_drift} 项漂移/未初始化——构建前先核对(git -C $wt submodule status --recursive)"
if [ -f "$wt/components.json" ]; then
(cd "$wt" && check_worktree materialize) || die "fixed sources unavailable; task worktree retained for retry"
else
# Historical commits keep the original submodule materializer.
(cd "$wt" && git submodule update --init --recursive >/dev/null 2>&1) \
|| echo "[warn] submodule update 非零退出,见下方状态核对"
local sub_drift
sub_drift=$(git -C "$wt" submodule status --recursive 2>/dev/null | grep -cE '^[-+U]' || true)
[ "${sub_drift:-0}" -gt 0 ] \
&& echo "[warn] worktree 子模块物化不完整:${sub_drift} 项漂移/未初始化——构建前先核对"
fi
local card="$wt/.task/${slug}.md"
mkdir -p "$wt/.task"
cat > "$card" <<EOF
@@ -310,6 +322,7 @@ cmd_resume() {
local receipt
receipt=$(realpath -- "$PRECHECK_RECEIPT")
VALIDATE_RECEIPT="$receipt" VALIDATE_RECEIPT_PURPOSE="resume-${mode}" bash "${SCRIPT_DIR}/evidence.sh"
check_startup_rules || return $?
IMPORT_RECEIPT="$receipt" bash "${SCRIPT_DIR}/evidence.sh"
echo "[resumed] work_mode=${mode} source_head=$(git rev-parse HEAD) observed_target=$(git rev-parse "origin/${TARGET_BRANCH}")"
}
@@ -357,7 +370,7 @@ cmd_sync() {
git add "$card"
git commit -q -m "Record the sync checkpoint" \
-m "Why: bind the task validation checkpoint to the integration baseline." \
-m "Evidence: SELF recursive component checks; sync_base=${base}; before_record=${head}." \
-m "Evidence: SELF component ownership and fixed-source checks; sync_base=${base}; before_record=${head}." \
-m "Impact: task metadata only." -m "Refs: ${card}" -- "$card"
fi
# JSON receipts carry before/after state; older table rows remain readable.
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env python3
"""Public entry point for fixed sources and local component development."""
from workspace.cli import main
if __name__ == "__main__":
raise SystemExit(main())
+1
View File
@@ -0,0 +1 @@
"""Safe, local component source workspaces; language manifests own dependencies."""
+282
View File
@@ -0,0 +1,282 @@
import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
from .common import WorkspaceError, safe_path, write_json
from .git import Git
from .go import Go
from .manifest import Manifest, validate_branch
from .npm import Npm
from .state import State
from .transition import Transition
def process_environment(root):
try:
from .transport import environment
except ModuleNotFoundError as error:
if error.name != "workspace.transport":
raise
result = os.environ.copy()
result.update({"GOWORK": "off", "GOTOOLCHAIN": "local"})
else:
result = environment(root)
# Read-only reporting does not refresh another owner's Git index.
result["GIT_OPTIONAL_LOCKS"] = "0"
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
result.pop(name, None)
return result
class Workspace:
def __init__(self, root):
self.root = root.resolve()
self.manifest = Manifest(self.root)
self.state = State(self.root)
self.env = process_environment(self.root)
self.git = Git(self.env)
self.go = Go(self)
self.npm = Npm(self)
self.transition = Transition(self)
def baseline(self, component):
name = component["id"]
if component["role"] == "go-library":
resolved = self.go.resolve(component)
if resolved:
return resolved
elif component["role"] == "npm-library":
resolved = self.npm.resolve(component)
if resolved:
return resolved
elif name in self.manifest.lock:
return self.manifest.lock[name]["commit"], {"kind": "source-lock"}
legacy = self.git.gitlink(self.root, component.get("legacy_path"))
if legacy:
return legacy, {"kind": "legacy-gitlink", "path": component.get("legacy_path")}
raise WorkspaceError(f"no fixed version source for {name}; latest main is not a fallback")
def record(self, component, commit, source):
record = {"path": component["path"], "origin": component["repo"], "baseline": commit,
"source": source, "owner_root": str(self.root), "mode": "preparing", "linked": False}
self.state.data["components"][component["id"]] = record
return record
def refresh_versioned(self, component, path, record, commit, source):
old_commit = record["baseline"]
old_source = record["source"]
reference = "refs/workspace/fixed/" + old_commit
try:
self.git.call(path, "fetch", "--no-tags", "origin", commit)
# Fetch can take time. Recheck ownership and files before changing
# this registered checkout, without forcing any owner's changes.
actual = self.git.safe(path, component["repo"])
if actual["branch"] or actual["head"] != old_commit:
raise WorkspaceError("fixed checkout changed during fetch; owner must inspect it")
saved = self.git.text(path, "rev-parse", "--verify", "--quiet", reference, check=False)
if saved and saved != old_commit:
raise WorkspaceError("preserved fixed reference has another value; owner must inspect it")
if not saved:
self.git.call(path, "update-ref", reference, old_commit, "0" * 40)
self.git.call(path, "checkout", "--no-overwrite-ignore", "--detach", commit)
actual = self.git.safe(path, component["repo"])
if actual["head"] != commit or actual["branch"]:
raise WorkspaceError("refreshed checkout differs from the declared fixed source")
history = list(record.get("previous_sources", []))
previous = {"commit": old_commit, "source": old_source, "ref": reference}
if previous not in history:
history.append(previous)
record.update({"baseline": commit, "source": source, "branch": None,
"mode": "versioned", "previous_sources": history})
record.pop("error", None)
self.state.save()
return record
except Exception as error:
# In particular, a failed fetch retains the old baseline and mode,
# so retrying the explicit refresh cannot mistake it for a partial clone.
record["error"] = str(error)
self.state.save()
raise
def prepare_one(self, component, refresh_fixed=False):
name = component["id"]
path = safe_path(self.root, component["path"])
commit, source = self.baseline(component)
record = self.state.data["components"].get(name)
if record and (record["path"] != component["path"] or record["origin"] != component["repo"]):
raise WorkspaceError(f"registered component identity changed: {name}")
if path.exists():
editable = record and record.get("mode") in ("editable", "linked")
partial = record and record.get("mode") == "preparing"
actual = self.git.safe(path, component["repo"], clean=refresh_fixed or not (editable or partial))
if refresh_fixed:
if not record or record.get("mode") != "versioned" or actual["branch"] or actual["head"] != record["baseline"]:
raise WorkspaceError(f"fixed refresh requires a registered detached versioned checkout at its baseline: {name}")
if actual["head"] != commit:
return self.refresh_versioned(component, path, record, commit, source)
if editable:
if record["baseline"] != commit:
raise WorkspaceError(f"version changed while {name} is editable; owner must synchronize explicitly")
if actual["branch"] != record.get("branch"):
raise WorkspaceError(f"registered edit branch changed: {name}")
return record
if partial:
if any(entry.name != ".git" for entry in path.iterdir()):
raise WorkspaceError(f"partial clone contains files: {name}; preserve them for its owner")
else:
if actual["branch"] or actual["head"] != commit:
raise WorkspaceError(f"existing checkout differs from fixed source: {name}; no automatic checkout")
if record is None:
record = self.record(component, commit, source)
record.update({"mode": "versioned", "baseline": commit, "source": source})
record.pop("error", None)
self.state.save()
return record
elif record and record.get("mode") != "preparing":
raise WorkspaceError(f"registered source is missing: {name}; owner must resolve it")
if record is None:
record = self.record(component, commit, source)
record.update({"baseline": commit, "source": source, "mode": "preparing"})
self.state.save()
try:
if not path.exists():
self.git.call(self.root, "clone", "--no-checkout", "--", component["repo"], str(path))
self.git.call(path, "fetch", "--no-tags", "origin", commit)
self.git.call(path, "checkout", "--detach", commit)
self.git.safe(path, component["repo"])
record.update({"mode": "versioned", "branch": None})
record.pop("error", None)
self.state.save()
return record
except Exception as error:
record["error"] = str(error)
self.state.save()
raise
def edit(self, component, branch):
self.go.check_owned()
if component["role"] in ("go-library", "npm-library"):
for consumer in self.manifest.select():
if consumer["role"] == "application" and (consumer.get("modules") or component["role"] == "npm-library"):
path = safe_path(self.root, consumer["path"])
if path.exists():
self.git.safe(path, consumer["repo"], clean=False)
else:
self.prepare_one(consumer)
name = component["id"]
record = self.prepare_one(component)
path = safe_path(self.root, component["path"])
branch = branch or "feature/workspace-" + name.lower()
validate_branch(branch)
if record.get("mode") in ("editable", "linked"):
if record.get("branch") != branch:
raise WorkspaceError(f"component already belongs to {record.get('branch')}; no branch replacement")
self.git.safe(path, component["repo"], clean=False)
else:
self.git.safe(path, component["repo"])
if self.git.call(path, "show-ref", "--verify", "--quiet", "refs/heads/" + branch, check=False).returncode == 0:
raise WorkspaceError(f"branch already exists: {branch}; owner must choose its checkout")
self.git.call(path, "checkout", "-b", branch, record["baseline"])
record.update({"mode": "editable", "branch": branch})
self.state.save()
record["linked"] = bool(self.go.modules(component))
if record["linked"]:
record["mode"] = "linked"
self.go.update()
self.state.save()
def unlink(self, component):
self.go.check_owned()
record = self.state.data["components"].get(component["id"])
if record is None:
raise WorkspaceError(f"component is not registered: {component['id']}")
self.git.safe(safe_path(self.root, component["path"]), component["repo"], clean=False)
record["linked"] = False
if record.get("mode") == "linked":
record["mode"] = "editable"
self.go.update()
self.state.save()
def status(self, components):
result = {"schema": 1, "root": str(self.root), "components": {},
"go_work": self.state.data.get("go_work")}
for component in components:
name = component["id"]
record = self.state.data["components"].get(name)
entry = {"path": component["path"], "repo": component["repo"], "role": component["role"],
"declared": self.manifest.lock.get(name), "registered": record}
path = safe_path(self.root, component["path"])
if path.exists():
try:
entry["actual"] = self.git.inspect(path)
except WorkspaceError as error:
entry["error"] = str(error)
result["components"][name] = entry
return result
def verify(self, components):
self.go.check_owned()
for component in components:
record = self.state.data["components"].get(component["id"])
if not record or record.get("linked"):
raise WorkspaceError(f"fixed source missing or development overlay active: {component['id']}")
actual = self.git.safe(safe_path(self.root, component["path"]), component["repo"])
baseline, _ = self.baseline(component)
if actual["head"] != baseline or actual["branch"]:
raise WorkspaceError(f"fixed source differs from declared version: {component['id']}")
def snapshot(self):
write_json(safe_path(self.root, ".workspace/resolved.json"), self.status(self.manifest.select()))
def main(argv=None):
parser = argparse.ArgumentParser(description="Fixed component sources and explicit local editing")
parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[2])
parser.add_argument("command", choices=("prepare", "edit", "unlink", "status", "verify", "transition"))
parser.add_argument("--name")
parser.add_argument("--branch")
parser.add_argument("--apply", action="store_true")
parser.add_argument("--refresh-fixed", action="store_true", help="explicitly refresh registered clean detached fixed sources")
args = parser.parse_args(argv)
try:
if args.branch:
validate_branch(args.branch)
if args.command in ("edit", "unlink") and not args.name:
raise WorkspaceError("--name is required for component editing/unlinking")
if args.apply and args.command != "transition":
raise WorkspaceError("--apply is only valid for transition")
if args.refresh_fixed and args.command != "prepare":
raise WorkspaceError("--refresh-fixed is only valid for prepare")
workspace = Workspace(args.root)
components = workspace.manifest.select(args.name, fixed=args.command in ("prepare", "verify"))
if args.command == "status":
result = workspace.status(components)
elif args.command == "transition" and not args.apply:
result = {"schema": 1, "components": {component["id"]: {"legacy": workspace.transition.inspect(component)} for component in components}}
elif args.command == "verify":
workspace.verify(components)
result = workspace.status(components)
else:
with workspace.state.operation():
# Read state again under the operation lock: another process may have
# finished between initial inspection and lock acquisition.
workspace.state = State(workspace.root)
if args.command == "transition":
workspace.transition.apply_all(components, args.branch)
for component in components:
if args.command == "prepare":
workspace.prepare_one(component, refresh_fixed=args.refresh_fixed)
elif args.command == "edit":
workspace.edit(component, args.branch)
elif args.command == "unlink":
workspace.unlink(component)
workspace.snapshot()
result = workspace.status(components)
print(json.dumps(result, indent=2, sort_keys=True))
return 0
except (WorkspaceError, OSError, ValueError, subprocess.TimeoutExpired) as error:
print(f"workspace: {error}", file=sys.stderr)
return 1
+75
View File
@@ -0,0 +1,75 @@
import hashlib
import json
import os
from pathlib import PurePosixPath
import subprocess
import tempfile
class WorkspaceError(Exception):
pass
def run(args, cwd, env=None, check=True):
result = subprocess.run(args, cwd=cwd, env=env, text=True, capture_output=True,
timeout=180)
if check and result.returncode:
raise WorkspaceError(f"{args[0]} failed ({result.returncode}): {result.stderr.strip() or result.stdout.strip()}")
return result
def relative_path(value, allow_dot=False):
if not isinstance(value, str) or not value or "\\" in value or any(ord(c) < 32 for c in value):
raise WorkspaceError("invalid relative path")
path = PurePosixPath(value)
if path.is_absolute() or ".." in path.parts or (value == "." and not allow_dot):
raise WorkspaceError(f"unsafe relative path: {value}")
return path
def safe_path(root, value, allow_dot=False):
relative_path(value, allow_dot)
path = root / value
# Refuse symlink path components even when a link currently points inside root.
# This avoids creating a second apparent owner and later retargeting escapes.
current = root
for part in PurePosixPath(value).parts:
current /= part
if current.is_symlink():
raise WorkspaceError(f"symlink path is not owned by this workspace: {value}")
if not path.resolve().is_relative_to(root.resolve()):
raise WorkspaceError(f"path escapes workspace: {value}")
return path
def read_json(path):
if path.is_symlink():
raise WorkspaceError(f"refusing symlink metadata: {path}")
try:
return json.loads(path.read_text())
except (OSError, ValueError) as error:
raise WorkspaceError(f"cannot read {path.name}: {error}") from error
def atomic_write(path, content):
if path.is_symlink():
raise WorkspaceError(f"refusing symlink output: {path}")
path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary = tempfile.mkstemp(prefix="." + path.name + "-", dir=path.parent)
try:
with os.fdopen(descriptor, "w") as stream:
stream.write(content)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def write_json(path, value):
atomic_write(path, json.dumps(value, indent=2, sort_keys=True) + "\n")
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
+70
View File
@@ -0,0 +1,70 @@
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit
from .common import WorkspaceError, run
def display_origin(value):
parsed = urlsplit(value)
netloc = parsed.netloc
if parsed.username and (parsed.scheme != "ssh" or parsed.username != "git" or parsed.password):
netloc = "<redacted>@" + netloc.rsplit("@", 1)[-1]
return urlunsplit((parsed.scheme, netloc, parsed.path,
"<redacted>" if parsed.query else "", "<redacted>" if parsed.fragment else ""))
class Git:
def __init__(self, env):
self.env = env
def call(self, path, *args, check=True):
return run(["git", "-c", "gc.auto=0", "-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", *args], path, self.env, check)
def text(self, path, *args, check=True):
return self.call(path, *args, check=check).stdout.strip()
def inspect(self, path):
if not (path / ".git").exists():
raise WorkspaceError(f"existing path is not an independent Git checkout: {path}")
top = Path(self.text(path, "rev-parse", "--show-toplevel"))
if top.resolve() != path.resolve():
raise WorkspaceError("component path is not a Git root")
gitdir = Path(self.text(path, "rev-parse", "--absolute-git-dir"))
common = Path(self.text(path, "rev-parse", "--path-format=absolute", "--git-common-dir"))
locks = sorted(str(file) for directory in {gitdir, common} for file in directory.rglob("*.lock"))
operations = [name for name in ("MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "BISECT_LOG")
if (gitdir / name).exists()]
changes = self.text(path, "status", "--porcelain=v1", "--untracked-files=all")
ignored = self.text(path, "ls-files", "--others", "--ignored", "--exclude-standard")
head = self.text(path, "rev-parse", "--verify", "HEAD", check=False)
branch = self.text(path, "symbolic-ref", "--quiet", "--short", "HEAD", check=False)
worktrees = self.text(path, "worktree", "list", "--porcelain")
return {"head": head or None, "branch": branch or None,
"origin": display_origin(self.text(path, "config", "--get", "remote.origin.url", check=False)),
"dirty": bool(changes or ignored), "changes": changes.splitlines(), "ignored": ignored.splitlines(),
"locks": locks, "operations": operations,
"worktrees": [line[9:] for line in worktrees.splitlines() if line.startswith("worktree ")],
"git_dir": str(gitdir), "common_dir": str(common)}
def safe(self, path, origin=None, clean=True, independent=True):
actual = self.inspect(path)
if origin is not None and actual["origin"] != origin:
raise WorkspaceError(f"origin mismatch at {path}")
if actual["locks"] or actual["operations"]:
raise WorkspaceError(f"Git operation/lock in progress at {path}")
if independent and (actual["git_dir"] != actual["common_dir"] or len(actual["worktrees"]) != 1):
raise WorkspaceError(f"shared Git worktree ownership at {path}; owner must resolve it")
if independent and ((path / ".git").is_symlink() or not (path / ".git").is_dir()
or Path(actual["git_dir"]).resolve() != (path / ".git").resolve()):
raise WorkspaceError(f"component Git metadata is owned outside its checkout: {path}")
if clean and actual["dirty"]:
raise WorkspaceError(f"dirty or ignored unique files at {path}; owner must save them")
return actual
def gitlink(self, root, legacy):
if not legacy:
return None
result = self.text(root, "ls-tree", "HEAD", "--", legacy, check=False)
if result.startswith("160000 commit "):
return result.split()[2]
return None
+158
View File
@@ -0,0 +1,158 @@
import json
from pathlib import Path
import shutil
import tempfile
from .common import WorkspaceError, atomic_write, digest, run, safe_path
from .manifest import SHA
class Go:
def __init__(self, workspace):
self.workspace = workspace
def call(self, path, *args):
result = run(["go", *args], path, self.workspace.env)
try:
return json.loads(result.stdout)
except ValueError as error:
raise WorkspaceError("Go returned invalid module metadata") from error
def modules(self, component):
result = []
base = safe_path(self.workspace.root, component["path"])
for module in component.get("modules", []):
directory = safe_path(base, module.get("directory", "."), allow_dot=True)
if (directory / "go.mod").is_file():
document = self.call(directory, "mod", "edit", "-json")
if document["Module"]["Path"] not in (module["path"], module.get("legacy")):
raise WorkspaceError(f"module identity mismatch at {directory}")
result.append((module, directory, document))
return result
def resolve(self, component):
selected = []
consumers = sorted(self.workspace.manifest.select(), key=lambda c: ("backend" not in c["id"].lower(), c["id"]))
for consumer in consumers:
if consumer["role"] != "application":
continue
for consumer_module, directory, document in self.modules(consumer):
if document["Module"]["Path"] != consumer_module["path"]:
# An old short-name graph still belongs to the old gitlinks.
continue
# Package management can update go.sum even when source merely
# needs inspection. Resolve an identical isolated manifest so
# both editable and historical consumers stay untouched.
with tempfile.TemporaryDirectory(prefix="workspace-go-resolve-") as temporary:
isolated = Path(temporary)
shutil.copy2(directory / "go.mod", isolated / "go.mod")
if (directory / "go.sum").exists():
shutil.copy2(directory / "go.sum", isolated / "go.sum")
output = run(["go", "list", "-mod=mod", "-m", "-json", "all"], isolated, self.workspace.env).stdout
graph = {}
decoder = json.JSONDecoder()
while output.strip():
metadata, end = decoder.raw_decode(output.lstrip())
output = output.lstrip()[end:]
graph[metadata["Path"]] = metadata
for module in component.get("modules", []):
metadata = graph.get(module["path"])
if not metadata:
continue
if metadata.get("Replace"):
raise WorkspaceError(f"local replacement cannot establish version baseline: {module['path']}")
version = metadata.get("Version")
if not version:
raise WorkspaceError("Go module has no fixed selected version")
download = self.call(isolated, "mod", "download", "-json", module["path"] + "@" + version)
origin = download.get("Origin") or {}
if not origin and download.get('Info'):
# Cached downloads may omit Origin in stdout; the
# Go-owned .info file retains the authenticated source.
cached = json.loads(Path(download['Info']).read_text())
origin = cached.get('Origin') or {}
commit = origin.get("Hash", "")
if origin.get("VCS") != "git" or not SHA.fullmatch(commit):
raise WorkspaceError(f"Go module has no verifiable Origin.Hash: {module['path']}")
if origin.get("URL") != component["repo"]:
# Canonical discovery uses HTTPS; transport maps that
# exact registered source to SSH, without a new identity.
canonical = "https://" + module["path"].split(".git", 1)[0]
if origin.get("URL") not in {canonical, canonical + '.git', component['repo'].removesuffix('.git')}:
raise WorkspaceError(f"Go Origin URL does not match approved repository: {module['path']}")
selected.append((commit, {"kind": "go-module", "module": module["path"],
"version": version, "consumer": consumer["id"], "origin": origin}))
if not selected:
return None
if len({entry[0] for entry in selected}) != 1:
raise WorkspaceError(f"consumer/submodule versions select different commits for {component['id']}; owner must choose compatible versions")
commit, source = selected[0]
source = dict(source, consumers=sorted({entry[1]["consumer"] for entry in selected}),
selections=[entry[1] for entry in selected])
return commit, source
def check_owned(self):
path = safe_path(self.workspace.root, "go.work")
recorded = self.workspace.state.data.get("go_work")
if path.exists() and (not recorded or digest(path) != recorded.get("sha256")):
raise WorkspaceError("go.work is not the registered generated overlay; preserve manual changes")
if recorded and not path.exists():
raise WorkspaceError("registered go.work is missing; owner must resolve overlay state")
def update(self):
self.check_owned()
paths = set()
linked_modules = set()
cached = {}
def modules(component):
if component["id"] not in cached:
cached[component["id"]] = self.modules(component)
return cached[component["id"]]
for name, entry in self.workspace.state.data["components"].items():
if entry.get("linked"):
for _, directory, document in modules(self.workspace.manifest.components[name]):
paths.add(directory)
linked_modules.add(document["Module"]["Path"])
for consumer in entry.get("source", {}).get("consumers", []):
for _, directory, document in modules(self.workspace.manifest.components[consumer]):
paths.add(directory)
linked_modules.add(document["Module"]["Path"])
if linked_modules:
# Include only consuming modules, including transitive local consumers.
changed = True
while changed:
changed = False
for component in self.workspace.manifest.select():
for _, directory, document in modules(component):
required = {item["Path"] for item in document.get("Require") or []}
if required & linked_modules and directory not in paths:
paths.add(directory)
linked_modules.add(document["Module"]["Path"])
changed = True
path = safe_path(self.workspace.root, "go.work")
if not paths:
if path.exists():
path.unlink()
self.workspace.state.data.pop("go_work", None)
return
version = run(["go", "env", "GOVERSION"], self.workspace.root, self.workspace.env).stdout.strip().removeprefix("go")
content = "go " + version + "\n\nuse (\n" + "".join("\t" + json.dumps("./" + str(item.relative_to(self.workspace.root))) + "\n" for item in sorted(paths)) + ")\n"
documents = [(directory, document) for rows in cached.values() for _, directory, document in rows if directory in paths]
directories = {document["Module"]["Path"]: directory for directory, document in documents}
aliases = {entry["Old"]["Path"] for _, document in documents for entry in document.get("Replace") or []
if not entry["New"].get("Version") and entry["Old"]["Path"] in directories}
versions = {(entry["Path"], entry["Version"]) for _, document in documents for entry in document.get("Require") or []
if entry["Path"] in aliases}
versions.update((entry["Old"]["Path"], entry["Old"]["Version"]) for _, document in documents
for entry in document.get("Replace") or []
if entry["Old"]["Path"] in aliases and entry["Old"].get("Version"))
# Historical modules can replace the same library with different nested
# paths. Exact-version local overrides reconcile those paths while Go
# forbids an all-version replacement of a workspace main module.
if versions:
content += "\nreplace (\n" + "".join("\t" + name + " " + selected + " => " + json.dumps("./" + str(directories[name].relative_to(self.workspace.root))) + "\n"
for name, selected in sorted(versions)) + ")\n"
atomic_write(path, content)
self.workspace.state.data["go_work"] = {"sha256": digest(path), "paths": [str(item.relative_to(self.workspace.root)) for item in sorted(paths)]}
+94
View File
@@ -0,0 +1,94 @@
import re
from urllib.parse import urlsplit
from .common import WorkspaceError, read_json, relative_path, safe_path
ROLES = {"application", "go-library", "npm-library", "documentation", "guidelines"}
FIXED_ROLES = {"application", "documentation", "guidelines"}
SHA = re.compile(r"[0-9a-f]{40}\Z")
IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9_-]*\Z")
def validate_repo(repo):
if not isinstance(repo, str) or not repo or repo.startswith("-") or any(c.isspace() for c in repo):
raise WorkspaceError("invalid component repository")
if "://" in repo:
parsed = urlsplit(repo)
if parsed.scheme not in {"ssh", "https", "file"} or parsed.password or parsed.query or parsed.fragment:
raise WorkspaceError("repository must be a credential-free approved Git URL")
if parsed.scheme == "https" and parsed.username:
raise WorkspaceError("repository credentials are forbidden")
if parsed.scheme == "ssh" and parsed.username not in (None, "git"):
raise WorkspaceError("unsupported SSH repository identity")
elif not repo.startswith("/"):
raise WorkspaceError("repository must be an absolute local path or approved URL")
class Manifest:
def __init__(self, root):
self.root = root
document = read_json(root / "components.json")
if not isinstance(document, dict) or document.get("schema") != 1 or not isinstance(document.get("components"), list):
raise WorkspaceError("unsupported components schema")
self.components = {}
modules = set()
for component in document["components"]:
if not isinstance(component, dict):
raise WorkspaceError("invalid component record")
name = component.get("id")
if not isinstance(name, str) or not IDENTIFIER.fullmatch(name) or name in self.components:
raise WorkspaceError("invalid or duplicate component id")
if component.get("path") != ".workspace/" + name:
raise WorkspaceError(f"component path must be .workspace/{name}")
safe_path(root, component["path"])
if component.get("legacy_path"):
safe_path(root, component["legacy_path"])
if component.get("role") not in ROLES:
raise WorkspaceError(f"invalid component role: {name}")
package = component.get("package_name")
if package is not None and (component["role"] != "npm-library" or not isinstance(package, str)
or not re.fullmatch(r"(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*", package)):
raise WorkspaceError(f"invalid npm package mapping: {name}")
validate_repo(component.get("repo"))
target = component.get("target", "main")
validate_ref(target)
entries = component.get("modules", [])
if not isinstance(entries, list):
raise WorkspaceError("modules must be a list")
for module in entries:
if not isinstance(module, dict) or not isinstance(module.get("path"), str) or not module["path"]:
raise WorkspaceError("invalid module mapping")
if module["path"] in modules or any(c.isspace() for c in module["path"]):
raise WorkspaceError("duplicate or invalid module identity")
relative_path(module.get("directory", "."), allow_dot=True)
modules.add(module["path"])
self.components[name] = component
lock = read_json(root / "components.lock.json")
if not isinstance(lock, dict) or lock.get("schema") != 1 or not isinstance(lock.get("components"), dict):
raise WorkspaceError("unsupported component lock schema")
self.lock = lock["components"]
for name, value in self.lock.items():
if name not in self.components or not isinstance(value, dict) or not SHA.fullmatch(str(value.get("commit", ""))):
raise WorkspaceError(f"invalid locked component: {name}")
if self.components[name]["role"] in ("go-library", "npm-library"):
raise WorkspaceError(f"language library {name} cannot have a competing source lock")
def select(self, name=None, fixed=False):
if name:
if name not in self.components:
raise WorkspaceError(f"unknown component: {name}")
return [self.components[name]]
return [entry for entry in self.components.values() if not fixed or entry["role"] in FIXED_ROLES]
def validate_ref(ref):
if not isinstance(ref, str) or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._/-]*", ref):
raise WorkspaceError("invalid branch/ref name")
if any(token in ref for token in ("..", "//", "/.", ".lock")) or ref.endswith(("/", ".")):
raise WorkspaceError("invalid branch/ref name")
def validate_branch(branch):
validate_ref(branch)
if not branch.startswith(("feature/", "fix/", "chore/", "codex/")):
raise WorkspaceError("editing requires a named task branch")
+62
View File
@@ -0,0 +1,62 @@
"""Resolve editable npm source from a consumer's Git package lock, without npm hooks."""
from pathlib import Path
from urllib.parse import unquote, urlsplit, urlunsplit
from .common import WorkspaceError, read_json, safe_path
from .manifest import SHA, validate_repo
def repository_identity(value):
value = value.removeprefix("git+")
parsed = urlsplit(value)
if parsed.scheme == "file":
if parsed.netloc not in ("", "localhost"):
raise WorkspaceError("npm Git origin must be local or approved")
return str(Path(unquote(parsed.path)).resolve())
validate_repo(value)
return value
class Npm:
def __init__(self, workspace):
self.workspace = workspace
def resolve(self, component):
package = component.get("package_name")
if not package:
return None
selected = []
consumers = sorted(self.workspace.manifest.select(), key=lambda entry: ("frontend" not in entry["id"].lower(), entry["id"]))
for consumer in consumers:
if consumer["role"] != "application":
continue
root = safe_path(self.workspace.root, consumer["path"])
path = safe_path(root, "package-lock.json")
if not path.exists():
continue
lock = read_json(path)
if not isinstance(lock, dict) or lock.get("lockfileVersion") not in (2, 3) or not isinstance(lock.get("packages"), dict):
raise WorkspaceError(f"unsupported npm lock for {consumer['id']}")
for location, entry in lock["packages"].items():
if location != "node_modules/" + package and not location.endswith("/node_modules/" + package):
continue
if not isinstance(entry, dict) or entry.get("link"):
raise WorkspaceError(f"local npm link cannot establish version baseline: {package}")
resolved = entry.get("resolved", "")
if not isinstance(resolved, str) or not resolved.startswith("git+"):
raise WorkspaceError(f"npm source needs a fixed approved Git origin: {package}")
parsed = urlsplit(resolved)
if not SHA.fullmatch(parsed.fragment):
raise WorkspaceError(f"npm Git source must pin a full commit: {package}")
origin = urlunsplit((parsed.scheme, parsed.netloc, parsed.path, parsed.query, ""))
if repository_identity(origin) != repository_identity(component["repo"]):
raise WorkspaceError(f"npm Git origin mismatch: {package}")
selected.append((parsed.fragment, {"kind": "npm-lock", "package": package,
"version": entry.get("version"), "resolved": resolved,
"consumer": consumer["id"], "lock_path": str(path.relative_to(self.workspace.root))}))
if not selected:
return None
if len({entry[0] for entry in selected}) != 1:
raise WorkspaceError(f"consumers select conflicting npm Git commits for {package}; owner must choose compatible versions")
commit, source = selected[0]
return commit, dict(source, selections=[entry[1] for entry in selected])
+36
View File
@@ -0,0 +1,36 @@
from contextlib import contextmanager
import os
from .common import WorkspaceError, read_json, safe_path, write_json
class State:
def __init__(self, root):
self.root = root
self.area = safe_path(root, ".workspace")
self.path = safe_path(root, ".workspace/state.json")
self.data = read_json(self.path) if self.path.exists() else {"schema": 1, "components": {}}
if not isinstance(self.data, dict) or self.data.get("schema") != 1 or not isinstance(self.data.get("components"), dict):
raise WorkspaceError("unsupported workspace state")
for name, entry in self.data["components"].items():
if not isinstance(entry, dict) or entry.get("owner_root") != str(root):
raise WorkspaceError(f"workspace owner mismatch: {name}")
safe_path(root, entry.get("path", ""))
def save(self):
write_json(self.path, self.data)
@contextmanager
def operation(self):
self.area.mkdir(exist_ok=True)
path = safe_path(self.root, ".workspace/.operation.lock")
try:
descriptor = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
except FileExistsError as error:
raise WorkspaceError("workspace has another writer (.operation.lock); preserve the owner lock") from error
try:
with os.fdopen(descriptor, "w") as stream:
stream.write(str(os.getpid()) + "\n")
yield
finally:
path.unlink()
+284
View File
@@ -0,0 +1,284 @@
import hashlib
import json
import os
import shutil
from .common import WorkspaceError, safe_path
from .manifest import validate_branch
def files_snapshot(path, excluded=()):
"""Hash files without following links or opening another repository's metadata."""
files = {}
for current, directories, names in os.walk(path, followlinks=False):
directories[:] = [name for name in directories if name != ".git"
and str((type(path)(current) / name).relative_to(path)) not in excluded]
for name in directories + names:
file = type(path)(current) / name
if name == ".git":
continue
relative = str(file.relative_to(path))
if file.is_symlink():
files[relative] = {"link": os.readlink(file)}
elif file.is_file():
files[relative] = {"sha256": hashlib.sha256(file.read_bytes()).hexdigest(),
"mode": file.stat().st_mode & 0o777}
return files
class Transition:
def __init__(self, workspace):
self.workspace = workspace
def inspect(self, component):
root = self.workspace.root
legacy_name = component.get("legacy_path")
legacy = safe_path(root, legacy_name) if legacy_name else None
if not legacy or not (legacy / ".git").exists():
return {"path": legacy_name, "gitlink": self.workspace.git.gitlink(root, legacy_name)}
return self.inspect_at(legacy, self.workspace.git.gitlink(root, legacy_name))
def inspect_at(self, legacy, pin=None):
root = self.workspace.root
result = {"path": str(legacy.relative_to(root)), "gitlink": pin}
if (legacy / ".git").exists():
result.update(self.workspace.git.inspect(legacy))
result["refs"] = self.workspace.git.text(legacy, "show-ref", check=False).splitlines()
result["reflog_commits"] = sorted(set(self.workspace.git.text(legacy, "reflog", "--all", "--format=%H", check=False).splitlines()))
index = type(root)(result["git_dir"]) / "index"
result["index_sha256"] = hashlib.sha256(index.read_bytes()).hexdigest() if index.exists() else None
result["index_entries"] = self.workspace.git.text(legacy, "ls-files", "--stage", "-z")
gitlinks = {}
for row in result["index_entries"].split("\0"):
if not row:
continue
metadata, relative = row.split("\t", 1)
mode, commit, stage = metadata.split()
if mode == "160000" and stage == "0":
gitlinks[relative] = commit
nested = []
for current, directories, names in os.walk(legacy, followlinks=False):
if current == str(legacy):
directories[:] = [name for name in directories if name != ".git"]
continue
if ".git" in directories or ".git" in names:
path = type(legacy)(current)
relative = str(path.relative_to(legacy))
child = self.inspect_at(path, gitlinks.get(relative))
matches = [entry["id"] for entry in self.workspace.manifest.select()
if entry["repo"] == child.get("origin")]
nested.append({"path": relative, "id": matches[0] if len(matches) == 1 else None,
"tracked": relative in gitlinks, "snapshot": child})
directories[:] = []
result["nested"] = nested
result["files"] = files_snapshot(legacy, {entry["path"] for entry in nested})
return result
def validate_files(self, legacy, snapshot):
for relative, entry in snapshot.items():
file = legacy / relative
if "link" in entry and (os.path.isabs(entry["link"]) or not file.resolve().is_relative_to(legacy.resolve())):
raise WorkspaceError(f"legacy symlink escapes component: {relative}; owner must resolve it")
def content_identity(self, snapshot):
return snapshot["head"], snapshot["files"], snapshot["index_entries"]
def ref_namespace(self, snapshot):
captured = {key: value for key, value in snapshot.items() if key != "ref_namespace"}
encoded = json.dumps(captured, sort_keys=True, separators=(",", ":")).encode()
source = hashlib.sha256(snapshot["path"].encode()).hexdigest()[:24]
version = hashlib.sha256(encoded).hexdigest()
return "refs/migration/sources/" + source + "/" + version
def remember_snapshots(self, record, snapshots):
history = list(record.get("legacy_snapshots", []))
seen = {entry.get("ref_namespace") for entry in history}
for snapshot in snapshots:
namespace = self.ref_namespace(snapshot)
if namespace not in seen:
history.append(dict(snapshot, ref_namespace=namespace))
seen.add(namespace)
record["legacy_snapshots"] = history
def pin_ref(self, path, reference, object_id):
current = self.workspace.git.text(path, "rev-parse", "--verify", "--quiet", reference, check=False)
if current:
if current != object_id:
raise WorkspaceError(f"preserved migration reference has another value: {reference}; owner must inspect it")
return
# Create only if absent: never overwrite a saved snapshot or a concurrent
# owner's reference, even when the expected namespace already exists.
self.workspace.git.call(path, "update-ref", reference, object_id, "0" * 40)
def preserve_refs(self, path, snapshots):
workspace = self.workspace
for snapshot in snapshots:
legacy = safe_path(workspace.root, snapshot["path"])
namespace = self.ref_namespace(snapshot)
for row in snapshot["refs"]:
object_id, reference = row.split(" ", 1)
if workspace.git.call(path, "cat-file", "-e", object_id, check=False).returncode:
# Fetch the captured object, not a mutable ref's current value.
# With no destination refspec this only updates FETCH_HEAD.
workspace.git.call(path, "fetch", "--no-tags", "--", str(legacy), object_id)
self.pin_ref(path, namespace + "/" + reference.removeprefix("refs/"), object_id)
for commit in snapshot["reflog_commits"]:
if workspace.git.call(path, "cat-file", "-e", commit, check=False).returncode:
workspace.git.call(path, "fetch", "--no-tags", "--", str(legacy), commit)
self.pin_ref(path, "refs/migration/reflog/" + commit, commit)
def apply_all(self, components, branch):
"""Resolve the whole nested ownership plan before creating any clone."""
workspace = self.workspace
workspace.go.check_owned()
groups = {}
def add(component, snapshot):
name = component["id"]
legacy = safe_path(workspace.root, snapshot["path"])
if not snapshot.get("head"):
raise WorkspaceError(f"legacy checkout is unavailable: {name}")
workspace.git.safe(legacy, component["repo"], clean=False, independent=False)
self.validate_files(legacy, snapshot["files"])
gitdir = type(legacy)(snapshot["git_dir"])
if (gitdir / "objects/info/alternates").exists():
raise WorkspaceError(f"legacy object store borrows another repository: {name}; owner must make it independent")
previous = groups.setdefault(name, [])
if previous:
if self.content_identity(previous[0]) != self.content_identity(snapshot):
raise WorkspaceError(f"conflicting duplicate logical component {name}: {previous[0]['path']} and {snapshot['path']}; owner must choose or reconcile them")
old_refs = dict(row.split(" ", 1)[::-1] for old in previous for row in old["refs"])
new_refs = dict(row.split(" ", 1)[::-1] for row in snapshot["refs"])
if any(old_refs[ref] != new_refs[ref] for ref in old_refs.keys() & new_refs.keys()):
raise WorkspaceError(f"conflicting duplicate refs for {name}; owner must reconcile them")
if any(old["path"] == snapshot["path"] for old in previous):
return
previous.append(snapshot)
for nested in snapshot.get("nested", []):
if not nested["tracked"] or nested["id"] is None:
raise WorkspaceError(f"unknown nested repository ownership at {snapshot['path']}/{nested['path']}; register its origin and Git ownership first")
add(workspace.manifest.components[nested["id"]], nested["snapshot"])
for component in components:
snapshot = self.inspect(component)
# Default transition also lists uninitialized components; a named
# unavailable checkout still reports a precise failure to its owner.
add(component, snapshot)
for name, snapshots in groups.items():
component = workspace.manifest.components[name]
path = safe_path(workspace.root, component["path"])
if path.exists():
record = workspace.state.data["components"].get(name)
actual = workspace.git.safe(path, component["repo"], clean=False)
if not record or record.get("source", {}).get("kind") != "legacy-copy" or actual["branch"] != record.get("branch"):
raise WorkspaceError(f"migration destination has another owner: {name}; inspect its registered branch")
if actual["head"] != snapshots[0]["head"] or files_snapshot(path) != snapshots[0]["files"] or workspace.git.text(path, "ls-files", "--stage", "-z") != snapshots[0]["index_entries"]:
raise WorkspaceError(f"conflicting duplicate destination for {name}; preserve both scenes for the owner")
for name, snapshots in groups.items():
component = workspace.manifest.components[name]
path = safe_path(workspace.root, component["path"])
if path.exists():
self.preserve_refs(path, snapshots)
record = workspace.state.data["components"][name]
self.remember_snapshots(record, snapshots)
workspace.state.save()
else:
self.copy_one(component, branch, snapshots)
for snapshots in groups.values():
for snapshot in snapshots:
legacy = safe_path(workspace.root, snapshot["path"])
if self.inspect_at(legacy, snapshot["gitlink"]) != snapshot:
raise WorkspaceError("legacy checkout changed during migration; independent copies retained for owner review")
for name in groups:
record = workspace.state.data["components"][name]
record["linked"] = bool(workspace.go.modules(workspace.manifest.components[name]))
if record["linked"]:
record["mode"] = "linked"
workspace.go.update()
workspace.state.save()
def copy_one(self, component, branch, snapshots):
workspace = self.workspace
name = component["id"]
snapshot = snapshots[0]
legacy = safe_path(workspace.root, snapshot["path"])
excluded = {entry["path"] for entry in snapshot.get("nested", [])}
gitdir = type(legacy)(snapshot["git_dir"])
path = safe_path(workspace.root, component["path"])
if path.exists():
raise WorkspaceError(f"migration destination already exists: {name}; inspect its owner before retrying")
branch = branch or snapshot.get("branch")
if not branch or not branch.startswith(("feature/", "fix/", "chore/", "codex/")):
branch = "feature/migration-" + name.lower()
validate_branch(branch)
mappings = [{"id": nested["id"], "legacy_path": nested["snapshot"]["path"],
"workspace_path": workspace.manifest.components[nested["id"]]["path"]}
for nested in snapshot.get("nested", [])]
record = workspace.record(component, snapshot["head"], {"kind": "legacy-copy", "gitlink": snapshot["gitlink"], "nested_components": mappings})
record.update({"mode": "migrating", "legacy_snapshot": snapshot})
self.remember_snapshots(record, snapshots)
workspace.state.save()
try:
# Local clone copies the complete object store without hardlinks/alternates.
# It never shares the submodule's Git common directory with the new root.
workspace.git.call(workspace.root, "clone", "--no-hardlinks", "--no-checkout", "--", str(legacy), str(path))
workspace.git.call(path, "remote", "set-url", "origin", component["repo"])
workspace.git.call(path, "fetch", "--no-tags", "--", str(legacy), "+refs/*:refs/migration/*")
self.preserve_refs(path, snapshots)
workspace.git.call(path, "checkout", "--detach", snapshot["head"])
if workspace.git.call(path, "show-ref", "--verify", "--quiet", "refs/heads/" + branch, check=False).returncode == 0:
if workspace.git.text(path, "rev-parse", "refs/heads/" + branch) != snapshot["head"]:
raise WorkspaceError("migration branch conflict; original and partial clone retained")
workspace.git.call(path, "checkout", branch)
else:
workspace.git.call(path, "checkout", "-b", branch)
# Keep deletions as well as tracked, untracked, and ignored file content.
for filename in workspace.git.text(legacy, "diff", "--name-only", "-z", "--diff-filter=D", "HEAD").split("\0"):
if not filename:
continue
relative = type(path)(filename)
parent = safe_path(path, str(relative.parent), allow_dot=True)
file = parent / relative.name
if file.is_file() or file.is_symlink():
file.unlink()
for current, directories, names in os.walk(legacy, followlinks=False):
directories[:] = [entry for entry in directories if entry != ".git"
and str((type(legacy)(current) / entry).relative_to(legacy)) not in excluded]
relative = type(legacy)(current).relative_to(legacy)
destination = path / relative
destination.mkdir(parents=True, exist_ok=True)
for filename in directories + names:
if filename == ".git":
continue
source = type(legacy)(current) / filename
target = destination / filename
if source.is_symlink():
if target.is_symlink() and os.readlink(target) == os.readlink(source):
continue
if target.exists() or target.is_symlink():
if target.is_dir() and not target.is_symlink():
raise WorkspaceError(f"migration symlink conflicts with directory: {filename}")
target.unlink()
target.symlink_to(os.readlink(source), target_is_directory=source.is_dir())
elif source.is_file():
if target.is_symlink():
target.unlink()
shutil.copy2(source, target)
# The clone copied all local objects, including staged blobs which
# are absent from HEAD. Preserve the index and split-index companions
# independently so staging is not silently flattened into file copies.
for index in list(gitdir.glob("sharedindex.*")) + [gitdir / "index"]:
if index.is_file():
shutil.copy2(index, path / ".git" / index.name)
if self.inspect_at(legacy, snapshot["gitlink"]) != snapshot:
raise WorkspaceError("legacy checkout changed during migration; partial clone preserved for owner")
if files_snapshot(path) != snapshot["files"]:
raise WorkspaceError("migration file snapshot differs; original and partial clone preserved for owner")
workspace.git.safe(path, component["repo"], clean=False)
record.update({"mode": "editable", "branch": branch, "linked": False})
record.pop("error", None)
workspace.state.save()
except Exception as error:
record["error"] = str(error)
workspace.state.save()
raise
+88
View File
@@ -0,0 +1,88 @@
"""Process-local module transport; configuration contains no credentials."""
import json
import os
import re
from pathlib import Path
from urllib.parse import urlsplit, unquote
def _url(value):
if not isinstance(value, str) or any(c.isspace() for c in value):
raise ValueError('invalid module transport URL')
parsed = urlsplit(value)
if parsed.scheme not in {'https', 'ssh', 'git+ssh', 'file'} or parsed.password:
raise ValueError('unsupported transport or credentials in URL')
if parsed.scheme == 'https' and parsed.username:
raise ValueError('transport URL must not contain credentials')
if parsed.scheme == 'file' and parsed.netloc not in {'', 'localhost'}:
raise ValueError('file transport must be local')
if parsed.query or parsed.fragment or not parsed.path:
raise ValueError('transport URL must identify a repository path')
return value
def _mirror_source(value, target, env):
"""Allow injected HTTP only as a verified, credential-free mirror alias."""
if isinstance(value, str) and value.startswith('http://'):
server = env.get('GITHUB_SERVER_URL', '').rstrip('/')
parsed = urlsplit(server)
owner = env.get('GITHUB_REPOSITORY', '').partition('/')[0]
if (parsed.scheme not in {'http', 'https'} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment or
not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*', owner) or
not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*\.git', target.name) or
value != server + '/' + owner + '/' + target.name):
raise ValueError('HTTP mirror source must match the injected server and repository')
return value
return _url(value)
def environment(root=None):
root = Path(root) if root is not None else Path(__file__).resolve().parents[2]
path = root / 'components.json'
config = json.loads(path.read_text()).get('transport', {}) if path.exists() else {}
env = os.environ.copy()
env.update(GOWORK='off', GOTOOLCHAIN='local')
private = config.get('go_private', [])
if not isinstance(private, list) or any(not isinstance(p, str) or not p or any(c.isspace() for c in p) for p in private):
raise ValueError('invalid private module prefixes')
if private:
env.update(GOPRIVATE=','.join(private), GONOPROXY=','.join(private), GONOSUMDB=','.join(private))
rewrites = config.get('git_rewrites', [])
if not isinstance(rewrites, list):
raise ValueError('invalid Git transport mappings')
if rewrites:
inherited = []
marker = env.get('CI_DEPENDENCY_MIRRORS')
if marker:
mirrors = Path(marker)
if not mirrors.is_absolute() or not mirrors.is_dir() or mirrors.is_symlink():
raise ValueError('invalid CI dependency mirror identity')
mirrors = mirrors.resolve()
try:
count = int(env.get('GIT_CONFIG_COUNT', '0'))
except ValueError as error:
raise ValueError('invalid process Git configuration') from error
for i in range(count):
key, value = env.get(f'GIT_CONFIG_KEY_{i}', ''), env.get(f'GIT_CONFIG_VALUE_{i}', '')
if (key, value) in {('protocol.file.allow', 'always'), ('core.hooksPath', '/dev/null'), ('credential.helper', '')}:
inherited.append((key, value))
elif key.startswith('url.file://') and key.endswith('.insteadOf'):
target = Path(unquote(urlsplit(key[4:-10]).path)).resolve()
if mirrors not in target.parents or not target.is_dir():
raise ValueError('CI mirror source escapes its registered directory')
inherited.append((key, _mirror_source(value, target, env)))
# Do not inherit temporary authentication configuration into candidate code.
for key in list(env):
if key == 'GIT_CONFIG_COUNT' or key.startswith(('GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_')):
del env[key]
entries = list(inherited)
for rewrite in rewrites:
if not isinstance(rewrite, dict) or set(rewrite) != {'from', 'to'}:
raise ValueError('invalid Git transport mapping')
source, target = _url(rewrite['from']), _url(rewrite['to'])
entries.append((f'url.{target}.insteadOf', source))
for i, (key, value) in enumerate(entries):
env[f'GIT_CONFIG_KEY_{i}'], env[f'GIT_CONFIG_VALUE_{i}'] = key, value
env['GIT_CONFIG_COUNT'] = str(len(entries))
return env
+498
View File
@@ -0,0 +1,498 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
vet-and-test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-Command":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-Command.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-Command")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Vet, test, and race
run: |
set -euo pipefail
source .ci-mirrors.env
GOWORK=off go vet -mod=readonly ./... && GOWORK=off go test -mod=readonly -count=1 ./... && GOWORK=off go test -mod=readonly -count=1 -race ./...
+510
View File
@@ -0,0 +1,510 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
vet-and-test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-Intent":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-Intent.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-Intent")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Vet, test, and race
run: |
set -euo pipefail
source .ci-mirrors.env
GOWORK=off go vet -mod=readonly ./... && GOWORK=off go test -mod=readonly -count=1 ./... && GOWORK=off go test -mod=readonly -count=1 -race ./...
docker-checkout-hostgateway:
runs-on: ubuntu-latest
options: --add-host gitea:host-gateway
env:
GITHUB_SERVER_URL: http://gitea:3000
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: *trusted_acquisition_1
- run: test -f go.mod && echo "docker-mode checkout via host-gateway OK"
+510
View File
@@ -0,0 +1,510 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
vet-and-test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-Native":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-Native.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-Native")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Vet, test, and race
run: |
set -euo pipefail
source .ci-mirrors.env
go vet -mod=readonly ./... && go test -mod=readonly -count=1 ./... && go test -mod=readonly -count=1 -race ./...
docker-checkout-hostgateway:
runs-on: ubuntu-latest
options: --add-host gitea:host-gateway
env:
GITHUB_SERVER_URL: http://gitea:3000
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: *trusted_acquisition_1
- run: test -f go.mod && echo "docker-mode checkout via host-gateway OK"
+513
View File
@@ -0,0 +1,513 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
vet-and-test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-Squad":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-Squad.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-Squad")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Vet, test, and race
run: |
set -euo pipefail
source .ci-mirrors.env
for module in . adapters/native adapters/intent adapters/sqlite adapters/a2a cmd/agent-squad; do
test -f "$module/go.mod"
(cd "$module" && go vet -mod=readonly ./... && go test -mod=readonly -count=1 ./... && go test -mod=readonly -count=1 -race ./...)
done
docker-checkout-hostgateway:
runs-on: ubuntu-latest
options: --add-host gitea:host-gateway
env:
GITHUB_SERVER_URL: http://gitea:3000
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: *trusted_acquisition_1
- run: test -f go.mod && echo "docker-mode checkout via host-gateway OK"
+500
View File
@@ -0,0 +1,500 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
vet-and-test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-System-Backend":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-System-Backend.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-System-Backend")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install house-style Python packages
run: python3 -m pip install --break-system-packages -i https://pypi.tuna.tsinghua.edu.cn/simple python-docx python-pptx openpyxl
- name: Vet and test
run: |
set -euo pipefail
source .ci-mirrors.env
GOWORK=off go vet -mod=readonly ./... && GOWORK=off go test -mod=readonly -count=1 ./...
+707
View File
@@ -0,0 +1,707 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-System-Core":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-System-Core.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-System-Core")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Vet, test, and race
run: |
set -euo pipefail
source .ci-mirrors.env
GOWORK=off go vet -mod=readonly ./... && GOWORK=off go test -mod=readonly -count=1 ./... && GOWORK=off go test -mod=readonly -count=1 -race ./...
- name: Install Node 22
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/node.tar.xz" https://npmmirror.com/mirrors/node/v22.23.3/node-v22.23.3-linux-x64.tar.xz
tar -C /usr/local --strip-components=1 -xJf "$RUNNER_TEMP/node.tar.xz"
- name: Prefetch npm packages without lifecycle scripts
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_3 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
def git_package(value):
if not isinstance(value, str):
return None
if value.startswith(("file:", "link:", "workspace:")):
raise ValueError("Formal npm acquisition cannot consume a local package override")
if value.startswith(("http://", "https://")):
registry = urlsplit(value)
if registry.scheme != "https" or registry.hostname != "registry.npmjs.org" or registry.username or registry.password:
raise ValueError("npm tarball source is outside the reviewed public registry")
elif "/" in value and not value.startswith(("git+", "npm:")):
raise ValueError("npm repository shorthand is outside the fixed-SHA package mapping")
if value.startswith("git+") or value.startswith(("git://", "ssh://", "github:", "gitlab:", "bitbucket:")):
for repo in ("Agent-System-Core", "Agent-System-UI"):
prefix = "git+" + SSH + repo + ".git#"
if value.startswith(prefix):
return repo, fixed_sha(value[len(prefix):])
raise ValueError("npm Git source is outside the reviewed fixed-SHA package mapping")
return None
try:
targets = []
selected = set()
for directory in source_roots():
if not (directory / "package.json").is_file():
continue
if (directory / ".npmrc").exists():
raise ValueError("Candidate .npmrc is not allowed during private dependency acquisition")
package = json.loads((directory / "package.json").read_text())
lock = json.loads((directory / "package-lock.json").read_text())
if lock.get("lockfileVersion") not in {2, 3}:
raise ValueError("npm acquisition requires a package lock")
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in package.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
for entry in lock.get("packages", {}).values():
found = git_package(entry.get("resolved"))
if found:
selected.add(found)
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in entry.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
targets.append(directory)
# Fetch only registered fixed Git package commits. npm never receives token config.
for repo, sha in sorted(selected):
repository = mirror(repo, sha, component_token)
package = json.loads(git(repository, "show", sha + ":package.json"))
forbidden = {"prepare", "prepack", "prepublish", "prepublishOnly", "preinstall", "install", "postinstall"}
if forbidden & set(package.get("scripts", {})):
raise ValueError("Git package provider must commit outputs without acquisition lifecycle scripts")
if not git(repository, "ls-tree", sha, "package/").strip():
raise ValueError("Git package provider is missing committed package outputs")
no_auth = mirror_env()
empty_config = tempfile.NamedTemporaryFile(prefix="ci-npm-globalrc-")
for directory in targets:
result = subprocess.run(["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund",
"--userconfig=/dev/null", "--globalconfig=" + empty_config.name],
cwd=directory, env=no_auth, capture_output=True, text=True)
if result.returncode:
raise ValueError("npm dependency acquisition failed: " + result.stderr.strip())
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("npm prefetch refused: " + str(error))
print("npm packages cached without credentials or lifecycle execution")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Validate package and contracts without credentials
run: |
set -euo pipefail
source .ci-mirrors.env
npm test
npm run typecheck
+501
View File
@@ -0,0 +1,501 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-System-Frontend":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-System-Frontend.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-System-Frontend")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Node 22
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/node.tar.xz" https://npmmirror.com/mirrors/node/v22.23.3/node-v22.23.3-linux-x64.tar.xz
tar -C /usr/local --strip-components=1 -xJf "$RUNNER_TEMP/node.tar.xz"
- name: Prefetch npm packages without lifecycle scripts
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
def git_package(value):
if not isinstance(value, str):
return None
if value.startswith(("file:", "link:", "workspace:")):
raise ValueError("Formal npm acquisition cannot consume a local package override")
if value.startswith(("http://", "https://")):
registry = urlsplit(value)
if registry.scheme != "https" or registry.hostname != "registry.npmjs.org" or registry.username or registry.password:
raise ValueError("npm tarball source is outside the reviewed public registry")
elif "/" in value and not value.startswith(("git+", "npm:")):
raise ValueError("npm repository shorthand is outside the fixed-SHA package mapping")
if value.startswith("git+") or value.startswith(("git://", "ssh://", "github:", "gitlab:", "bitbucket:")):
for repo in ("Agent-System-Core", "Agent-System-UI"):
prefix = "git+" + SSH + repo + ".git#"
if value.startswith(prefix):
return repo, fixed_sha(value[len(prefix):])
raise ValueError("npm Git source is outside the reviewed fixed-SHA package mapping")
return None
def registry_package(location, entry):
resolved = entry.get("resolved", "")
if not isinstance(resolved, str) or not resolved.startswith(("https://", "http://")):
return None
# A metadata spec is name@exact-version, never a candidate URL.
source = urlsplit(resolved)
if (source.scheme != "https" or source.netloc != "registry.npmjs.org" or
source.username or source.password or source.query or source.fragment):
raise ValueError("npm tarball source is outside the reviewed public registry")
relative = Path(location)
name = entry.get("name") or location.rsplit("node_modules/", 1)[-1]
version = entry.get("version", "")
if (not location.startswith("node_modules/") or relative.is_absolute() or ".." in relative.parts or
not isinstance(name, str) or not re.fullmatch(r"(?:@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*", name) or
not isinstance(version, str) or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?", version)):
raise ValueError("Invalid locked registry package name or version")
return name + "@" + version
try:
targets = []
selected = set()
for directory in source_roots():
if not (directory / "package.json").is_file():
continue
if (directory / ".npmrc").exists():
raise ValueError("Candidate .npmrc is not allowed during private dependency acquisition")
package = json.loads((directory / "package.json").read_text())
lock_bytes = (directory / "package-lock.json").read_bytes()
lock = json.loads(lock_bytes)
if lock.get("lockfileVersion") not in {2, 3}:
raise ValueError("npm acquisition requires a package lock")
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in package.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
registry_packages = []
for location, entry in lock.get("packages", {}).items():
spec = registry_package(location, entry)
if spec:
registry_packages.append((location, spec))
found = git_package(entry.get("resolved"))
if found:
selected.add(found)
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in entry.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
targets.append((directory, registry_packages, lock_bytes))
# Fetch only registered fixed Git package commits. npm never receives token config.
for repo, sha in sorted(selected):
repository = mirror(repo, sha, component_token)
package = json.loads(git(repository, "show", sha + ":package.json"))
forbidden = {"prepare", "prepack", "prepublish", "prepublishOnly", "preinstall", "install", "postinstall"}
if forbidden & set(package.get("scripts", {})):
raise ValueError("Git package provider must commit outputs without acquisition lifecycle scripts")
if not git(repository, "ls-tree", sha, "package/").strip():
raise ValueError("Git package provider is missing committed package outputs")
no_auth = mirror_env()
empty_config = tempfile.NamedTemporaryFile(prefix="ci-npm-globalrc-")
for directory, registry_packages, lock_bytes in targets:
result = subprocess.run(["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund",
"--userconfig=/dev/null", "--globalconfig=" + empty_config.name],
cwd=directory, env=no_auth, capture_output=True, text=True)
if result.returncode:
raise ValueError("npm dependency acquisition failed: " + result.stderr.strip())
# npm ci caches tarballs; packed consumers also need resolver
# metadata. Only installed locked registry entries are fetched.
specs = sorted({spec for location, spec in registry_packages if (directory / location).is_dir()})
if specs:
with tempfile.TemporaryDirectory(prefix="ci-npm-metadata-") as metadata_root:
result = subprocess.run(["npm", "cache", "add", *specs, "--ignore-scripts",
"--registry=https://registry.npmjs.org", "--userconfig=/dev/null",
"--globalconfig=" + empty_config.name], cwd=metadata_root,
env=no_auth, capture_output=True, text=True)
if result.returncode:
raise ValueError("npm metadata acquisition failed: " + result.stderr.strip())
if (directory / "package-lock.json").read_bytes() != lock_bytes:
raise ValueError("npm acquisition changed the committed package lock")
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("npm prefetch refused: " + str(error))
print("npm packages cached without credentials or lifecycle execution")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Validate package and contracts without credentials
run: |
set -euo pipefail
source .ci-mirrors.env
npm test
npm run build
+949
View File
@@ -0,0 +1,949 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
GOTOOLCHAIN: local
GOWORK: "off"
GOPRIVATE: git-ssh.u1680832.nyat.app/weichen
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-System-RDM":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-System-RDM.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-System-RDM")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Prefetch fixed Dev product composition
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
locked = json.loads((root / "composition.dev.json").read_text())
if set(locked) != {"schema", "repo", "commit"} or locked["schema"] != 1:
raise ValueError("Unsupported Dev product composition lock")
if locked["repo"] != SSH + "Agent-System-Dev.git":
raise ValueError("Dev product source is outside the reviewed allowlist")
sha = fixed_sha(locked["commit"])
directory = root / ".ci-composition-dev"
if directory.exists() or directory.is_symlink():
raise ValueError("Dev product composition requires a fresh destination")
directory.mkdir()
git(directory, "init", "-q")
url = SERVER + "/weichen/Agent-System-Dev.git"
git(directory, "remote", "add", "origin", url)
git(directory, "fetch", "--depth=1", "origin", sha, token=component_token, repo="Agent-System-Dev")
git(directory, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(directory, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Dev composition differs from the fixed lock")
if (directory / ".ci-mirrors").exists() or (directory / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
git(directory, "remote", "set-url", "origin", locked["repo"])
materialize_manifest(directory)
mirror("Agent-System-Dev", sha, component_token)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Dev product composition prefetch refused: " + str(error))
print("Fixed Dev product source cached; candidate scripts run only without credentials")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Node 22
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/node.tar.xz" https://mirrors.aliyun.com/nodejs-release/v22.14.0/node-v22.14.0-linux-x64.tar.xz
tar -C /usr/local -xJf "$RUNNER_TEMP/node.tar.xz"
ln -sfn /usr/local/node-v22.14.0-linux-x64/bin/node /usr/local/bin/node
- name: Prefetch npm packages without lifecycle scripts
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_3 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
def git_package(value):
if not isinstance(value, str):
return None
if value.startswith(("file:", "link:", "workspace:")):
raise ValueError("Formal npm acquisition cannot consume a local package override")
if value.startswith(("http://", "https://")):
registry = urlsplit(value)
if registry.scheme != "https" or registry.hostname != "registry.npmjs.org" or registry.username or registry.password:
raise ValueError("npm tarball source is outside the reviewed public registry")
elif "/" in value and not value.startswith(("git+", "npm:")):
raise ValueError("npm repository shorthand is outside the fixed-SHA package mapping")
if value.startswith("git+") or value.startswith(("git://", "ssh://", "github:", "gitlab:", "bitbucket:")):
for repo in ("Agent-System-Core", "Agent-System-UI"):
prefix = "git+" + SSH + repo + ".git#"
if value.startswith(prefix):
return repo, fixed_sha(value[len(prefix):])
raise ValueError("npm Git source is outside the reviewed fixed-SHA package mapping")
return None
try:
targets = []
selected = set()
for directory in source_roots():
if not (directory / "package.json").is_file():
continue
if (directory / ".npmrc").exists():
raise ValueError("Candidate .npmrc is not allowed during private dependency acquisition")
package = json.loads((directory / "package.json").read_text())
lock = json.loads((directory / "package-lock.json").read_text())
if lock.get("lockfileVersion") not in {2, 3}:
raise ValueError("npm acquisition requires a package lock")
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in package.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
for entry in lock.get("packages", {}).values():
found = git_package(entry.get("resolved"))
if found:
selected.add(found)
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in entry.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
targets.append(directory)
# Fetch only registered fixed Git package commits. npm never receives token config.
for repo, sha in sorted(selected):
repository = mirror(repo, sha, component_token)
package = json.loads(git(repository, "show", sha + ":package.json"))
forbidden = {"prepare", "prepack", "prepublish", "prepublishOnly", "preinstall", "install", "postinstall"}
if forbidden & set(package.get("scripts", {})):
raise ValueError("Git package provider must commit outputs without acquisition lifecycle scripts")
if not git(repository, "ls-tree", sha, "package/").strip():
raise ValueError("Git package provider is missing committed package outputs")
no_auth = mirror_env()
empty_config = tempfile.NamedTemporaryFile(prefix="ci-npm-globalrc-")
for directory in targets:
result = subprocess.run(["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund",
"--userconfig=/dev/null", "--globalconfig=" + empty_config.name],
cwd=directory, env=no_auth, capture_output=True, text=True)
if result.returncode:
raise ValueError("npm dependency acquisition failed: " + result.stderr.strip())
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("npm prefetch refused: " + str(error))
print("npm packages cached without credentials or lifecycle execution")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
# Preserve the Go build, workbench guards and real RDM Go tests.
- name: Make test
run: |
set -euo pipefail
source .ci-mirrors.env
export COMPOSITION_ROOT="$PWD/.ci-composition-dev"
GOWORK=off make test
+501
View File
@@ -0,0 +1,501 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
env:
GOTOOLCHAIN: local
GOWORK: "off"
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Agent-System-UI":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Agent-System-UI.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Agent-System-UI")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Node 22
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/node.tar.xz" https://npmmirror.com/mirrors/node/v22.23.3/node-v22.23.3-linux-x64.tar.xz
tar -C /usr/local --strip-components=1 -xJf "$RUNNER_TEMP/node.tar.xz"
- name: Prefetch npm packages without lifecycle scripts
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
def git_package(value):
if not isinstance(value, str):
return None
if value.startswith(("file:", "link:", "workspace:")):
raise ValueError("Formal npm acquisition cannot consume a local package override")
if value.startswith(("http://", "https://")):
registry = urlsplit(value)
if registry.scheme != "https" or registry.hostname != "registry.npmjs.org" or registry.username or registry.password:
raise ValueError("npm tarball source is outside the reviewed public registry")
elif "/" in value and not value.startswith(("git+", "npm:")):
raise ValueError("npm repository shorthand is outside the fixed-SHA package mapping")
if value.startswith("git+") or value.startswith(("git://", "ssh://", "github:", "gitlab:", "bitbucket:")):
for repo in ("Agent-System-Core", "Agent-System-UI"):
prefix = "git+" + SSH + repo + ".git#"
if value.startswith(prefix):
return repo, fixed_sha(value[len(prefix):])
raise ValueError("npm Git source is outside the reviewed fixed-SHA package mapping")
return None
def registry_package(location, entry):
resolved = entry.get("resolved", "")
if not isinstance(resolved, str) or not resolved.startswith(("https://", "http://")):
return None
# A metadata spec is name@exact-version, never a candidate URL.
source = urlsplit(resolved)
if (source.scheme != "https" or source.netloc != "registry.npmjs.org" or
source.username or source.password or source.query or source.fragment):
raise ValueError("npm tarball source is outside the reviewed public registry")
relative = Path(location)
name = entry.get("name") or location.rsplit("node_modules/", 1)[-1]
version = entry.get("version", "")
if (not location.startswith("node_modules/") or relative.is_absolute() or ".." in relative.parts or
not isinstance(name, str) or not re.fullmatch(r"(?:@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*", name) or
not isinstance(version, str) or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?", version)):
raise ValueError("Invalid locked registry package name or version")
return name + "@" + version
try:
targets = []
selected = set()
for directory in source_roots():
if not (directory / "package.json").is_file():
continue
if (directory / ".npmrc").exists():
raise ValueError("Candidate .npmrc is not allowed during private dependency acquisition")
package = json.loads((directory / "package.json").read_text())
lock_bytes = (directory / "package-lock.json").read_bytes()
lock = json.loads(lock_bytes)
if lock.get("lockfileVersion") not in {2, 3}:
raise ValueError("npm acquisition requires a package lock")
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in package.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
registry_packages = []
for location, entry in lock.get("packages", {}).items():
spec = registry_package(location, entry)
if spec:
registry_packages.append((location, spec))
found = git_package(entry.get("resolved"))
if found:
selected.add(found)
for group in ("dependencies", "devDependencies", "optionalDependencies"):
for value in entry.get(group, {}).values():
found = git_package(value)
if found:
selected.add(found)
targets.append((directory, registry_packages, lock_bytes))
# Fetch only registered fixed Git package commits. npm never receives token config.
for repo, sha in sorted(selected):
repository = mirror(repo, sha, component_token)
package = json.loads(git(repository, "show", sha + ":package.json"))
forbidden = {"prepare", "prepack", "prepublish", "prepublishOnly", "preinstall", "install", "postinstall"}
if forbidden & set(package.get("scripts", {})):
raise ValueError("Git package provider must commit outputs without acquisition lifecycle scripts")
if not git(repository, "ls-tree", sha, "package/").strip():
raise ValueError("Git package provider is missing committed package outputs")
no_auth = mirror_env()
empty_config = tempfile.NamedTemporaryFile(prefix="ci-npm-globalrc-")
for directory, registry_packages, lock_bytes in targets:
result = subprocess.run(["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund",
"--userconfig=/dev/null", "--globalconfig=" + empty_config.name],
cwd=directory, env=no_auth, capture_output=True, text=True)
if result.returncode:
raise ValueError("npm dependency acquisition failed: " + result.stderr.strip())
# npm ci caches tarballs; packed consumers also need resolver
# metadata. Only installed locked registry entries are fetched.
specs = sorted({spec for location, spec in registry_packages if (directory / location).is_dir()})
if specs:
with tempfile.TemporaryDirectory(prefix="ci-npm-metadata-") as metadata_root:
result = subprocess.run(["npm", "cache", "add", *specs, "--ignore-scripts",
"--registry=https://registry.npmjs.org", "--userconfig=/dev/null",
"--globalconfig=" + empty_config.name], cwd=metadata_root,
env=no_auth, capture_output=True, text=True)
if result.returncode:
raise ValueError("npm metadata acquisition failed: " + result.stderr.strip())
if (directory / "package-lock.json").read_bytes() != lock_bytes:
raise ValueError("npm acquisition changed the committed package lock")
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("npm prefetch refused: " + str(error))
print("npm packages cached without credentials or lifecycle execution")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- run: |
set -euo pipefail
source .ci-mirrors.env
npm run typecheck
- run: |
set -euo pipefail
source .ci-mirrors.env
npm test
+508
View File
@@ -0,0 +1,508 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
vet-and-test:
runs-on: nightly
env:
GOTOOLCHAIN: local
GOWORK: "off"
GOPROXY: https://goproxy.cn,direct
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Error-Code-System":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Error-Code-System.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Error-Code-System")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- run: |
set -euo pipefail
source .ci-mirrors.env
GOWORK=off go vet -mod=readonly ./... && go test -mod=readonly -count=1 ./...
docker-checkout-hostgateway:
runs-on: ubuntu-latest
options: --add-host gitea:host-gateway
env:
GITHUB_SERVER_URL: http://gitea:3000
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: *trusted_acquisition_1
- run: test -f go.mod && echo "docker-mode checkout via host-gateway OK"
+511
View File
@@ -0,0 +1,511 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
vet-and-test:
runs-on: nightly
env:
GOTOOLCHAIN: local
GOWORK: "off"
GOPROXY: https://goproxy.cn,direct
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_1 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def materialize_legacy(directory, depth=0):
if depth > 16:
raise ValueError("Historical submodule depth exceeds the checkout limit")
links = {}
for entry in git(directory, "ls-tree", "-rz", "HEAD").split(b"\0"):
if entry:
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(directory, "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 fixed gitlink paths")
for name, fields in entries.items():
repo = LEGACY.get(name)
if not repo or fields.get("path") != name or fields.get("url") not in {
SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"}:
raise ValueError("Historical submodule source is outside the reviewed allowlist")
for name in entries:
repo = LEGACY[name]
sha = links[name]
url = f"{SERVER}/weichen/{repo}.git"
git(directory, "config", f"submodule.{name}.url", url)
# Trusted explicit checkout prevents candidate update commands from running.
git(directory, "submodule", "update", "--init", "--checkout", "--", name, token=component_token, repo=repo)
child = directory / name
if git(child, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Historical submodule does not match its fixed SHA")
mirror(repo, sha, component_token)
materialize_legacy(child, depth + 1)
def materialize_manifest(directory):
manifest = directory / "components.json"
lock = directory / "components.lock.json"
if not manifest.exists() and not lock.exists():
materialize_legacy(directory)
return
data = json.loads(manifest.read_text())
locked = json.loads(lock.read_text())
if data.get("schema") != 1 or locked.get("schema") != 1:
raise ValueError("Unsupported source manifest schema")
entries = {}
for item in data["components"]:
repo = item["id"]
path = ".workspace/" + repo
if (repo not in REPOS or repo in entries or item["path"] != path or
item["repo"] not in {SSH + repo + ".git", f"{SERVER}/weichen/{repo}.git"} or
item.get("role") not in {"go-library", "npm-library", "application", "guidelines", "documentation"}):
raise ValueError("Candidate source manifest is outside the reviewed allowlist")
for module in item.get("modules", []):
if module.get("path") not in GO_MODULES or not module["path"].startswith(GO_PREFIX + repo + ".git"):
raise ValueError("Candidate module mapping is outside the reviewed allowlist")
entries[repo] = item
selected = locked["components"]
if set(selected) - set(entries):
raise ValueError("Source lock refers to an unregistered repository")
for repo, item in entries.items():
if item["role"] in {"application", "guidelines", "documentation"} and repo not in selected:
raise ValueError("Application and documentation source needs a fixed lock")
for repo, pin in selected.items():
fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
if destination.exists() or destination.is_symlink() or destination.parent.is_symlink():
raise ValueError("Source materialization requires a fresh destination")
# Validate the complete lock before any authenticated component fetch.
for repo, pin in selected.items():
sha = fixed_sha(pin["commit"])
destination = directory / entries[repo]["path"]
destination.mkdir(parents=True)
url = f"{SERVER}/weichen/{repo}.git"
git(destination, "init", "-q")
git(destination, "remote", "add", "origin", url)
git(destination, "fetch", "--depth=1", "origin", sha, token=component_token, repo=repo)
git(destination, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(destination, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Materialized source differs from the fixed lock")
# Persist the registered identity; process config chooses the transport.
git(destination, "remote", "set-url", "origin", entries[repo]["repo"])
mirror(repo, sha, component_token)
materialize_legacy(destination)
try:
if os.environ.get("GITHUB_REPOSITORY") != "weichen/Logging-System":
raise ValueError("Checkout repository is outside the reviewed allowlist")
sha = fixed_sha(os.environ.get("GITHUB_SHA", ""))
if not root_token:
raise ValueError("Checkout requires TOKEN")
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
git(root, "remote", "add", "origin", SERVER + "/weichen/Logging-System.git")
git(root, "fetch", "--depth=1", "origin", sha, token=root_token, repo="Logging-System")
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
if git(root, "rev-parse", "HEAD").decode().strip() != sha:
raise ValueError("Checked-out commit differs from the platform commit")
if (root / ".ci-mirrors").exists() or (root / ".ci-mirrors.env").exists():
raise ValueError("Candidate cannot provide trusted prefetch state")
materialize_manifest(root)
mirror_env()
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
# Do not print child stderr or environment: a failed credential fetch stays redacted.
sys.exit("Checkout refused: " + str(error))
print("Fixed source checkout complete; credentials were not persisted")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Prefetch Go modules without executing candidate code
env:
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: &trusted_acquisition_2 |
set -euo pipefail
python3 - <<'PY_TRUSTED'
# Trusted workflow code only: never import or execute checkout scripts here.
# Token scopes, workflow provenance and runner isolation are platform NOT_RUN.
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import tempfile
from urllib.parse import urlsplit
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed = urlsplit(SERVER)
if (parsed.scheme not in {"http", "https"} or not parsed.netloc or
parsed.username or parsed.password or parsed.query or parsed.fragment):
sys.exit("Acquisition requires the platform-injected http(s) server")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
GO_PREFIX = "git-ssh.u1680832.nyat.app/weichen/"
REPOS = {"Agent-System-Dev", "Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native",
"Agent-Intent", "Agent-Command", "Agent-Squad", "Agent-System-Backend",
"Agent-System-Frontend", "Agent-System-UI", "Agent-System-RDM",
"Development-Guidelines", "Agent-System-Documents"}
LEGACY = {
"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", "pins/agent-system-backend": "Agent-System-Backend",
"pins/agent-system-core": "Agent-System-Core", "pins/agent-system-frontend": "Agent-System-Frontend",
"pins/agent-system-ui": "Agent-System-UI",
}
GO_REPOS = REPOS - {"Agent-System-Dev", "Agent-System-UI", "Agent-System-Frontend", "Development-Guidelines", "Agent-System-Documents"}
GO_MODULES = {GO_PREFIX + repo + ".git" for repo in GO_REPOS}
GO_MODULES |= {GO_PREFIX + "Agent-Squad.git/" + suffix for suffix in
("adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad")}
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
# Go environment files and inherited Git tracing/config must not change acquisition.
clean = {k: v for k, v in os.environ.items()
if not k.startswith(("GIT_", "NPM_CONFIG_", "npm_config_")) and
not any(word in k.upper() for word in ("TOKEN", "PASSWORD", "SECRET"))}
clean.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null", GIT_CONFIG_GLOBAL="/dev/null",
GIT_TERMINAL_PROMPT="0", GOENV="off", GOTOOLCHAIN="local", GOWORK="off", GOFLAGS="-mod=readonly",
GOPRIVATE=GO_PREFIX.rstrip("/"), GONOPROXY=GO_PREFIX.rstrip("/"), GONOSUMDB=GO_PREFIX.rstrip("/"))
if os.environ.get("npm_config_cache"):
clean["npm_config_cache"] = os.environ["npm_config_cache"]
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." + parsed.scheme + ".allow": "always"}
root = Path.cwd()
mirrors = root / ".ci-mirrors"
def process_env(values):
env = clean | {"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 env
def git(directory, *args, token="", repo=""):
values = config.copy()
if token:
if repo not in REPOS:
raise ValueError("Credential destination is outside the reviewed repository allowlist")
values[f"http.{SERVER}/weichen/{repo}.git.extraheader"] = "AUTHORIZATION: token " + token
result = subprocess.run(["git", "-C", str(directory), *args], env=process_env(values),
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout
def fixed_sha(value):
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", value):
raise ValueError("Source acquisition requires a fixed commit SHA")
return value.lower()
def mirror(repo, sha, token):
sha = fixed_sha(sha)
destination = mirrors / (repo + ".git")
if destination.is_symlink() or mirrors.is_symlink():
raise ValueError("Mirror directory cannot be a symlink")
mirrors.mkdir(exist_ok=True)
if not destination.exists():
destination.mkdir()
git(destination, "init", "--bare", "-q")
url = f"{SERVER}/weichen/{repo}.git"
try:
git(destination, "cat-file", "-e", sha + "^{commit}")
except subprocess.CalledProcessError:
git(destination, "fetch", "--depth=200", url, sha, token=token, repo=repo)
git(destination, "update-ref", "refs/heads/main", sha)
git(destination, "symbolic-ref", "HEAD", "refs/heads/main")
git(destination, "update-ref", "refs/ci/" + sha, sha)
return destination
def mirror_env():
values = {"protocol.file.allow": "always", "core.hooksPath": "/dev/null", "credential.helper": ""}
# Distinct keys use repeated .insteadOf values, represented as a sequence below.
pairs = list(values.items())
for repo in sorted(REPOS):
destination = mirrors / (repo + ".git")
if not destination.exists():
continue
key = "url." + destination.as_uri() + ".insteadOf"
pairs.extend((key, url) for url in (SSH + repo + ".git", "https://" + GO_PREFIX + repo + ".git",
SERVER + "/weichen/" + repo + ".git", "https://" + GO_PREFIX + repo))
env = clean | {"CI_DEPENDENCY_MIRRORS": str(mirrors), "GIT_CONFIG_COUNT": str(len(pairs))}
for index, (key, value) in enumerate(pairs):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
exports = {k: v for k, v in env.items() if k.startswith("GIT_CONFIG_") or k == "CI_DEPENDENCY_MIRRORS"}
(root / ".ci-mirrors.env").write_text("".join("export " + k + "=" + shlex.quote(v) + "\n" for k, v in exports.items()))
return env
def source_roots():
bases = [root]
composition = root / ".ci-composition-dev"
if composition.is_dir() and not composition.is_symlink():
bases.append(composition)
paths = []
for base in bases:
paths += [base, base / "rdm"] + [base / path for path in LEGACY] + [base / ".workspace" / repo for repo in sorted(REPOS)]
return [path for path in paths if path.is_dir() and not path.is_symlink()]
try:
targets = []
for source in source_roots():
for suffix in (".", "examples/trace", "adapters/native", "adapters/intent", "adapters/sqlite", "adapters/a2a", "cmd/agent-squad"):
directory = source / suffix
if (directory / "go.mod").is_file() and directory.resolve() not in targets:
targets.append(directory.resolve())
# Parse using the installed local Go tool without credentials or candidate execution.
original_files = {}
for directory in targets:
parsed_mod = json.loads(subprocess.check_output(["go", "mod", "edit", "-json"], cwd=directory, env=clean))
canonical = parsed_mod["Module"]["Path"].startswith(GO_PREFIX)
for name in ("go.mod", "go.sum"):
dependency_file = directory / name
if dependency_file.is_file():
original_files[(directory, name)] = dependency_file.read_bytes()
elif canonical and name == "go.sum":
original_files[(directory, name)] = None
for dep in (parsed_mod.get("Require") or []):
path = dep["Path"]
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go dependency is outside the reviewed module mapping")
for replacement in (parsed_mod.get("Replace") or []):
path = replacement["New"]["Path"]
if canonical and not replacement["New"].get("Version"):
raise ValueError("Canonical module cannot acquire a local path replacement")
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Go replacement is outside the reviewed module mapping")
auth = config.copy()
for repo in sorted(GO_REPOS):
url = f"{SERVER}/weichen/{repo}.git"
auth[f"url.{url}.insteadOf"] = "https://" + GO_PREFIX + repo
auth[f"http.{url}.extraheader"] = "AUTHORIZATION: token " + component_token
def objects(output):
decoder = json.JSONDecoder()
remaining = output.strip()
while remaining:
item, end = decoder.raw_decode(remaining)
remaining = remaining[end:].lstrip()
yield item
def unchanged():
for (owner, name), contents in original_files.items():
dependency_file = owner / name
current = dependency_file.read_bytes() if dependency_file.exists() else None
if current != contents:
raise ValueError("Go acquisition modified a committed Go dependency file: " + str(dependency_file))
for directory in targets:
# Resolve private go.mod metadata only through exact repository headers;
# validate the complete selected graph before acquiring module zip files.
graph = subprocess.run(["go", "list", "-mod=readonly", "-m", "-json", "all"], cwd=directory,
env=process_env(auth), capture_output=True, text=True)
if graph.returncode:
raise ValueError("Go selected graph resolution failed: " + graph.stderr.strip())
for item in objects(graph.stdout):
if item.get("Main"):
continue
selected = item.get("Replace") or item
for path in (item.get("Path", ""), selected.get("Path", "")):
if path.startswith(GO_PREFIX) and path not in GO_MODULES:
raise ValueError("Selected Go module is outside the reviewed mapping")
unchanged()
# Go 1.27 can prune go.sum even under -mod=readonly for this
# metadata-only command. Use a disposable copy, then validate actual
# package loading against the unchanged committed source locks.
local_replacements = json.loads(subprocess.check_output(
["go", "mod", "edit", "-json"], cwd=directory, env=clean)).get("Replace") or []
downloaded = ""
if not any(not item["New"].get("Version") for item in local_replacements):
with tempfile.TemporaryDirectory(prefix="ci-go-prefetch-") as temporary:
download_root = Path(temporary)
for name in ("go.mod", "go.sum"):
if (directory / name).is_file():
(download_root / name).write_bytes((directory / name).read_bytes())
result = subprocess.run(["go", "mod", "download", "-json"], cwd=download_root,
env=process_env(auth), capture_output=True, text=True)
if result.returncode:
raise ValueError("Go module acquisition failed: " + result.stderr.strip())
downloaded = result.stdout
unchanged()
for item in objects(downloaded):
path = item.get("Path", "")
if path.startswith(GO_PREFIX):
if path not in GO_MODULES:
raise ValueError("Resolved Go module is outside the reviewed mapping")
repo = path[len(GO_PREFIX):].split(".git", 1)[0]
origin = item.get("Origin") or json.loads(Path(item["Info"]).read_text()).get("Origin") or {}
mirror(repo, fixed_sha(origin.get("Hash")), component_token)
# Package metadata loads actual test/build dependencies without running
# vet, compilers, package init functions or candidate tests. No CGO tools.
packages = subprocess.run(["go", "list", "-mod=readonly", "-deps", "-test", "./..."], cwd=directory,
env=process_env(auth) | {"CGO_ENABLED": "0"}, capture_output=True, text=True)
if packages.returncode:
raise ValueError("Go test/build dependency acquisition failed: " + packages.stderr.strip())
unchanged()
mirror_env()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit("Go prefetch refused: " + str(error))
print("Go modules cached with GOTOOLCHAIN=local; no candidate code executed")
PY_TRUSTED
unset TOKEN SUBMODULE_TOKEN
- run: |
set -euo pipefail
source .ci-mirrors.env
for module in . examples/trace; do
test -f "$module/go.mod"
(cd "$module" && go vet -mod=readonly ./... && go test -mod=readonly -count=1 ./...)
done
docker-checkout-hostgateway:
runs-on: ubuntu-latest
options: --add-host gitea:host-gateway
env:
GITHUB_SERVER_URL: http://gitea:3000
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: *trusted_acquisition_1
- run: test -f go.mod && echo "docker-mode checkout via host-gateway OK"
+73
View File
@@ -0,0 +1,73 @@
{
"schema": 1,
"usage": "test-input-only",
"capture": "git-show",
"components": {
"Logging-System": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Logging-System.git",
"source_sha": "2e2620009fcb21c302369c1125bea33e2a383c71",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "163d62d87654c98506a97bff6188c6d4c2338c3abfce1bec110b1de6c872cb24"
},
"Error-Code-System": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Error-Code-System.git",
"source_sha": "f55c6b11b6eeda36b9d30fb9e92dcecc44a9fcae",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "3fbbc1a91412bcac32a9eec8deaded8ce1b5151e512987a4669030a2cf5b776e"
},
"Agent-System-Core": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Core.git",
"source_sha": "dc4440d5d81a7ff9a79349c9c6a390cc2f9cb60a",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "6b0b5568bffa26709469fc655dee61210300156dba79bb619a7017162b1846a6"
},
"Agent-Native": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Native.git",
"source_sha": "3b550d783dd7d4dd37d30bb312f0c4369a3102a5",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "8d0cdab26b2758318b3bffeeb90b9c1ae947bc3f232e89e56ebf2f8edf45420d"
},
"Agent-Intent": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Intent.git",
"source_sha": "c29cf6a283d0a2d93099e703d76b08c8a7801c90",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "c15dde13c41be0437db8d5b7e7e99c4ff37cab1af66515971400bfd7e5c94c55"
},
"Agent-Command": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Command.git",
"source_sha": "63dc7e06412af632c6b9c32ca3d71fb7cd8235fc",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "b12f615219a32dc4c477658e9abedbbe250aee260baf9c8cbffdb9a9d645f24e"
},
"Agent-Squad": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Squad.git",
"source_sha": "42e1c4cd3296c7c68eef9473f34e50d59ba1412b",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "641f7e84fae05b99397ffc3b311031099526a29e8c6e39ae6b43617e7e4b9fd4"
},
"Agent-System-Backend": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Backend.git",
"source_sha": "fffd5790cfab317fadafb40fa55a11bab6e9a847",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "c14a60739881d243cf459e05e8859976e6e86e6df3a272410cd2df3e0d43d063"
},
"Agent-System-UI": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-UI.git",
"source_sha": "63d289e6bc15b701356cfe2d5b8740210fac0dcc",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "03fb580f523649db843a21ff70e3d21c5165198c1a8cf0a0bea882c3ec04f8f1"
},
"Agent-System-Frontend": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Frontend.git",
"source_sha": "5467517e1831e7ca8b58bb5df52494a3a2c9d468",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "802e5c199afd8805d87f0fd1958cd001960d8d820a4e6acb14a401007d278012"
},
"Agent-System-RDM": {
"repository": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-RDM.git",
"source_sha": "8a18dfa86e9afb1be231661e82cde8c0db7ee31b",
"workflow_path": ".gitea/workflows/ci.yml",
"workflow_sha256": "47693ea41584cae52a32e291fae5fa995b5ca746b41cf150d6dac709e6a83b55"
}
}
}
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Historical fixture: Agent-System-RDM c058bfcf651c42b2dfc9c93ec7cb1f1036d3fd95; original production script follows.
# U1/KTD1 组合产物构建:frontend dist 构建 → 装入 backend web embed → RDM 组合二进制。
# 物化布局(pins 检出保持只读,AGENTS 红线):
# .build/product/frontend/ ← pins/agent-system-frontend(源码,无 node_modules/dist)
# .build/product/ui/ ← pins/agent-system-ui(frontend 的 @ui 邻接依赖)
# .build/product/backend/ ← pins/agent-system-backend(web/dist 注入点)
# .build/product/rdm/ ← 本仓模块(go.mod 的 backend replace 重写为 ../backend;
# 其余 8 条 replace 均在 backend/deps/ 之下,随前缀一并重写)
# 产物:bin/agent-system-rdm-product + bin/PRODUCT_MANIFEST.json(组件 SHA/dist 摘要/二进制摘要)。
# 完整性守卫(KTD1):dist 缺 index.html / assets 骨架 → 拒绝产出(不装半成品)。
set -euo pipefail
cd "$(dirname "$0")/.."
ROOT="$PWD"
BUILD="$ROOT/.build/product"
OUT_BIN="$ROOT/bin/agent-system-rdm-product"
OUT_MAN="$ROOT/bin/PRODUCT_MANIFEST.json"
need() { command -v "$1" >/dev/null 2>&1 || { echo "product-build: 缺少 $1(完整 Web 产物不可构建)" >&2; exit 2; }; }
need node; need npm; need go
# copy_tree SRC DST EXCLUDE...:tar 管道拷贝 + 拷贝期排除(CI runner 无 rsync;
# cp -a 无法在拷贝中排除——源在目标祖先目录内时会「拷入自身」)。
copy_tree() {
local src="$1" dst="$2"; shift 2
mkdir -p "$dst"
local excl=() rel
for rel in "$@"; do excl+=(--exclude "./$rel"); done
( cd "$src" && tar cf - "${excl[@]}" . ) | ( cd "$dst" && tar xf - )
}
echo "[product] 物化组件(pins 只读;.build/product 全新重建)"
rm -rf "$BUILD"
mkdir -p "$BUILD"
copy_tree pins/agent-system-frontend "$BUILD/frontend" .git node_modules dist tsconfig.tsbuildinfo
copy_tree pins/agent-system-ui "$BUILD/ui" .git node_modules dist
copy_tree pins/agent-system-backend "$BUILD/backend" .git internal/runtime/data internal/server/data
copy_tree "$ROOT" "$BUILD/rdm" .git .build bin node_modules data .task .zcode dist
echo "[product] 构建前端(frontend+ui 邻接布局;npm ci + vite build)"
# ui 仓有自己的 node_modules(marked/dompurify 等,见 frontend vite.config dedupe 注释)。
( cd "$BUILD/ui" && npm ci --no-audit --no-fund >/dev/null 2>&1 )
( cd "$BUILD/frontend" && npm ci --no-audit --no-fund >/dev/null 2>&1 && npm run build 2>&1 | tail -4 )
INDEX="$BUILD/frontend/dist/index.html"
[ -f "$INDEX" ] || { echo "product-build: frontend/dist 缺 index.html——拒绝作为完整 Web 产物" >&2; exit 3; }
ls "$BUILD/frontend/dist/assets" >/dev/null 2>&1 || { echo "product-build: dist 缺 assets/ 骨架" >&2; exit 3; }
echo "[product] 注入 backend web embed(dist → backend/web/dist)"
rm -rf "$BUILD/backend/web/dist"
mkdir -p "$BUILD/backend/web/dist"
cp -r "$BUILD/frontend/dist/." "$BUILD/backend/web/dist/"
touch "$BUILD/backend/web/dist/.gitkeep"
echo "[product] 重写 rdm/go.mod 的 backend replace(→ ../backend;其余随前缀)"
sed -i 's#=> \./pins/agent-system-backend#=> ../backend#g' "$BUILD/rdm/go.mod"
echo "[product] 编译组合二进制"
( cd "$BUILD/rdm" && go build -trimpath -buildvcs=false -o "$OUT_BIN" . )
# 同源 admin 工具(backend cmd/agent-system:apikey 等 CLI;烟测/运维用,同 embed 源)。
( cd "$BUILD/backend" && go build -trimpath -buildvcs=false -o "$ROOT/bin/agent-system-admin" ./cmd/agent-system )
DIST_SHA=$(find "$BUILD/backend/web/dist" -type f ! -name .gitkeep -print0 | sort -z | xargs -0 sha256sum | sha256sum | cut -c1-16)
BIN_SHA=$(sha256sum "$OUT_BIN" | cut -c1-16)
BE_SHA=$(git -C pins/agent-system-backend rev-parse HEAD)
FE_SHA=$(git -C pins/agent-system-frontend rev-parse HEAD)
UI_SHA=$(git -C pins/agent-system-ui rev-parse HEAD)
RDM_SHA=$(git rev-parse HEAD)
cat > "$OUT_MAN" <<EOF
{
"kind": "rdm-product/1",
"rdm": "$RDM_SHA",
"backend": "$BE_SHA",
"frontend": "$FE_SHA",
"ui": "$UI_SHA",
"dist_sha256_16": "$DIST_SHA",
"binary_sha256_16": "$BIN_SHA",
"built_at": "$(date -u +%FT%TZ)"
}
EOF
echo "[product] OK binary=$OUT_BIN ($BIN_SHA) dist=$DIST_SHA"
echo "[product] manifest=$OUT_MAN"
+86 -15
View File
@@ -1,7 +1,10 @@
"""Execute the workflow checkout against real disposable HTTP Git repositories."""
import http.server
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import tempfile
import textwrap
@@ -15,22 +18,73 @@ 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"
COMPONENT_REPOSITORIES = (
"Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native", "Agent-Intent",
"Agent-Command", "Agent-Squad", "Agent-System-Backend", "Agent-System-UI",
"Agent-System-Frontend", "Agent-System-RDM",
)
COMPONENT_FIXTURES = Path(__file__).resolve().parent / "fixtures/component-ci"
def component_workflow(repository, *, snapshots=COMPONENT_FIXTURES, source_root=None):
"""Read committed test input; never use these snapshots for real acquisition.
Optional COMPONENT_CI_SOURCE_ROOT verifies the recorded immutable Git object
and current source HEAD. An archive requires only its committed fixture set.
"""
if repository not in COMPONENT_REPOSITORIES:
raise ValueError("Unknown component workflow fixture")
provenance = json.loads((snapshots / "provenance.json").read_text())
if provenance.get("schema") != 1 or set(provenance.get("components", {})) != set(COMPONENT_REPOSITORIES):
raise ValueError("Component workflow provenance must cover every reviewed repository")
record = provenance["components"][repository]
if (set(record) != {"repository", "source_sha", "workflow_path", "workflow_sha256"} or
record["repository"] != SSH + repository + ".git" or
record["workflow_path"] != ".gitea/workflows/ci.yml" or
not re.fullmatch(r"[0-9a-f]{40}", record["source_sha"]) or
not re.fullmatch(r"[0-9a-f]{64}", record["workflow_sha256"])):
raise ValueError("Invalid fixed component workflow provenance")
path = snapshots / repository / "ci.yml"
if path.is_symlink() or hashlib.sha256(path.read_bytes()).hexdigest() != record["workflow_sha256"]:
raise ValueError("Component workflow snapshot differs from its recorded source digest")
source_root = source_root or os.environ.get("COMPONENT_CI_SOURCE_ROOT")
if source_root:
source = Path(source_root) / repository
head = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
if head != record["source_sha"]:
raise ValueError("Component source HEAD differs from fixture provenance")
captured = subprocess.check_output(["git", "-C", str(source), "show",
record["source_sha"] + ":" + record["workflow_path"]])
if captured != path.read_bytes():
raise ValueError("Component workflow snapshot differs from its immutable Git source")
return path
def workflow_script(workflow, name):
"""Extract an inline workflow run block, resolving workflow-local YAML aliases."""
lines = workflow.read_text().splitlines()
start = next((i for i, line in enumerate(lines) if line.strip() == "- name: " + name), None)
if start is None:
raise AssertionError(f"Workflow has no executable phase: {name}")
run = next(i for i in range(start, len(lines)) if lines[i].strip().startswith("run: "))
value = lines[run].strip().removeprefix("run: ")
if value.startswith("*"):
run = next(i for i, line in enumerate(lines) if line.strip() == "run: &" + value[1:] + " |")
value = "|"
if value != "|" and not (value.startswith("&") and value.endswith(" |")):
return value
start = run + 1
end = next((i for i in range(start, len(lines))
if lines[i].strip() and not lines[i].startswith(" ")), len(lines))
return textwrap.dedent("\n".join(lines[start:end]))
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]))
return workflow_script(WORKFLOW, "Checkout")
def rdm_clone_script():
lines = WORKFLOW.read_text().splitlines()
start = next(i for i, line in enumerate(lines) if "Clone Agent-System-RDM" in line)
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]))
return workflow_script(WORKFLOW, "Clone Agent-System-RDM with nested pins (reviewed allowlist addition)")
class CheckoutTests(unittest.TestCase):
@@ -283,14 +337,31 @@ class CheckoutTests(unittest.TestCase):
for sha in shas:
self.assertEqual(self.git(work / "rdm", "cat-file", "-t", sha), "commit")
self.assertEqual(self.git(work / "rdm", "rev-parse", "HEAD"), shas[2])
# A lifecycle cache clones this isolated checkout. Local clones copy
# heads, so the fixed source must expose a deterministic main head.
lifecycle = self.base / "lifecycle-source"
self.git(self.base, "clone", "--no-hardlinks", str(work / "rdm"), str(lifecycle))
self.assertEqual(self.git(lifecycle, "rev-parse", "origin/main"), shas[2])
# Candidate builds are a separate phase: the shell has no checkout token,
# and lifecycle source fetches resolve through public local mirror paths.
from tests.test_dependency_ci import phase
built = subprocess.run(
["bash", "-c", phase(WORKFLOW, "Build fixed RDM pins without credentials")],
cwd=work, env=self.env, capture_output=True, text=True, timeout=60)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
self.assertNotIn(SUB_TOKEN, built.stdout + built.stderr)
digests = (work / "rdm" / "pin-build-digests.txt").read_text()
for sha in shas:
self.assertIn(sha, digests)
gate = WORKFLOW.read_text().split(
"Real-combination lifecycle gate", 1)[1].split("\n svc-realcombo:", 1)[0]
self.assertIn('export SVC_CI_GIT_TOKEN="$SUBMODULE_TOKEN"', gate)
self.assertNotRegex(gate, r"(?m)^\s*source\s")
self.assertNotIn(">>", gate)
clone = work / "lifecycle-source"
replay = subprocess.run(
["bash", "-c", 'source .ci-mirrors.env; git clone -q "$1" "$2"; '
'git -C "$2" fetch -q origin; env', "fixture",
SSH + "Agent-System-RDM.git", str(clone)], cwd=work, env=self.env,
capture_output=True, text=True, timeout=30)
self.assertEqual(replay.returncode, 0, replay.stderr)
self.assertNotIn(SUB_TOKEN, replay.stdout + replay.stderr)
self.assertEqual(self.git(clone, "rev-parse", "HEAD"), shas[2])
if __name__ == "__main__":
+227
View File
@@ -0,0 +1,227 @@
"""Local named checks prepare fixed sources and retain edits with real Go/Git."""
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
SOURCE = Path(__file__).resolve().parents[1]
@unittest.skipUnless(shutil.which("go"), "Go required")
class ComponentCheckTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix="component-check-")
self.addCleanup(self.tmp.cleanup)
self.base = Path(self.tmp.name)
self.root = self.base / "dev"
self.root.mkdir()
self.env = os.environ.copy()
for key in list(self.env):
if key.startswith("GIT_CONFIG_") or key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GOWORK", "COMPOSITION_SOURCE_MAP", "CI_DEPENDENCY_MIRRORS"):
self.env.pop(key, None)
self.env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL="/dev/null", GIT_ALLOW_PROTOCOL="file",
GIT_AUTHOR_NAME="Checks", GIT_AUTHOR_EMAIL="check@example.invalid",
GIT_COMMITTER_NAME="Checks", GIT_COMMITTER_EMAIL="check@example.invalid",
GOTOOLCHAIN="local", GOPROXY="off", GOSUMDB="off")
self.call(self.root, "git", "init", "--initial-branch=main")
shutil.copytree(SOURCE / "scripts", self.root / "scripts")
shutil.copyfile(SOURCE / "Makefile", self.root / "Makefile")
(self.root / ".gitignore").write_text(".workspace/\ngo.work\ngo.work.sum\ntests/integration/multi-instance/.build/\n")
app = self.base / "backend"
self.backend = app
app.mkdir()
self.call(app, "git", "init", "--initial-branch=main")
(app / "go.mod").write_text("module example.com/backend\n\ngo 1.23\n")
(app / "source.go").write_text("package backend\nfunc Value() string {return \"fixed\"}\n")
(app / "source_test.go").write_text('package backend\nimport "testing"\nfunc TestValue(t *testing.T) {if Value() != "fixed" {t.Fatal(Value())}}\n')
(app / "cmd/agent-system").mkdir(parents=True)
(app / "cmd/agent-system/main.go").write_text('package main\nimport "fmt"\nfunc main(){fmt.Print("fixed-binary")}\n')
self.call(app, "git", "add", ".")
self.call(app, "git", "commit", "-m", "Fixed Backend")
sha = self.call(app, "git", "rev-parse", "HEAD").stdout.strip()
manifest = {"schema": 1, "components": [{"id": "Agent-System-Backend", "repo": str(app), "path": ".workspace/Agent-System-Backend", "legacy_path": "modules/backend", "role": "application", "modules": [{"path": "example.com/backend", "directory": "."}], "target": "main"}]}
(self.root / "components.json").write_text(json.dumps(manifest))
(self.root / "components.lock.json").write_text(json.dumps({"schema": 1, "components": {"Agent-System-Backend": {"commit": sha}}}))
self.call(self.root, "git", "add", ".")
self.call(self.root, "git", "commit", "-m", "Fixed Dev")
def call(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=90)
if check:
self.assertEqual(result.returncode, 0, result.stdout)
return result
def test_backend_check_prepares_fixed_source_and_replays_owner_edits(self):
result = self.call(self.root, "make", "test-backend")
self.assertIn("GOWORK=off", result.stdout)
child = self.root / ".workspace/Agent-System-Backend"
self.assertEqual((child / "source.go").read_text(), 'package backend\nfunc Value() string {return "fixed"}\n')
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/owned")
(child / "source.go").write_text('package backend\nfunc Value() string {return "owner-edit"}\n')
result = self.call(self.root, "make", "test-backend", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("owner-edit", result.stdout)
self.assertEqual(self.call(child, "git", "branch", "--show-current").stdout.strip(), "feature/owned")
self.assertIn('"owner-edit"', (child / "source.go").read_text())
def test_pg_gate_requires_environment_and_forwards_it_to_real_go_tests(self):
result = self.call(self.root, "make", "test-backend-pg", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertFalse((self.root / ".workspace").exists())
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/pg-gate")
child = self.root / ".workspace/Agent-System-Backend"
(child / "pg_gate_test.go").write_text('package backend\nimport("os";"testing")\nfunc TestGateEnv(t *testing.T){if os.Getenv("AGENT_PG_TEST_DSN")!="fixture-dsn"||os.Getenv("AGENT_REDIS_TEST_ADDR")!="fixture-redis"{t.Fatal("gate environment missing")}}\n')
self.call(self.root, "make", "test-backend-pg", env=self.env | {"AGENT_PG_TEST_DSN": "fixture-dsn", "AGENT_REDIS_TEST_ADDR": "fixture-redis"})
def test_multi_instance_binary_is_built_inside_dev_root_and_reached_by_suite(self):
suite = self.add_multi_instance_suite()
self.call(self.root, "make", "test-multi-instance", env=self.env | {"AGENT_PG_TEST_DSN": "fixture-dsn"})
self.assertTrue((suite / ".build/agent-system").is_file())
self.assertFalse((self.root / ".workspace/tests").exists())
def add_multi_instance_suite(self):
suite = self.root / "tests/integration/multi-instance"
suite.mkdir(parents=True)
(suite / "go.mod").write_text("module example.com/multi\n\ngo 1.23\n")
(suite / "binary_test.go").write_text('package multi\nimport("os";"os/exec";"testing")\nfunc TestBinary(t *testing.T){b,e:=exec.Command(os.Getenv("AGENT_BIN")).Output();if e!=nil||string(b)!="fixed-binary"{t.Fatalf("%s %v",b,e)}}\n')
return suite
def add_go_byproduct_fixture(self, fail=False):
(self.backend / ".gitignore").write_text("__pycache__/\n.fixture-output\n")
(self.backend / "fixture_module.py").write_text("VALUE = 'imported from Go'\n")
(self.backend / "byproduct_test.go").write_text('''package backend
import("os";"os/exec";"testing")
func TestByproducts(t *testing.T) {
if err := os.WriteFile(".fixture-output", []byte("generated by Go test"), 0600); err != nil {t.Fatal(err)}
cmd := exec.Command("python3", "-c", "import fixture_module")
cmd.Env = append(os.Environ(), "PYTHONDONTWRITEBYTECODE=", "PYTHONPYCACHEPREFIX=")
if out, err := cmd.CombinedOutput(); err != nil {t.Fatalf("%s %v", out, err)}
''' + (' t.Fatal("deliberate fixture failure")\n' if fail else '') + '}\n')
self.call(self.backend, "git", "add", ".")
self.call(self.backend, "git", "commit", "-m", "Add Go and Python byproducts fixture")
lock = json.loads((self.root / "components.lock.json").read_text())
lock["components"]["Agent-System-Backend"]["commit"] = self.call(self.backend, "git", "rev-parse", "HEAD").stdout.strip()
(self.root / "components.lock.json").write_text(json.dumps(lock))
self.call(self.root, "git", "add", "components.lock.json")
self.call(self.root, "git", "commit", "-m", "Pin byproducts fixture")
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "prepare")
return self.root / ".workspace/Agent-System-Backend"
def source_snapshot(self, child):
return {"head": self.call(child, "git", "rev-parse", "HEAD").stdout,
"branch": self.call(child, "git", "branch", "--show-current").stdout,
"index": (child / ".git/index").read_bytes(),
"status": self.call(child, "git", "status", "--porcelain", "--untracked-files=all").stdout,
"ignored": self.call(child, "git", "ls-files", "--others", "--ignored", "--exclude-standard").stdout,
"files": {path.relative_to(child).as_posix(): path.read_bytes() for path in child.rglob("*")
if path.is_file() and ".git" not in path.relative_to(child).parts}}
def test_fixed_go_checks_keep_source_clean_across_pg_and_multi_instance(self):
child = self.add_go_byproduct_fixture()
suite = self.add_multi_instance_suite()
before = self.source_snapshot(child)
env = self.env | {"AGENT_PG_TEST_DSN": "fixture-dsn", "AGENT_REDIS_TEST_ADDR": "fixture-redis"}
self.call(self.root, "make", "test-backend-pg", env=env)
generated = self.call(child, "git", "ls-files", "--others", "--ignored", "--exclude-standard").stdout
print("fixed checkout byproducts after PostgreSQL fixture: " + repr(generated), flush=True)
self.call(self.root, "make", "test-multi-instance", env=env)
self.assertEqual(self.source_snapshot(child), before)
self.assertTrue((suite / ".build/agent-system").is_file())
def test_fixed_go_failure_is_reported_without_polluting_source(self):
child = self.add_go_byproduct_fixture(fail=True)
before = self.source_snapshot(child)
result = self.call(self.root, "make", "test-backend", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("deliberate fixture failure", result.stdout)
self.assertIn("local component command failed (1)", result.stdout)
self.assertEqual(self.source_snapshot(child), before)
def test_fixed_go_checks_refuse_and_preserve_owner_changes_and_ignored_files(self):
child = self.add_go_byproduct_fixture()
for kind in ("unstaged", "staged", "ignored"):
with self.subTest(kind=kind):
if kind == "ignored":
(child / ".fixture-output").write_text("owner's unique ignored file")
else:
(child / "source.go").write_text('package backend\nfunc Value() string {return "owner-edit"}\n')
if kind == "staged":
self.call(child, "git", "add", "source.go")
before = self.source_snapshot(child)
result = self.call(self.root, "make", "test-backend", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("dirty or ignored unique files", result.stdout)
self.assertEqual(self.source_snapshot(child), before)
# This fixture owns these files and its index; reset only between cases.
if kind != "ignored":
self.call(child, "git", "restore", "--source=HEAD", "--staged", "--worktree", "source.go")
def test_pg_and_multi_instance_prerequisites_still_refuse_before_preparing_sources(self):
for target, supplied in (("test-backend-pg", {}),
("test-backend-pg", {"AGENT_PG_TEST_DSN": "fixture-dsn"}),
("test-multi-instance", {})):
with self.subTest(target=target, supplied=supplied):
env = {key: value for key, value in self.env.items()
if key not in ("AGENT_PG_TEST_DSN", "AGENT_REDIS_TEST_ADDR")}
result = self.call(self.root, "make", target, check=False, env=env | supplied)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertFalse((self.root / ".workspace").exists())
def test_new_pin_gate_validates_fixed_sources_and_rejects_local_replaces(self):
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "prepare")
self.call(self.root, "bash", "scripts/check-pins.sh")
child = self.root / ".workspace/Agent-System-Backend"
(child / "go.mod").write_text((child / "go.mod").read_text() + "\nreplace example.com/dependency => ../local\n")
result = self.call(self.root, "bash", "scripts/check-pins.sh", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("local replace", result.stdout)
def test_registered_private_transport_reaches_real_go_process_without_global_change(self):
manifest = json.loads((self.root / "components.json").read_text())
manifest["transport"] = {"go_private": ["private.example.test"], "git_rewrites": [{"from": "https://private.example.test/", "to": self.base.as_uri() + "/"}]}
(self.root / "components.json").write_text(json.dumps(manifest))
global_config = self.base / "global.gitconfig"
global_config.write_text("[user]\n name = existing-user\n")
env = self.env | {"GIT_CONFIG_GLOBAL": str(global_config)}
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/transport", env=env)
child = self.root / ".workspace/Agent-System-Backend"
(child / "transport_test.go").write_text('package backend\nimport("os";"testing")\nfunc TestTransport(t *testing.T){if os.Getenv("GOPRIVATE")!="private.example.test"||os.Getenv("GOWORK")!="off"||os.Getenv("GIT_CONFIG_COUNT")==""{t.Fatal("process-scoped transport missing")}}\n')
before = global_config.read_bytes()
self.call(self.root, "make", "test-backend", env=env)
self.assertEqual(global_config.read_bytes(), before)
def test_explicit_overlay_uses_registered_workspace_and_preserves_default_version_mode(self):
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/overlay")
child = self.root / ".workspace/Agent-System-Backend"
work = self.root / "go.work"
(child / "overlay_test.go").write_text('package backend\nimport("os";"testing")\nfunc TestOverlay(t *testing.T){if os.Getenv("GOWORK")!="' + str(work) + '"{t.Fatal("registered overlay missing")}}\n')
self.call(self.root, "make", "test-backend", "LOCAL_OVERLAY=1")
default = self.call(self.root, "make", "test-backend", check=False)
self.assertNotEqual(default.returncode, 0, default.stdout)
self.assertIn("registered overlay missing", default.stdout)
self.assertTrue(work.is_file())
def test_make_pin_gate_uses_map_in_gitless_formal_root_and_ignores_external_rdm_identity(self):
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "prepare")
formal = self.base / "formal"
formal.mkdir()
shutil.copytree(self.root / "scripts", formal / "scripts")
for name in ("Makefile", "components.json", "components.lock.json"):
shutil.copy2(self.root / name, formal / name)
entry = json.loads((self.root / "components.json").read_text())["components"][0]
locked = json.loads((self.root / "components.lock.json").read_text())["components"][entry["id"]]["commit"]
mapping = {"schema": 1, "root_commit": self.call(self.root, "git", "rev-parse", "HEAD").stdout.strip(),
"components": {entry["id"]: {"path": str(self.root / entry["path"]), "repo": entry["repo"], "commit": locked},
"Agent-System-RDM": {"path": str(self.base / "external-rdm"), "repo": "external registered by composer", "commit": locked}}}
source_map = self.base / "map.json"
source_map.write_text(json.dumps(mapping))
self.call(formal, "make", "test-pins", env=self.env | {"COMPOSITION_SOURCE_MAP": str(source_map)})
self.assertFalse((formal / ".workspace").exists())
if __name__ == "__main__":
unittest.main()
+93
View File
@@ -0,0 +1,93 @@
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
SCRIPTS = Path(__file__).resolve().parents[1] / 'scripts'
class ComponentGovernanceTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.run_git(self.root, 'init', '-q')
self.run_git(self.root, 'config', 'user.name', 'Fixture')
self.run_git(self.root, 'config', 'user.email', 'fixture@example.test')
self.child = self.root / '.workspace/Provider'
self.child.mkdir(parents=True)
self.run_git(self.child, 'init', '-q')
self.run_git(self.child, 'config', 'user.name', 'Fixture')
self.run_git(self.child, 'config', 'user.email', 'fixture@example.test')
(self.child/'value').write_text('one')
self.run_git(self.child, 'add', 'value')
self.run_git(self.child, 'commit', '-qm', 'Add value')
(self.root/'.gitignore').write_text('.workspace/\n')
(self.root/'components.json').write_text(json.dumps({'schema':1,'components':[{
'id':'Provider','repo':self.child.as_uri(),'path':'.workspace/Provider',
'role':'documentation','modules':[],'target':'main'}]}))
self.run_git(self.root, 'add', '.gitignore', 'components.json')
self.run_git(self.root, 'commit', '-qm', 'Add component mapping')
self.old = Path.cwd()
os.chdir(self.root)
self.addCleanup(os.chdir,self.old)
def run_git(self,root,*args):
return subprocess.check_output(['git','-C',str(root),*args],stderr=subprocess.STDOUT,text=True)
def snapshot(self):
spec=importlib.util.spec_from_file_location('component_evidence',SCRIPTS/'evidence.py')
module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
return module.snapshot()
def test_evidence_captures_ignored_component_revision_and_dirty_bytes(self):
first=self.snapshot()
(self.child/'value').write_text('two')
second=self.snapshot()
self.assertIn('components',first,'receipts must identify ordinary ignored component checkouts')
self.assertNotEqual(first['components'],second['components'])
self.run_git(self.child,'commit','-qam','Change value')
third=self.snapshot()
self.assertNotEqual(second['components'],third['components'])
self.assertEqual(first['head'],third['head'])
def test_before_sync_rejects_component_git_lock(self):
(self.child/'.git/index.lock').write_text('active owner')
result=subprocess.run(['python3',str(SCRIPTS/'check-worktree.py'),'before-sync'],cwd=self.root,capture_output=True,text=True)
self.assertNotEqual(result.returncode,0,'a hidden component writer must block synchronization')
self.assertTrue((self.child/'.git/index.lock').exists())
def test_startup_receipt_rejects_changed_ignored_component_objects(self):
spec=importlib.util.spec_from_file_location('component_evidence_validation',SCRIPTS/'evidence.py')
module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
self.run_git(self.root,'update-ref','refs/remotes/origin/main','HEAD')
before=module.snapshot()
receipt=module.create_receipt('make precheck','[verdict] READY\n',0,before,before,
timestamp='2026-10-13T00:00:00+00:00',cwd=str(self.root),
git_common_dir=module.common_dir())
module.validate_precheck(receipt)
(self.child/'value').write_text('changed after startup')
with self.assertRaises(module.EvidenceError):
module.validate_precheck(receipt)
def test_new_layout_cannot_use_receipt_without_component_identity(self):
spec=importlib.util.spec_from_file_location('component_evidence_legacy',SCRIPTS/'evidence.py')
module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
self.run_git(self.root,'update-ref','refs/remotes/origin/main','HEAD')
before=module.snapshot();before.pop('components')
receipt=module.create_receipt('make precheck','[verdict] READY\n',0,before,before,
timestamp='2026-10-13T00:00:00+00:00',cwd=str(self.root),
git_common_dir=module.common_dir())
with self.assertRaises(module.EvidenceError):
module.validate_precheck(receipt)
def test_receipts_reject_component_path_escape(self):
data=json.loads((self.root/'components.json').read_text())
data['components'][0]['path']='../outside'
(self.root/'components.json').write_text(json.dumps(data))
with self.assertRaises(Exception):
self.snapshot()
if __name__=='__main__': unittest.main()
+700
View File
@@ -0,0 +1,700 @@
"""Real Git/npm/Go fixtures for immutable product composition."""
import base64
import hashlib
import importlib.util
import io
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tarfile
import tempfile
import threading
import unittest
import zipfile
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
SCRIPT = Path(__file__).resolve().parents[1] / "scripts/build-composition.py"
RDM_SCRIPTS = Path(os.environ.get("COMPOSITION_RDM_SOURCE", str(SCRIPT.parent.parent / ".migration/Agent-System-RDM")))
LEGACY_PRODUCT = Path(__file__).resolve().parent / "fixtures/rdm-legacy-product.sh"
class LocalNpmRegistry:
"""Real npm cache/registry traffic with no public-network dependency."""
name = "composition-cold-fixture"
def __init__(self, base, env):
self.base, self.env = base, env
self.requests = []
self.tarballs = {}
self.integrities = {}
for version in ("1.0.0", "1.0.1"):
payload = json.dumps({"name": self.name, "version": version,
"scripts": {"install": "node -e 'process.exit(91)'"}}).encode()
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w:gz") as archive:
entry = tarfile.TarInfo("package/package.json")
entry.size = len(payload)
archive.addfile(entry, io.BytesIO(payload))
self.tarballs[version] = stream.getvalue()
self.integrities[version] = "sha512-" + base64.b64encode(hashlib.sha512(stream.getvalue()).digest()).decode()
registry = self
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
registry.requests.append((self.path, self.headers.get("Authorization")))
if self.path == "/" + registry.name:
versions = {v: {"name": registry.name, "version": v, "dist": {
"tarball": registry.url + "/" + registry.name + "/-/" + v + ".tgz",
"integrity": registry.integrities[v]}} for v in registry.tarballs}
body = json.dumps({"name": registry.name, "dist-tags": {"latest": "1.0.1"}, "versions": versions}).encode()
content_type = "application/json"
else:
version = self.path.rsplit("/", 1)[-1].removesuffix(".tgz")
if version not in registry.tarballs:
self.send_error(404)
return
body, content_type = registry.tarballs[version], "application/octet-stream"
self.send_response(200)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
self.server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
self.url = "http://127.0.0.1:" + str(self.server.server_port)
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
self.thread.start()
self.cache = base / "cold-npm-cache"
self.global_config = base / "empty-npm-globalrc"
self.global_config.write_text("")
self.home = base / "cold-home"
self.home.mkdir()
self.tools = base / "npm-tools"
self.tools.mkdir()
self.calls = base / "npm-calls.jsonl"
npm = shutil.which("npm")
wrapper = self.tools / "npm"
wrapper.write_text("#!" + sys.executable + "\n" +
"import json, os, sys\nfrom pathlib import Path\nargs = sys.argv[1:]\n" +
"with Path(" + repr(str(self.calls)) + ").open('a') as output:\n" +
" output.write(json.dumps({'args': args, 'credential_keys': [k for k in os.environ if any(w in k.upper() for w in ('TOKEN', 'SECRET', 'PASSWORD'))]}) + '\\n')\n" +
"if args[:2] == ['cache', 'add'] and args[2].startswith(" + repr(self.name + "@") + "):\n" +
" args = [a.replace('https://registry.npmjs.org', " + repr(self.url) + ") for a in args]\n" +
"if args[:1] == ['ci']: args.append('--offline')\n" +
"os.execv(" + repr(npm) + ", [" + repr(npm) + ", *args])\n")
wrapper.chmod(0o755)
self.env = env | {"HOME": str(self.home), "npm_config_cache": str(self.cache),
"PATH": str(self.tools) + os.pathsep + env["PATH"]}
# Seed only the locked tarball, exactly the state produced by npm ci.
tarball = base / "locked.tgz"
tarball.write_bytes(self.tarballs["1.0.0"])
subprocess.run([npm, "cache", "add", str(tarball), "--ignore-scripts", "--userconfig=/dev/null",
"--globalconfig=" + str(self.global_config)], env=self.env, check=True, capture_output=True)
def close(self):
self.server.shutdown()
self.server.server_close()
self.thread.join()
def manifests(self, source):
source.mkdir(exist_ok=True)
manifest = {"name": "fixture", "version": "1.0.0", "dependencies": {self.name: "1.0.0"}}
(source / "package.json").write_text(json.dumps(manifest))
lock = {"name": "fixture", "version": "1.0.0", "lockfileVersion": 3, "requires": True,
"packages": {"": manifest, "node_modules/" + self.name: {
"version": "1.0.0", "resolved": "https://registry.npmjs.org/" + self.name + "/-/1.0.0.tgz",
"integrity": self.integrities["1.0.0"]}}}
(source / "package-lock.json").write_text(json.dumps(lock))
return (source / "package-lock.json").read_bytes()
def consumer(self, directory, env=None):
directory.mkdir(exist_ok=True)
(directory / "package.json").write_text(json.dumps({"dependencies": {self.name: "1.0.0"}}))
return subprocess.run(["npm", "install", "--offline", "--ignore-scripts", "--no-audit", "--no-fund",
"--registry=" + self.url, "--userconfig=/dev/null", "--globalconfig=" + str(self.global_config)],
cwd=directory, env=env or self.env, capture_output=True, text=True)
@unittest.skipUnless(shutil.which("npm"), "npm required")
class NpmAcquisitionTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(prefix="composition-cold-npm-")
self.addCleanup(self.temporary.cleanup)
self.base = Path(self.temporary.name)
self.registry = LocalNpmRegistry(self.base, os.environ.copy())
self.addCleanup(self.registry.close)
spec = importlib.util.spec_from_file_location("cold_npm_composer", SCRIPT)
self.composer = importlib.util.module_from_spec(spec)
spec.loader.exec_module(self.composer)
def test_locked_metadata_is_acquired_before_offline_consumer_without_credentials(self):
source = self.base / "source"
original = self.registry.manifests(source)
red = self.registry.consumer(self.base / "before")
self.assertNotEqual(red.returncode, 0)
self.assertIn("ENOTCACHED", red.stderr)
self.composer.npm_dependencies(source, {}, self.registry.env | {"GITEA_TOKEN": "synthetic-secret"})
self.assertEqual((source / "package-lock.json").read_bytes(), original)
green = self.registry.consumer(self.base / "after", self.composer.execution_environment(self.registry.env))
self.assertEqual(green.returncode, 0, green.stdout + green.stderr)
actual = json.loads((self.base / "after/node_modules" / self.registry.name / "package.json").read_text())
self.assertEqual(actual["version"], "1.0.0")
calls = [json.loads(line) for line in self.registry.calls.read_text().splitlines()]
metadata = [call for call in calls if call["args"][:2] == ["cache", "add"]]
self.assertTrue(metadata, "tarball acquisition alone does not populate offline resolver metadata")
self.assertEqual(metadata[0]["args"][2], self.registry.name + "@1.0.0")
self.assertFalse(metadata[0]["credential_keys"])
self.assertTrue(self.registry.requests)
self.assertTrue(all(auth is None for _, auth in self.registry.requests))
def test_unreviewed_tarball_source_is_refused_before_acquisition(self):
source = self.base / "source"
self.registry.manifests(source)
path = source / "package-lock.json"
lock = json.loads(path.read_text())
lock["packages"]["node_modules/" + self.registry.name]["resolved"] = self.registry.url + "/untrusted.tgz"
path.write_text(json.dumps(lock))
with self.assertRaisesRegex(self.composer.CompositionError, "reviewed public registry"):
self.composer.npm_dependencies(source, {}, self.registry.env | {"GITEA_TOKEN": "synthetic-secret"})
self.assertFalse(self.registry.requests)
self.assertFalse(self.registry.calls.exists())
@unittest.skipUnless(all(shutil.which(c) for c in ("git", "go", "node", "npm")), "Git/Go/Node/npm required")
class CompositionTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="composition-test-")
self.addCleanup(self.temp.cleanup)
self.base = Path(self.temp.name)
self.root = self.base / "dev"
self.backend = self.base / "backend"
self.frontend = self.base / "frontend"
self.out = self.base / "output"
self.env = os.environ.copy()
self.env.update(GOWORK="off", GOPROXY="off", GOSUMDB="off", GOTOOLCHAIN="local")
self.env.pop("GOFLAGS", None)
for repo in (self.root, self.backend, self.frontend):
repo.mkdir()
self.run_command(["git", "init", "-q", "-b", "main"], repo)
self.run_command(["git", "config", "user.email", "fixture@example.invalid"], repo)
self.run_command(["git", "config", "user.name", "Composition Fixture"], repo)
self.write(self.backend, "go.mod", "module example.com/backend\n\ngo 1.23\n")
self.write(self.backend, "web/dist/.gitkeep", "")
self.write(self.backend, "web/embed.go", '''package web
import "embed"
//go:embed all:dist
var Files embed.FS
func Page() string { b, _ := Files.ReadFile("dist/index.html"); return string(b) }
''')
self.write(self.backend, "cmd/agent-system/main.go", '''package main
import ("fmt"; "os"; "example.com/backend/web")
var version = "fixture-dev"
func main() { if len(os.Args)>1 && os.Args[1]=="version" {fmt.Print(version)} else {fmt.Print(web.Page())} }
''')
self.write(self.backend, "cmd/storage-migrate/main.go", '''package main
import "fmt"
func main() { fmt.Print("migration-tool") }
''')
self.write(self.backend, "web/embed_test.go", '''package web
import "testing"
func TestProduct(t *testing.T) { if Page() != "immutable-page" { t.Fatalf("unexpected page: %q", Page()) } }
''')
self.backend_sha = self.commit(self.backend)
self.write(self.frontend, "package.json", json.dumps({
"name": "composition-fixture", "version": "1.0.0", "private": True,
"scripts": {"build": "node build.cjs", "test": "node test.cjs", "postinstall": "node -e \"process.exit(91)\""},
}))
self.write(self.frontend, "build.cjs", '''const fs = require('node:fs');
if (process.env.GITEA_TOKEN || process.env.SSH_AUTH_SOCK || process.env.GIT_CONFIG_COUNT) process.exit(90);
fs.mkdirSync('dist/assets', { recursive: true });
fs.writeFileSync('dist/index.html', 'immutable-page');
fs.writeFileSync('dist/assets/main.js', 'console.log("fixed")');
''')
self.write(self.frontend, "test.cjs", '''const fs = require('node:fs');
if (fs.readFileSync('dist/index.html', 'utf8') !== 'immutable-page') process.exit(92);
''')
self.run_command(["npm", "install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund"], self.frontend)
self.frontend_sha = self.commit(self.frontend)
self.manifest = {"schema": 1, "components": [
{"id": "Agent-System-Backend", "repo": str(self.backend), "path": ".workspace/Agent-System-Backend", "role": "application", "modules": [{"path": "example.com/backend", "directory": "."}], "target": "main"},
{"id": "Agent-System-Frontend", "repo": str(self.frontend), "path": ".workspace/Agent-System-Frontend", "role": "application", "modules": [], "target": "main"},
]}
self.lock = {"schema": 1, "components": {
"Agent-System-Backend": {"commit": self.backend_sha},
"Agent-System-Frontend": {"commit": self.frontend_sha},
}}
self.save_inputs()
self.root_sha = self.commit(self.root)
def write(self, repo, path, value):
destination = repo / path
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(value)
def run_command(self, command, cwd=None, env=None, check=True):
result = subprocess.run(command, cwd=cwd, env=env or self.env, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if check:
self.assertEqual(result.returncode, 0, result.stdout)
return result
def commit(self, repo):
self.run_command(["git", "add", "."], repo)
self.run_command(["git", "commit", "-qm", "Fixture"], repo)
return self.run_command(["git", "rev-parse", "HEAD"], repo).stdout.strip()
def save_inputs(self):
self.write(self.root, "components.json", json.dumps(self.manifest))
self.write(self.root, "components.lock.json", json.dumps(self.lock))
def compose(self, target="build", extra=(), env=None):
return self.run_command([sys.executable, "-B", str(SCRIPT), "--root", str(self.root), "--target", target, "--output", str(self.out), *extra], env=env, check=False)
def acquisition_guard(self, env, *, mark_frontend=True):
"""Forward to real tools, refusing acquisition after candidate execution."""
marker = self.base / "candidate-executed"
tools = self.base / "guarded-tools"
tools.mkdir()
for name in ("git", "go", "npm"):
executable = shutil.which(name)
wrapper = tools / name
wrapper.write_text("#!" + sys.executable + "\n" + '''import os, pathlib, sys
args = sys.argv[1:]
name = pathlib.Path(sys.argv[0]).name
acquisition = (name == 'git' and args and args[0] in {'clone', 'fetch', 'archive'}
or name == 'go' and args and (args[0] == 'mod' or args[0] == 'list')
or name == 'npm' and args and (args[0] in {'ci', 'install'} or args[:2] == ['cache', 'add']))
if acquisition and pathlib.Path(os.environ['COMPOSITION_EXEC_MARKER']).exists():
print('acquisition-after-execution: ' + name + ' ' + ' '.join(args[:2]), file=sys.stderr)
sys.exit(86)
''' + "os.execv(" + repr(executable) + ", [" + repr(executable) + ", *args])\n")
wrapper.chmod(0o755)
if mark_frontend:
self.write(self.frontend, "build.cjs", (self.frontend / "build.cjs").read_text()
+ "\nrequire('node:fs').writeFileSync(process.env.COMPOSITION_EXEC_MARKER, 'frontend-executed')\n")
self.lock["components"]["Agent-System-Frontend"]["commit"] = self.commit(self.frontend)
self.save_inputs()
self.commit(self.root)
return env | {"PATH": str(tools) + os.pathsep + env["PATH"],
"COMPOSITION_EXEC_MARKER": str(marker), "GITEA_TOKEN": "synthetic-phase-token"}
def test_all_product_acquisition_precedes_candidate_execution(self):
self.test_rdm_vendored_remote_backend_embeds_assets_without_mutating_module_cache()
self.write(self.root, "plugins/example/plugin.json", '{"name":"fixed-plugin"}')
self.commit(self.root)
env = self.acquisition_guard(self.env | {"GOPROXY": (self.base / "proxy").as_uri(),
"GOMODCACHE": str(self.base / "module-cache")})
result = self.compose(extra=("--rdm-source", str(self.base / "rdm")), env=env)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual((self.base / "candidate-executed").read_text(), "frontend-executed")
self.assertEqual(self.run_command([str(self.out / "agent-system-rdm-product")]).stdout, "immutable-page")
self.assertEqual(self.run_command([str(self.out / "agent-system-admin")]).stdout, "immutable-page")
self.assertEqual((self.out / "plugins/example/plugin.json").read_text(), '{"name":"fixed-plugin"}')
def test_fixed_commits_embed_assets_without_touching_developer_checkout(self):
# Reading working trees or allowing go.work/npm overlays would produce DIRTY or fail.
self.write(self.backend, "web/dist/index.html", "DIRTY")
self.write(self.backend, "cmd/agent-system/main.go", "invalid local Go")
self.write(self.frontend, "build.cjs", "throw new Error('dirty local frontend')")
self.write(self.root, "go.work", "invalid developer workspace")
self.write(self.root, ".workspace/Agent-System-Frontend/package.json", '{"dependencies":{"bad":"file:/missing"}}')
env = self.env | {"GITEA_TOKEN": "fixture-secret", "SSH_AUTH_SOCK": "/missing-agent", "GIT_CONFIG_COUNT": "1", "GIT_CONFIG_KEY_0": "credential.helper", "GIT_CONFIG_VALUE_0": "bad-helper"}
result = self.compose(env=env)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual(self.run_command([str(self.out / "agent-system")]).stdout, "immutable-page")
self.assertEqual(self.run_command([str(self.out / "storage-migrate")]).stdout, "migration-tool")
self.assertEqual((self.backend / "web/dist/index.html").read_text(), "DIRTY")
resolved = json.loads((self.out / "resolved-manifest.json").read_text())
self.assertEqual(resolved["root"]["commit"], self.root_sha)
self.assertEqual(resolved["applications"]["Agent-System-Backend"]["commit"], self.backend_sha)
self.assertEqual(resolved["applications"]["Agent-System-Frontend"]["commit"], self.frontend_sha)
self.assertEqual(resolved["artifacts"]["agent-system"]["sha256"], hashlib.sha256((self.out / "agent-system").read_bytes()).hexdigest())
self.assertEqual(resolved["go"]["backend"]["modules"][0]["Path"], "example.com/backend")
self.assertTrue(resolved["npm"]["lock_sha256"])
self.assertTrue(resolved["frontend"]["sha256"])
self.assertNotIn("fixture-secret", (self.out / "resolved-manifest.json").read_text())
def test_test_target_runs_real_frontend_and_go_tests(self):
result = self.compose("test")
self.assertEqual(result.returncode, 0, result.stdout)
self.assertIn("example.com/backend/web", result.stdout)
self.assertEqual(json.loads((self.out / "resolved-manifest.json").read_text())["target"], "test")
def test_unused_transitive_zip_does_not_require_consumer_sum_rewrite(self):
proxy = self.base / "unused-proxy"
for name, mod, body in (
("unused", "module example.com/unused\n\ngo 1.16\n", "package unused\n"),
("used", "module example.com/used\n\ngo 1.16\nrequire example.com/unused v1.0.0\n", "package used\nfunc Value() int { return 7 }\n"),
):
directory = proxy / "example.com" / name / "@v"
directory.mkdir(parents=True)
(directory / "v1.0.0.mod").write_text(mod)
(directory / "v1.0.0.info").write_text('{"Version":"v1.0.0","Time":"2026-10-13T00:00:00Z"}')
(directory / "list").write_text("v1.0.0\n")
with zipfile.ZipFile(directory / "v1.0.0.zip", "w") as archive:
archive.writestr(f"example.com/{name}@v1.0.0/go.mod", mod)
archive.writestr(f"example.com/{name}@v1.0.0/value.go", body)
env = self.env | {"GOPROXY": proxy.as_uri(), "GOMODCACHE": str(self.base / "unused-cache")}
self.write(self.backend, "go.mod", "module example.com/backend\n\ngo 1.23\nrequire example.com/used v1.0.0\n")
self.write(self.backend, "web/value.go", 'package web\nimport "example.com/used"\nvar Value = used.Value()\n')
self.run_command(["go", "mod", "tidy"], self.backend, env=env)
sums = (self.backend / "go.sum").read_text()
self.assertNotIn("example.com/unused v1.0.0 h1:", sums)
self.lock["components"]["Agent-System-Backend"]["commit"] = self.commit(self.backend)
self.save_inputs()
self.commit(self.root)
result = self.compose(env=env)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual((self.backend / "go.sum").read_text(), sums)
graph = json.loads((self.out / "resolved-manifest.json").read_text())["go"]["backend"]["modules"]
self.assertIn("example.com/used", {module["Path"] for module in graph})
def test_developer_package_tarball_overrides_do_not_reach_formal_tests(self):
self.write(self.frontend, "test.cjs", "if (process.env.CONTRACTS_TARBALL || process.env.UI_TARBALL) process.exit(93)")
self.lock["components"]["Agent-System-Frontend"]["commit"] = self.commit(self.frontend)
self.save_inputs()
self.commit(self.root)
result = self.compose("test", env=self.env | {"CONTRACTS_TARBALL": "/tmp/dirty-contracts.tgz", "UI_TARBALL": "/tmp/dirty-ui.tgz"})
self.assertEqual(result.returncode, 0, result.stdout)
def test_test_target_checks_fixed_go_library_and_ui_sources(self):
library = self.base / "probe-library"
library.mkdir()
self.run_command(["git", "init", "-q", "-b", "main"], library)
self.run_command(["git", "config", "user.email", "fixture@example.invalid"], library)
self.run_command(["git", "config", "user.name", "Fixture"], library)
self.write(library, "go.mod", "module example.com/probe\n\ngo 1.23\n")
self.write(library, "probe.go", "package probe\nfunc Text() string { return \"\" }\n")
self.write(library, "probe_test.go", '''package probe
import ("testing"; "os")
func TestFixedLibrary(t *testing.T) {
if err:=os.WriteFile(os.Getenv("COMPOSITION_GO_MARKER"), []byte("fixed-go-test"), 0600); err!=nil {t.Fatal(err)}
if path:=os.Getenv("COMPOSITION_EXEC_MARKER"); path!="" { if err:=os.WriteFile(path, []byte("library-executed"), 0600); err!=nil {t.Fatal(err)} }
}
''')
library_sha = self.commit(library)
version = "v0.0.0-" + self.run_command(["git", "show", "-s", "--format=%cd", "--date=format-local:%Y%m%d%H%M%S", library_sha], library).stdout.strip() + "-" + library_sha[:12]
proxy = self.base / "probe-proxy/example.com/probe/@v"
proxy.mkdir(parents=True)
(proxy / (version + ".mod")).write_text((library / "go.mod").read_text())
(proxy / (version + ".info")).write_text(json.dumps({"Version": version, "Time": "2026-10-13T00:00:00Z", "Origin": {"VCS": "git", "URL": str(library), "Hash": library_sha}}))
(proxy / "list").write_text(version + "\n")
with zipfile.ZipFile(proxy / (version + ".zip"), "w") as archive:
for path in self.run_command(["git", "ls-files"], library).stdout.splitlines():
archive.write(library / path, "example.com/probe@" + version + "/" + path)
self.write(self.backend, "go.mod", f"module example.com/backend\n\ngo 1.23\nrequire example.com/probe {version}\n")
self.write(self.backend, "web/probe.go", 'package web\nimport "example.com/probe"\nvar _ = probe.Text()\n')
env = self.env | {"GOPROXY": (self.base / "probe-proxy").as_uri(), "GOMODCACHE": str(self.base / "probe-cache"),
"COMPOSITION_GO_MARKER": str(self.base / "go-tested"), "COMPOSITION_UI_MARKER": str(self.base / "ui-tested")}
self.run_command(["go", "mod", "download", "all"], self.backend, env=env)
self.lock["components"]["Agent-System-Backend"]["commit"] = self.commit(self.backend)
self.write(library, "probe_test.go", "invalid dirty library tests")
ui = self.base / "ui"
ui.mkdir()
self.run_command(["git", "init", "-q", "-b", "main"], ui)
self.run_command(["git", "config", "user.email", "fixture@example.invalid"], ui)
self.run_command(["git", "config", "user.name", "Fixture"], ui)
self.write(ui, "package.json", json.dumps({"name": "@agent-system/ui", "version": "1.0.0", "scripts": {"test": "node test.cjs", "typecheck": "node -e \"process.exit(0)\""}}))
self.write(ui, "test.cjs", "require('node:fs').writeFileSync(process.env.COMPOSITION_UI_MARKER, 'fixed-ui-test')")
self.run_command(["npm", "install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund"], ui)
ui_sha = self.commit(ui)
package = json.loads((self.frontend / "package.json").read_text())
package["dependencies"] = {"@agent-system/ui": "git+" + ui.as_uri() + "#" + ui_sha}
self.write(self.frontend, "package.json", json.dumps(package))
self.run_command(["npm", "install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund"], self.frontend)
self.lock["components"]["Agent-System-Frontend"]["commit"] = self.commit(self.frontend)
self.write(ui, "test.cjs", "throw Error('dirty ui tests')")
self.manifest["components"].extend([
{"id": "Probe", "repo": str(library), "path": ".workspace/Probe", "role": "go-library", "modules": [{"path": "example.com/probe", "directory": "."}], "target": "main"},
{"id": "Agent-System-UI", "repo": ui.as_uri(), "path": ".workspace/Agent-System-UI", "role": "npm-library", "modules": [], "target": "main"},
])
self.save_inputs()
self.commit(self.root)
env = self.acquisition_guard(env, mark_frontend=False)
result = self.compose("test", env=env)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertTrue((self.base / "go-tested").is_file(), "resolved Go provider tests were not executed")
self.assertEqual((self.base / "go-tested").read_text(), "fixed-go-test")
self.assertTrue((self.base / "ui-tested").is_file(), "fixed npm UI provider tests were not executed")
self.assertEqual((self.base / "ui-tested").read_text(), "fixed-ui-test")
self.assertIn("go vet", result.stdout)
def test_version_and_plugin_assets_come_from_committed_root(self):
self.write(self.root, "plugins/example/plugin.json", '{"name":"immutable-plugin"}')
self.root_sha = self.commit(self.root)
self.write(self.root, "plugins/example/plugin.json", '{"name":"dirty-plugin"}')
result = self.compose()
self.assertEqual(result.returncode, 0, result.stdout)
resolved = json.loads((self.out / "resolved-manifest.json").read_text())
self.assertIn("version", resolved)
self.assertEqual(self.run_command([str(self.out / "agent-system"), "version"]).stdout, resolved["version"])
self.assertIn(self.root_sha[:12], resolved["version"])
self.assertIn(self.backend_sha[:12], resolved["version"])
self.assertIn(self.frontend_sha[:12], resolved["version"])
self.assertEqual((self.out / "plugins/example/plugin.json").read_text(), '{"name":"immutable-plugin"}')
self.assertTrue(resolved["assets"]["plugins"]["sha256"])
def test_npm_local_dependency_overlay_is_rejected(self):
lock = json.loads((self.frontend / "package-lock.json").read_text())
lock["packages"]["node_modules/local"] = {"resolved": "file:../dirty"}
self.write(self.frontend, "package-lock.json", json.dumps(lock))
self.lock["components"]["Agent-System-Frontend"]["commit"] = self.commit(self.frontend)
self.save_inputs()
self.commit(self.root)
result = self.compose()
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("local", result.stdout)
self.assertFalse((self.out / "agent-system").exists())
def test_frontend_dist_symlink_cannot_import_assets_from_outside_source(self):
external = self.base / "external-assets"
self.write(external, "index.html", "escaped-page")
self.write(external, "assets/main.js", "escaped-code")
self.write(self.frontend, "build.cjs", "require('node:fs').symlinkSync(" + json.dumps(str(external)) + ", 'dist', 'dir')")
self.lock["components"]["Agent-System-Frontend"]["commit"] = self.commit(self.frontend)
self.save_inputs()
self.commit(self.root)
result = self.compose()
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("symlink", result.stdout)
self.assertFalse((self.out / "agent-system").exists())
def test_project_test_mode_refuses_missing_boundary_gate(self):
self.write(self.root, "Makefile", "test-governance:\n\ttrue\n")
self.commit(self.root)
result = self.compose("test")
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("lint-boundaries", result.stdout)
def test_fixed_root_governance_and_boundaries_receive_proven_source_map(self):
self.write(self.root, "Makefile", "test-governance:\n\tpython3 -B scripts/map-check.py governance\n")
self.write(self.root, "scripts/lint-boundaries.sh", "#!/bin/sh\npython3 -B scripts/map-check.py boundaries\n")
self.write(self.root, "scripts/check-pins.sh", "#!/bin/sh\npython3 -B scripts/map-check.py pins\n")
self.write(self.root, "scripts/map-check.py", '''import json, os, pathlib, sys
root = pathlib.Path.cwd()
assert not (root / '.git').exists(), 'gates must run from the immutable archive'
mapping = json.loads(pathlib.Path(os.environ['COMPOSITION_SOURCE_MAP']).read_text())
lock = json.loads((root / 'components.lock.json').read_text())
for name in ('Agent-System-Backend', 'Agent-System-Frontend'):
assert mapping['components'][name]['commit'] == lock['components'][name]['commit']
assert (pathlib.Path(mapping['components']['Agent-System-Backend']['path']) / 'web/dist/index.html').read_text() == 'immutable-page'
with pathlib.Path(os.environ['COMPOSITION_GATE_MARKER']).open('a') as output: output.write(sys.argv[1]+'\\n')
''')
self.commit(self.root)
# Dirty gate code is excluded, just like dirty application code.
self.write(self.root, "scripts/map-check.py", "raise Exception('dirty governance')")
marker = self.base / "gates-run"
result = self.compose("test", extra=("--source-map", str(self.base / "source-map.json")), env=self.env | {"COMPOSITION_GATE_MARKER": str(marker)})
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual(marker.read_text(), "boundaries\npins\ngovernance\n")
self.assertEqual(json.loads((self.base / "source-map.json").read_text())["components"]["Agent-System-Backend"]["commit"], self.backend_sha)
def test_missing_frontend_assets_fails_before_publishing_binary(self):
self.write(self.frontend, "build.cjs", "require('node:fs').mkdirSync('dist', {recursive:true});")
self.lock["components"]["Agent-System-Frontend"]["commit"] = self.commit(self.frontend)
self.save_inputs()
self.commit(self.root)
result = self.compose()
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("index.html", result.stdout)
self.assertFalse((self.out / "agent-system").exists())
def test_dirty_root_lock_is_rejected(self):
self.lock["components"]["Agent-System-Backend"]["commit"] = "a" * 40
self.save_inputs()
result = self.compose()
self.assertNotEqual(result.returncode, 0, result.stdout)
def test_dirty_root_composer_cannot_publish_under_committed_identity(self):
tools = self.root / "scripts"
(tools / "workspace").mkdir(parents=True)
shutil.copy2(SCRIPT, tools / "build-composition.py")
shutil.copy2(SCRIPT.parent / "workspace/transport.py", tools / "workspace/transport.py")
self.commit(self.root)
composer = tools / "build-composition.py"
composer.write_text(composer.read_text().replace(
'(destination / ".gitkeep").touch()',
'(destination / ".gitkeep").touch(); (destination / "index.html").write_text("dirty-tool-page")'))
result = self.run_command([sys.executable, str(composer), "--root", str(self.root),
"--target", "build", "--output", str(self.out)], check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("tool", result.stdout)
self.assertFalse((self.out / "agent-system").exists())
def test_dirty_root_transport_is_refused_before_import(self):
tools = self.root / "scripts"
(tools / "workspace").mkdir(parents=True)
shutil.copy2(SCRIPT, tools / "build-composition.py")
shutil.copy2(SCRIPT.parent / "workspace/transport.py", tools / "workspace/transport.py")
self.commit(self.root)
marker = self.base / "transport-executed"
(tools / "workspace/transport.py").write_text(
"from pathlib import Path\nPath(" + repr(str(marker)) + ").write_text('executed')\n")
result = self.run_command([sys.executable, str(tools / "build-composition.py"), "--root", str(self.root),
"--target", "build", "--output", str(self.out)], check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertFalse(marker.exists(), "dirty transport executed before source identity validation")
self.assertIn("committed", result.stdout)
def test_locked_missing_revision_is_rejected(self):
self.lock["components"]["Agent-System-Backend"]["commit"] = "a" * 40
self.save_inputs()
self.commit(self.root)
result = self.compose()
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertFalse((self.out / "agent-system").exists())
def test_source_path_escape_is_rejected(self):
self.manifest["components"][0]["path"] = "../outside"
self.save_inputs()
self.commit(self.root)
result = self.compose()
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("path", result.stdout)
def test_local_go_replace_is_rejected(self):
self.write(self.backend, "go.mod", "module example.com/backend\n\ngo 1.23\nreplace example.com/dirty => /missing/local\n")
self.lock["components"]["Agent-System-Backend"]["commit"] = self.commit(self.backend)
self.save_inputs()
self.commit(self.root)
result = self.compose()
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("replace", result.stdout)
def test_rdm_vendored_remote_backend_embeds_assets_without_mutating_module_cache(self):
rdm = self.base / "rdm"
rdm.mkdir()
self.run_command(["git", "init", "-q", "-b", "main"], rdm)
self.run_command(["git", "config", "user.email", "fixture@example.invalid"], rdm)
self.run_command(["git", "config", "user.name", "Fixture"], rdm)
version = "v0.0.0-" + self.run_command(["git", "show", "-s", "--format=%cd", "--date=format-local:%Y%m%d%H%M%S", self.backend_sha], self.backend).stdout.strip() + "-" + self.backend_sha[:12]
proxy = self.base / "proxy/example.com/backend/@v"
proxy.mkdir(parents=True)
(proxy / (version + ".mod")).write_text((self.backend / "go.mod").read_text())
(proxy / (version + ".info")).write_text(json.dumps({"Version": version, "Time": "2026-10-13T00:00:00Z"}))
(proxy / "list").write_text(version + "\n")
with zipfile.ZipFile(proxy / (version + ".zip"), "w") as archive:
paths = self.run_command(["git", "ls-files"], self.backend).stdout.splitlines()
for path in paths:
archive.write(self.backend / path, "example.com/backend@" + version + "/" + path)
self.write(rdm, "go.mod", f"module example.com/rdm\n\ngo 1.23\nrequire example.com/backend {version}\n")
self.write(rdm, "main.go", 'package main\nimport ("fmt"; "example.com/backend/web")\nfunc main() { fmt.Print(web.Page()) }\n')
module_cache = self.base / "module-cache"
env = self.env | {"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(module_cache)}
self.run_command(["go", "mod", "download", "all"], rdm, env=env)
rdm_sha = self.commit(rdm)
cached_asset = module_cache / ("example.com/backend@" + version) / "web/dist/.gitkeep"
self.assertTrue(cached_asset.exists())
result = self.compose(extra=("--rdm-source", str(rdm)), env=env)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual(self.run_command([str(self.out / "agent-system-rdm-product")]).stdout, "immutable-page")
self.assertFalse(cached_asset.with_name("index.html").exists())
resolved = json.loads((self.out / "resolved-manifest.json").read_text())
self.assertEqual(resolved["applications"]["Agent-System-RDM"]["commit"], rdm_sha)
self.assertTrue(resolved["go"]["rdm"]["vendor_modules_sha256"])
self.assertTrue(resolved["go"]["rdm"]["vendor_backend_source_sha256"])
self.assertEqual(resolved["go"]["rdm"]["modules"][1]["Version"], version)
@unittest.skipUnless((RDM_SCRIPTS / "scripts/build-product.sh").is_file(), "external RDM source was not supplied; host must replay the actual wrapper")
def test_versioned_rdm_product_entry_uses_fixed_composer(self):
# Reuse the real module fixture first; its independent embed test remains
# mandatory even when an external RDM wrapper is unavailable.
self.test_rdm_vendored_remote_backend_embeds_assets_without_mutating_module_cache()
rdm = self.base / "rdm"
version = self.run_command(["go", "list", "-m", "-f", "{{.Version}}", "example.com/backend"], rdm,
env=self.env | {"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")}).stdout.strip()
env = self.env | {"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")}
# The versioned RDM entry invokes the same fixed Dev composer, even when
# local RDM Go source is dirty. No API-only fallback is allowed.
self.write(rdm, "go.mod", f"module git-ssh.u1680832.nyat.app/weichen/Agent-System-RDM.git\n\ngo 1.23\nrequire example.com/backend {version}\n")
shutil.copy2(RDM_SCRIPTS / "Makefile", rdm / "Makefile")
(rdm / "scripts").mkdir()
shutil.copy2(RDM_SCRIPTS / "scripts/build-product.sh", rdm / "scripts/build-product.sh")
self.commit(rdm)
self.write(rdm, "main.go", "invalid local RDM")
(self.root / "scripts/workspace").mkdir(parents=True)
shutil.copy2(SCRIPT, self.root / "scripts/build-composition.py")
shutil.copy2(SCRIPT.parent / "workspace/transport.py", self.root / "scripts/workspace/transport.py")
self.commit(self.root)
result = self.run_command(["make", "product", "COMPOSITION_ROOT=" + str(self.root)], rdm, env=env, check=False)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual(self.run_command([str(rdm / "bin/agent-system-rdm-product")]).stdout, "immutable-page")
self.assertTrue((rdm / "bin/agent-system-admin").exists())
self.assertEqual(json.loads((rdm / "bin/PRODUCT_MANIFEST.json").read_text())["kind"], "agent-system-composition/1")
def test_modified_go_module_cache_is_rejected_before_product_publication(self):
self.test_rdm_vendored_remote_backend_embeds_assets_without_mutating_module_cache()
cached = next((self.base / "module-cache/example.com").glob("backend@*/web/embed.go"))
cached.chmod(0o644)
cached.write_text(cached.read_text() + "\n// unauthorized cache mutation\n")
result = self.compose(extra=("--rdm-source", str(self.base / "rdm")),
env=self.env | {"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")})
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("modified", result.stdout)
def test_rdm_product_and_admin_follow_its_selected_backend_not_dev_source_lock(self):
self.test_rdm_vendored_remote_backend_embeds_assets_without_mutating_module_cache()
selected_embed = (self.backend / "web/embed.go").read_text().replace("return string(b)", 'return "selected-rdm:" + string(b)')
self.write(self.backend, "web/embed.go", selected_embed)
selected_sha = self.commit(self.backend)
selected_version = "v0.0.0-" + self.run_command(["git", "show", "-s", "--format=%cd", "--date=format-local:%Y%m%d%H%M%S", selected_sha], self.backend).stdout.strip() + "-" + selected_sha[:12]
proxy = self.base / "proxy/example.com/backend/@v"
(proxy / (selected_version + ".mod")).write_text((self.backend / "go.mod").read_text())
(proxy / (selected_version + ".info")).write_text(json.dumps({"Version": selected_version, "Time": "2026-10-13T00:00:00Z", "Origin": {"VCS": "git", "URL": str(self.backend), "Hash": selected_sha}}))
with zipfile.ZipFile(proxy / (selected_version + ".zip"), "w") as archive:
for path in self.run_command(["git", "ls-files"], self.backend).stdout.splitlines():
archive.write(self.backend / path, "example.com/backend@" + selected_version + "/" + path)
rdm = self.base / "rdm"
self.write(rdm, "go.mod", f"module example.com/rdm\n\ngo 1.23\nrequire example.com/backend {selected_version}\n")
env = self.env | {"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")}
self.run_command(["go", "mod", "download", "all"], rdm, env=env)
self.commit(rdm)
result = self.compose(extra=("--rdm-source", str(rdm)), env=env)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual(self.run_command([str(self.out / "agent-system")]).stdout, "immutable-page")
self.assertEqual(self.run_command([str(self.out / "agent-system-rdm-product")]).stdout, "selected-rdm:immutable-page")
self.assertEqual(self.run_command([str(self.out / "agent-system-admin")]).stdout, "selected-rdm:immutable-page")
resolved = json.loads((self.out / "resolved-manifest.json").read_text())
self.assertEqual(resolved["applications"]["Agent-System-Backend"]["commit"], self.backend_sha)
self.assertEqual(resolved["go"]["rdm"]["backend_source"]["commit"], selected_sha)
self.assertEqual(resolved["go"]["rdm"]["backend_source"]["version"], selected_version)
admin_version = self.run_command([str(self.out / "agent-system-admin"), "version"]).stdout
self.assertEqual(admin_version, resolved["go"]["rdm"]["admin_version"])
self.assertIn(selected_sha[:12], admin_version)
self.assertFalse((self.base / "module-cache/example.com" / ("backend@" + selected_version) / "web/dist/index.html").exists())
def test_historical_rdm_product_still_builds_from_legacy_pins(self):
rdm = self.base / "legacy-rdm"
rdm.mkdir()
self.run_command(["git", "init", "-q", "-b", "main"], rdm)
self.run_command(["git", "config", "user.email", "fixture@example.invalid"], rdm)
self.run_command(["git", "config", "user.name", "Fixture"], rdm)
self.write(rdm, "go.mod", "module legacy-rdm\n\ngo 1.23\nrequire example.com/backend v0.0.0\nreplace example.com/backend => ./pins/agent-system-backend\n")
self.write(rdm, "main.go", 'package main\nimport ("fmt"; "example.com/backend/web")\nfunc main() { fmt.Print(web.Page()) }\n')
(rdm / "scripts").mkdir()
shutil.copy2(LEGACY_PRODUCT, rdm / "scripts/build-product.sh")
self.commit(rdm)
pins = rdm / "pins"
pins.mkdir()
for name, source in (("agent-system-backend", self.backend), ("agent-system-frontend", self.frontend), ("agent-system-ui", self.frontend)):
self.run_command(["git", "clone", "-q", str(source), str(pins / name)])
for name in ("agent-system-frontend", "agent-system-ui"):
package = json.loads((pins / name / "package.json").read_text())
del package["scripts"]["postinstall"]
self.write(pins / name, "package.json", json.dumps(package))
self.run_command(["npm", "install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund"], pins / name)
result = self.run_command(["bash", "scripts/build-product.sh"], rdm, check=False)
self.assertEqual(result.returncode, 0, result.stdout)
self.assertEqual(self.run_command([str(rdm / "bin/agent-system-rdm-product")]).stdout, "immutable-page")
self.assertEqual(json.loads((rdm / "bin/PRODUCT_MANIFEST.json").read_text())["kind"], "rdm-product/1")
if __name__ == "__main__":
unittest.main()
+424
View File
@@ -0,0 +1,424 @@
"""Replay trusted workflow phases with real Git, Go, npm and disposable HTTP repos."""
import json
import os
import re
from pathlib import Path
import subprocess
import shutil
import tempfile
import textwrap
import unittest
from tests import test_ci_credentials as fixture
from tests.test_composition import LocalNpmRegistry
ROOT_TOKEN, SUB_TOKEN, SSH, WORKFLOW = fixture.ROOT_TOKEN, fixture.SUB_TOKEN, fixture.SSH, fixture.WORKFLOW
MODULE = "git-ssh.u1680832.nyat.app/weichen/Logging-System.git"
def phase(workflow, name):
return fixture.workflow_script(workflow, name)
class ComponentWorkflowFixtureTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.base = Path(self.temporary.name)
self.snapshots = self.base / "snapshots"
shutil.copytree(fixture.COMPONENT_FIXTURES, self.snapshots)
def test_all_required_component_snapshots_have_fixed_provenance(self):
for repository in fixture.COMPONENT_REPOSITORIES:
with self.subTest(repository=repository):
path = fixture.component_workflow(repository)
self.assertEqual(path, fixture.COMPONENT_FIXTURES / repository / "ci.yml")
self.assertTrue(phase(path, "Checkout"))
def test_modified_workflow_snapshot_is_rejected_before_execution(self):
path = self.snapshots / "Agent-Native/ci.yml"
path.write_bytes(path.read_bytes() + b"\n# unexpected workflow replacement\n")
with self.assertRaisesRegex(ValueError, "recorded source digest"):
fixture.component_workflow("Agent-Native", snapshots=self.snapshots)
def test_missing_component_snapshot_is_not_skipped(self):
(self.snapshots / "Agent-Native/ci.yml").unlink()
with self.assertRaises(FileNotFoundError):
fixture.component_workflow("Agent-Native", snapshots=self.snapshots)
def test_omitted_repository_provenance_is_rejected(self):
path = self.snapshots / "provenance.json"
provenance = json.loads(path.read_text())
del provenance["components"]["Agent-Native"]
path.write_text(json.dumps(provenance))
with self.assertRaisesRegex(ValueError, "every reviewed repository"):
fixture.component_workflow("Agent-Native", snapshots=self.snapshots)
def test_optional_source_validation_uses_immutable_commit_and_rejects_stale_head(self):
source_root = self.base / "sources"
source = source_root / "Agent-Native"
workflow = source / ".gitea/workflows/ci.yml"
workflow.parent.mkdir(parents=True)
captured = self.snapshots / "Agent-Native/ci.yml"
workflow.write_bytes(captured.read_bytes())
env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
env.update(GIT_CONFIG_GLOBAL="/dev/null", GIT_CONFIG_SYSTEM="/dev/null")
def git(*args):
return subprocess.check_output(["git", "-C", str(source), *args], env=env, text=True).strip()
git("init", "-q")
git("add", ".")
git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-q", "-m", "Source")
metadata = self.snapshots / "provenance.json"
provenance = json.loads(metadata.read_text())
provenance["components"]["Agent-Native"]["source_sha"] = git("rev-parse", "HEAD")
metadata.write_text(json.dumps(provenance))
# A mutable file must not substitute for the committed Git object.
workflow.write_text("uncommitted workflow replacement\n")
self.assertEqual(fixture.component_workflow("Agent-Native", snapshots=self.snapshots,
source_root=source_root), captured)
git("add", ".")
git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-q", "-m", "Changed")
with self.assertRaisesRegex(ValueError, "source HEAD differs"):
fixture.component_workflow("Agent-Native", snapshots=self.snapshots, source_root=source_root)
provenance["components"]["Agent-Native"]["source_sha"] = git("rev-parse", "HEAD")
metadata.write_text(json.dumps(provenance))
with self.assertRaisesRegex(ValueError, "immutable Git source"):
fixture.component_workflow("Agent-Native", snapshots=self.snapshots, source_root=source_root)
class DependencyCITests(unittest.TestCase):
def test_boundary_gate_refuses_missing_ripgrep_before_resolving_sources(self):
with tempfile.TemporaryDirectory(prefix="boundary-no-tools-") as directory:
result = subprocess.run([shutil.which("bash"), str(WORKFLOW.parents[2] / "scripts/lint-boundaries.sh")],
cwd=directory, env=os.environ | {"PATH": directory}, capture_output=True, text=True)
self.assertEqual(result.returncode, 2, result.stdout + result.stderr)
self.assertIn("requires ripgrep", result.stderr)
def test_privileged_acquisition_finishes_before_candidate_execution_in_each_job(self):
content = WORKFLOW.read_text()
jobs = re.split(r"^ ([A-Za-z0-9_-]+):\n", content, flags=re.M)
for index in range(1, len(jobs), 2):
job, body = jobs[index:index+2]
executed = False
steps = re.split(r"^ - name: ([^\n]+)\n", body, flags=re.M)
for step in range(1, len(steps), 2):
name, script = steps[step:step+2]
with self.subTest(job=job, step=name):
if re.search(r"\$\{\{\s*secrets\.", script):
self.assertFalse(executed, "privileged acquisition follows candidate execution")
# run may be an inline YAML scalar or a multiline block;
# both shell and Python project entry points execute code.
if re.search(r"(?:^|\n)\s*(?:run:\s*)?(?:make\s|go\s+(?:build|test|vet)\s|npm\s+(?:run|test)\s|bash\s+scripts/|python3?\s+(?:-m\s+unittest|scripts/))", script):
executed = True
setUp = fixture.CheckoutTests.setUp
git = fixture.CheckoutTests.git
publish = fixture.CheckoutTests.publish
run_checkout = fixture.CheckoutTests.run_checkout
def run_phase(self, name, directory=None, workflow=WORKFLOW, extra=None, after=""):
env = self.env | {"SUBMODULE_TOKEN": SUB_TOKEN, "GITHUB_SERVER_URL": self.url,
"GITHUB_REPOSITORY": "weichen/Agent-System-Dev",
"GOCACHE": str(self.base / "gocache"),
"GOMODCACHE": str(self.base / "gomodcache"),
"npm_config_cache": str(self.base / "npm-cache")}
return subprocess.run(["bash", "-c", phase(workflow, name) + "\n" + after],
cwd=directory or self.checkout, env=env | (extra or {}),
capture_output=True, text=True, timeout=60)
def commit_fixture(self, name, files):
sha = self.publish(name)
source = self.base / name
for path, content in files.items():
dest = source / path
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(content)
self.git(source, "add", ".")
subprocess.run(["git", "-C", str(source), "-c", "user.name=Fixture",
"-c", "user.email=fixture@example.invalid", "commit", "-q", "-m", "Package"],
env=self.env | {"GIT_AUTHOR_DATE": "2026-01-02T03:04:05Z",
"GIT_COMMITTER_DATE": "2026-01-02T03:04:05Z"}, check=True)
sha = self.git(source, "rev-parse", "HEAD")
self.git(source, "push", "-q", str(self.published / "weichen" / (name + ".git")), "main")
return sha
def fixture_checksums(self, module, sha):
"""Use Go's checksum algorithm on a local fixture, outside the cache under test."""
directory = self.base / "checksum-fixture"
directory.mkdir()
version = "v0.0.0-20260102030405-" + sha[:12]
(directory / "go.mod").write_text(f"module checksumfixture\n\ngo 1.25\nrequire {module} {version}\n")
repo = self.published / "weichen/Logging-System.git"
env = self.env | {"GOWORK": "off", "GOTOOLCHAIN": "local", "GOENV": "off", "GOFLAGS": "",
"GOPROXY": "off", "GOSUMDB": "off", "GOMODCACHE": str(self.base / "checksum-cache"),
"GOPRIVATE": "git-ssh.u1680832.nyat.app/weichen", "GONOPROXY": "git-ssh.u1680832.nyat.app/weichen",
"GIT_CONFIG_GLOBAL": "/dev/null", "GIT_CONFIG_COUNT": "3",
"GIT_CONFIG_KEY_0": "protocol.file.allow", "GIT_CONFIG_VALUE_0": "always",
"GIT_CONFIG_KEY_1": "url." + repo.as_uri() + ".insteadOf",
"GIT_CONFIG_VALUE_1": "https://git-ssh.u1680832.nyat.app/weichen/Logging-System",
"GIT_CONFIG_KEY_2": "core.hooksPath", "GIT_CONFIG_VALUE_2": "/dev/null"}
result = subprocess.run(["go", "mod", "download", "-json"], cwd=directory, env=env,
capture_output=True, text=True, check=True)
info = json.loads(result.stdout)
return f"{module} {version} {info['Sum']}\n{module} {version}/go.mod {info['GoModSum']}\n"
def test_registered_application_lock_materializes_fixed_source(self):
backend = self.publish("Agent-System-Backend")
root = self.commit_fixture("Agent-System-Dev", {
"components.json": json.dumps({"schema": 1, "components": [{
"id": "Agent-System-Backend", "repo": SSH + "Agent-System-Backend.git",
"path": ".workspace/Agent-System-Backend", "role": "application", "modules": []}, {
"id": "Agent-System-UI", "repo": SSH + "Agent-System-UI.git",
"path": ".workspace/Agent-System-UI", "role": "npm-library", "modules": []}]}),
"components.lock.json": json.dumps({"schema": 1, "components": {
"Agent-System-Backend": {"commit": backend}}})})
result = self.run_checkout(root)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue((self.checkout / ".workspace/Agent-System-Backend/.git").exists(),
"versioned application source was not materialized")
self.assertEqual(self.git(self.checkout / ".workspace/Agent-System-Backend", "rev-parse", "HEAD"), backend)
self.assertEqual(self.git(self.checkout / ".workspace/Agent-System-Backend", "config", "remote.origin.url"),
SSH + "Agent-System-Backend.git")
def test_unregistered_source_lock_is_rejected_before_component_fetch(self):
root = self.commit_fixture("Agent-System-Dev", {
"components.json": json.dumps({"schema": 1, "components": [{
"id": "Agent-System-Backend", "repo": self.url + "/untrusted/receiver",
"path": ".workspace/Agent-System-Backend", "role": "application", "modules": []}]}),
"components.lock.json": json.dumps({"schema": 1, "components": {
"Agent-System-Backend": {"commit": "a" * 40}}})})
result = self.run_checkout(root)
self.assertNotEqual(result.returncode, 0, "untrusted manifest was accepted")
self.assertFalse(any("Backend" in path or "untrusted" in path for path, _ in self.requests))
def test_go_prefetch_uses_only_registered_private_repo_and_local_toolchain(self):
sha = self.commit_fixture("Logging-System", {
"go.mod": f"module {MODULE}\n\ngo 1.25\n", "logging.go": "package logging\nconst Value = 42\n"})
(self.checkout / "go.mod").write_text(
f"module fixture\n\ngo 1.25\n\nrequire {MODULE} v0.0.0-20260102030405-{sha[:12]}\n")
(self.checkout / "go.sum").write_text(self.fixture_checksums(MODULE, sha))
(self.checkout / "probe.go").write_text(f'package fixture\nimport logging "{MODULE}"\nvar Value = logging.Value\n')
result = self.run_phase("Prefetch Go modules without executing candidate code",
extra={"GOTOOLCHAIN": "auto", "GIT_TRACE": "1"})
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertTrue(self.requests)
for path, auth in self.requests:
self.assertRegex(path, r"^/weichen/Logging-System\.git/")
self.assertEqual(auth, "token " + SUB_TOKEN)
self.assertNotIn(SUB_TOKEN, result.stdout + result.stderr)
tested = subprocess.run(["go", "test", "./..."], cwd=self.checkout,
env=self.env | {"GOTOOLCHAIN": "local", "GOWORK": "off", "GOPROXY": "off",
"GOSUMDB": "off", "GOMODCACHE": str(self.base / "gomodcache"),
"GOCACHE": str(self.base / "gocache")}, capture_output=True, text=True)
self.assertEqual(tested.returncode, 0, tested.stdout + tested.stderr)
for config in self.base.rglob("config"):
if config.is_file():
self.assertNotIn(SUB_TOKEN, config.read_text(errors="replace"))
def test_unregistered_go_module_rejected_before_authenticated_request(self):
(self.checkout / "go.mod").write_text(
"module fixture\n\ngo 1.25\nrequire git-ssh.u1680832.nyat.app/weichen/Unlisted.git v1.0.0\n")
result = self.run_phase("Prefetch Go modules without executing candidate code")
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.requests)
def test_npm_git_package_installs_committed_output_without_prepare(self):
marker = self.base / "prepare-ran"
sha = self.commit_fixture("Agent-System-Core", {
"package.json": json.dumps({"name": "@agent-system/contracts", "version": "1.0.0",
"files": ["package"], "main": "package/index.js"}),
"package/index.js": "module.exports = {value: 42}\n"})
spec = "git+" + SSH + "Agent-System-Core.git#" + sha
(self.checkout / "package.json").write_text(json.dumps({"name": "fixture", "version": "1.0.0",
"dependencies": {"@agent-system/contracts": spec},
"scripts": {"postinstall": f"touch '{marker}'"}}))
(self.checkout / "package-lock.json").write_text(json.dumps({"name": "fixture", "version": "1.0.0",
"lockfileVersion": 3, "requires": True, "packages": {"": {"name": "fixture", "version": "1.0.0",
"dependencies": {"@agent-system/contracts": spec}}, "node_modules/@agent-system/contracts": {
"version": "1.0.0", "resolved": spec}}}))
result = self.run_phase("Prefetch npm packages without lifecycle scripts")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertFalse(marker.exists(), "candidate lifecycle ran while credential config was present")
consumer = subprocess.run(["node", "-e", "if(require('@agent-system/contracts').value !== 42)process.exit(1)"],
cwd=self.checkout, env=self.env, capture_output=True, text=True)
self.assertEqual(consumer.returncode, 0, consumer.stderr)
self.assertNotIn(SUB_TOKEN, result.stdout + result.stderr)
for path, auth in self.requests:
self.assertRegex(path, r"^/weichen/Agent-System-Core\.git/")
self.assertEqual(auth, "token " + SUB_TOKEN)
def test_npm_prefetch_populates_cold_offline_resolver_metadata_without_credentials(self):
workflows = {"Agent-System-Dev": WORKFLOW, **{repo: fixture.component_workflow(repo)
for repo in ("Agent-System-UI", "Agent-System-Frontend")}}
for repo, workflow in workflows.items():
with self.subTest(repo=repo):
base = self.base / repo
base.mkdir()
registry = LocalNpmRegistry(base, self.env)
self.addCleanup(registry.close)
original = registry.manifests(self.checkout)
red = registry.consumer(base / "cold-before")
self.assertNotEqual(red.returncode, 0)
self.assertIn("ENOTCACHED", red.stderr)
result = self.run_phase("Prefetch npm packages without lifecycle scripts", workflow=workflow, extra=registry.env)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual((self.checkout / "package-lock.json").read_bytes(), original)
green = registry.consumer(base / "cold-after")
self.assertEqual(green.returncode, 0, green.stdout + green.stderr)
installed = json.loads((base / "cold-after/node_modules" / registry.name / "package.json").read_text())
self.assertEqual(installed["version"], "1.0.0")
calls = [json.loads(line) for line in registry.calls.read_text().splitlines()]
metadata = [call for call in calls if call["args"][:2] == ["cache", "add"]]
self.assertTrue(metadata)
self.assertEqual(metadata[0]["args"][2], registry.name + "@1.0.0")
self.assertFalse(metadata[0]["credential_keys"])
self.assertTrue(all(auth is None for _, auth in registry.requests))
def test_npm_metadata_version_cannot_be_a_destination(self):
(self.checkout / "package.json").write_text(json.dumps({"name": "fixture"}))
(self.checkout / "package-lock.json").write_text(json.dumps({"lockfileVersion": 3, "packages": {
"node_modules/evil": {"version": self.url + "/untrusted.tgz",
"resolved": "https://registry.npmjs.org/evil/-/evil-1.0.0.tgz"}}}))
workflows = {"Agent-System-Dev": WORKFLOW, **{repo: fixture.component_workflow(repo)
for repo in ("Agent-System-UI", "Agent-System-Frontend")}}
for repo, workflow in workflows.items():
with self.subTest(repo=repo):
result = self.run_phase("Prefetch npm packages without lifecycle scripts", workflow=workflow)
self.assertNotEqual(result.returncode, 0)
self.assertIn("locked registry package", result.stderr)
self.assertFalse(self.requests)
def test_npm_provider_prepare_is_rejected_without_execution(self):
marker = self.base / "prepare-ran"
sha = self.commit_fixture("Agent-System-Core", {
"package.json": json.dumps({"name": "@agent-system/contracts", "version": "1.0.0",
"files": ["package"], "scripts": {"prepare": f"touch '{marker}'"}}),
"package/index.js": "module.exports = {value: 42}\n"})
spec = "git+" + SSH + "Agent-System-Core.git#" + sha
(self.checkout / "package.json").write_text(json.dumps({"name": "fixture", "dependencies": {"contracts": spec}}))
(self.checkout / "package-lock.json").write_text(json.dumps({"lockfileVersion": 3,
"packages": {"node_modules/contracts": {"resolved": spec}}}))
result = self.run_phase("Prefetch npm packages without lifecycle scripts")
self.assertNotEqual(result.returncode, 0)
self.assertIn("without acquisition lifecycle", result.stderr)
self.assertFalse(marker.exists())
def test_external_npm_git_source_rejected_before_authenticated_request(self):
spec = self.url + "/untrusted/receiver.git#" + "a" * 40
(self.checkout / "package.json").write_text(json.dumps({"name": "fixture", "dependencies": {"evil": "git+" + spec}}))
(self.checkout / "package-lock.json").write_text(json.dumps({"lockfileVersion": 3,
"packages": {"node_modules/evil": {"resolved": "git+" + spec}}}))
result = self.run_phase("Prefetch npm packages without lifecycle scripts")
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.requests)
def test_component_checkout_removes_tokens_before_candidate_executes(self):
for repo in ("Logging-System", "Error-Code-System", "Agent-System-Core", "Agent-Native", "Agent-Intent",
"Agent-Command", "Agent-Squad", "Agent-System-Backend", "Agent-System-UI",
"Agent-System-Frontend", "Agent-System-RDM"):
with self.subTest(repo=repo):
sha = self.publish(repo)
self.checkout = self.base / (repo + "-checkout")
self.checkout.mkdir()
result = self.run_phase("Checkout", workflow=fixture.component_workflow(repo),
extra={"TOKEN": ROOT_TOKEN, "GITHUB_SHA": sha, "GITHUB_REPOSITORY": "weichen/" + repo},
after="python3 probe.py")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_go_requirement_for_new_toolchain_fails_without_downloading_one(self):
(self.checkout / "go.mod").write_text("module fixture\n\ngo 99.0\n")
result = self.run_phase("Prefetch Go modules without executing candidate code", extra={"GOTOOLCHAIN": "auto"})
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.requests)
self.assertIn("GOTOOLCHAIN=local", result.stderr)
def test_npm_nonregistry_tarball_rejected_before_request(self):
spec = self.url + "/untrusted/payload.tgz"
(self.checkout / "package.json").write_text(json.dumps({"name": "fixture", "dependencies": {"evil": spec}}))
(self.checkout / "package-lock.json").write_text(json.dumps({"lockfileVersion": 3,
"packages": {"node_modules/evil": {"resolved": spec}}}))
result = self.run_phase("Prefetch npm packages without lifecycle scripts")
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.requests, "candidate tarball source was contacted")
def test_component_ci_executes_nested_go_module_tests(self):
(self.checkout / "go.mod").write_text("module fixture\n\ngo 1.25\n")
(self.checkout / "main.go").write_text("package fixture\nconst Value = 42\n")
(self.checkout / ".ci-mirrors.env").write_text("export GOTOOLCHAIN=local GOWORK=off GOENV=off\n")
for repo, suffix in (("Logging-System", "examples/trace"), ("Agent-Squad", "adapters/native")):
with self.subTest(repo=repo):
directory = self.checkout / suffix
directory.mkdir(parents=True, exist_ok=True)
(directory / "go.mod").write_text("module nestedfixture\n\ngo 1.25\n")
(directory / "regression_test.go").write_text(
'package nestedfixture\nimport "testing"\nfunc TestNested(t *testing.T) {t.Fatal("intentional nested regression")}\n')
lines = fixture.component_workflow(repo).read_text().splitlines()
command = next(i for i, line in enumerate(lines) if "go vet" in line)
start = max(i for i in range(command) if lines[i].strip().endswith("run: |")) + 1
end = next((i for i in range(start, len(lines))
if lines[i].strip() and not lines[i].startswith(" ")), len(lines))
script = textwrap.dedent("\n".join(lines[start:end]))
# Component CI's race gate uses the native runner C compiler;
# formal product builds independently set CGO_ENABLED=0.
result = subprocess.run(["bash", "-c", script], cwd=self.checkout, env=self.env | {"CGO_ENABLED": "1"},
capture_output=True, text=True, timeout=60)
self.assertNotEqual(result.returncode, 0, "nested regression was skipped by component CI")
self.assertIn("intentional nested regression", result.stdout + result.stderr)
def test_go_prefetch_covers_logging_trace_module(self):
sha = self.commit_fixture("Logging-System", {
"go.mod": f"module {MODULE}\n\ngo 1.25\n", "logging.go": "package logging\nconst Value = 42\n"})
(self.checkout / "go.mod").write_text("module fixture\n\ngo 1.25\n")
example = self.checkout / "examples/trace"
example.mkdir(parents=True)
(example / "go.mod").write_text(
f"module tracefixture\n\ngo 1.27\n\nrequire {MODULE} v0.0.0-20260102030405-{sha[:12]}\n")
(example / "go.sum").write_text(self.fixture_checksums(MODULE, sha))
before = (example / "go.sum").read_bytes()
result = self.run_phase("Prefetch Go modules without executing candidate code",
workflow=fixture.component_workflow("Logging-System"))
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertTrue(self.requests, "trace module's private requirement was not prefetched")
self.assertEqual((example / "go.sum").read_bytes(), before, "Go 1.27 prefetch pruned source checksums")
def test_go_prefetch_rejects_mutation_of_committed_checksums(self):
sha = self.commit_fixture("Logging-System", {
"go.mod": f"module {MODULE}\n\ngo 1.25\n", "logging.go": "package logging\nconst Value = 42\n"})
(self.checkout / "go.mod").write_text(
f"module fixture\n\ngo 1.25\n\nrequire {MODULE} v0.0.0-20260102030405-{sha[:12]}\n")
(self.checkout / "go.sum").write_text("")
result = self.run_phase("Prefetch Go modules without executing candidate code")
self.assertNotEqual(result.returncode, 0, "private acquisition silently rewrote committed checksums")
self.assertTrue("modified a committed Go dependency file" in result.stderr or "missing go.sum" in result.stderr,
result.stderr)
def test_rdm_composition_lock_fetches_fixed_dev_without_executing_code(self):
dev = self.commit_fixture("Agent-System-Dev", {"should-not-run.py": "raise RuntimeError('candidate executed')\n"})
(self.checkout / "composition.dev.json").write_text(json.dumps({
"schema": 1, "repo": SSH + "Agent-System-Dev.git", "commit": dev}))
result = self.run_phase("Prefetch fixed Dev product composition",
workflow=fixture.component_workflow("Agent-System-RDM"))
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.git(self.checkout / ".ci-composition-dev", "rev-parse", "HEAD"), dev)
for path, auth in self.requests:
self.assertRegex(path, r"^/weichen/Agent-System-Dev\.git/")
self.assertEqual(auth, "token " + SUB_TOKEN)
self.assertNotIn(SUB_TOKEN, result.stdout + result.stderr)
def test_rdm_composition_lock_rejects_external_missing_and_malformed_commits(self):
for lock in ({"schema": 1, "repo": self.url + "/untrusted/receiver", "commit": "a" * 40},
{"schema": 1, "repo": SSH + "Agent-System-Dev.git"},
{"schema": 1, "repo": SSH + "Agent-System-Dev.git", "commit": "main"}):
with self.subTest(lock=lock):
(self.checkout / "composition.dev.json").write_text(json.dumps(lock))
result = self.run_phase("Prefetch fixed Dev product composition",
workflow=fixture.component_workflow("Agent-System-RDM"))
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.requests)
if __name__ == "__main__":
unittest.main()
+138
View File
@@ -0,0 +1,138 @@
"""Source adapters exercise real Git/Go/npm builds without starting services."""
import json
import os
from pathlib import Path
import shutil
import subprocess
import unittest
import zipfile
import test_composition
SOURCE = Path(__file__).resolve().parents[1]
@unittest.skipUnless(all(shutil.which(command) for command in ("git", "go", "node", "npm")), "Git/Go/Node/npm required")
class SvcBuildCutoverTests(unittest.TestCase):
def setUp(self):
self.fixture = test_composition.CompositionTests(methodName="runTest")
self.fixture.setUp()
self.addCleanup(self.fixture.doCleanups)
self.base, self.root, self.env = self.fixture.base, self.fixture.root, self.fixture.env
self.env = self.env.copy()
for key in ("COMPOSITION_ROOT", "DEV_ROOT", "COMPOSITION_SOURCE_MAP"):
self.env.pop(key, None)
(self.root / "scripts/workspace").mkdir(parents=True)
shutil.copy2(SOURCE / "scripts/build-composition.py", self.root / "scripts/build-composition.py")
shutil.copy2(SOURCE / "scripts/workspace/transport.py", self.root / "scripts/workspace/transport.py")
(self.root / ".gitignore").write_text("bin/\nprivate/\n")
(self.root / "Makefile").write_text("build frontend:\n\tpython3 scripts/build-composition.py --root . --target $@\n")
self.sha = self.fixture.commit(self.root)
self.cache = self.base / "cache"
self.call(self.base, "git", "clone", str(self.root), str(self.cache))
self.runtime = self.base / "runtime"
self.runtime.mkdir()
wrappers = self.base / "tools"
wrappers.mkdir()
git = shutil.which("git")
(wrappers / "git").write_text('#!/usr/bin/env bash\nfor arg in "$@"; do if [ "$arg" = submodule ]; then echo "recursive submodule acquisition forbidden" >&2; exit 63; fi; done\nexec ' + git + ' "$@"\n')
(wrappers / "git").chmod(0o755)
self.env["PATH"] = str(wrappers) + os.pathsep + self.env["PATH"]
def call(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=120)
if check:
self.assertEqual(result.returncode, 0, result.stdout)
return result
def adapter(self, commands, check=True, env=None):
code = 'source "$1"\nROOT="$2"; SRC_DIR="$3"; RUNTIME="$4"; ENV_NAME=fixture\n' + commands
return self.call(self.base, "bash", "-c", code, "cutover", str(SOURCE / "scripts/svc.sh"), str(self.root), str(self.cache), str(self.runtime), check=check, env=env)
def test_versioned_dev_materialization_uses_composer_without_submodules(self):
self.adapter('materialize_dev_commit "' + self.sha + '" dev build')
binary = self.runtime / ".worktrees/dev/bin/agent-system"
self.assertEqual(self.call(self.base, str(binary)).stdout, "immutable-page")
resolved = json.loads((binary.parent / "resolved-manifest.json").read_text())
self.assertEqual(resolved["root"]["commit"], self.sha)
def test_versioned_static_adapter_resolves_published_frontend_artifact(self):
self.adapter('wt=$(materialize_dev_commit "' + self.sha + '" frontend frontend)\nsvc_frontend_dist "$wt"')
dist = self.runtime / ".worktrees/frontend/bin/frontend-dist"
self.assertEqual((dist / "index.html").read_text(), "immutable-page")
def test_mismatched_existing_cache_is_rejected_before_candidate_build(self):
path = self.runtime / ".worktrees/reused"
self.call(self.base, "git", "-C", str(self.cache), "worktree", "add", "--detach", str(path), self.sha)
(self.root / "new-marker").write_text("new candidate\n")
new_sha = self.fixture.commit(self.root)
self.call(self.base, "git", "-C", str(self.cache), "fetch", "origin")
result = self.adapter('materialize_dev_commit "' + new_sha + '" reused build', check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertEqual(self.call(path, "git", "rev-parse", "HEAD").stdout.strip(), self.sha)
self.assertFalse((path / "bin").exists())
def test_cache_recycling_preserves_ignored_unique_files(self):
path = self.runtime / ".worktrees/unique"
self.call(self.base, "git", "-C", str(self.cache), "worktree", "add", "--detach", str(path), self.sha)
(path / "private").mkdir()
(path / "private/unique").write_text("owner data\n")
self.adapter('recycle_worktree unique')
self.assertTrue((path / "private/unique").is_file())
self.assertEqual((path / "private/unique").read_text(), "owner data\n")
def test_versioned_rdm_adapter_passes_fixed_dev_root_to_product_wrapper(self):
rdm = self.base / "rdm"
rdm.mkdir()
self.call(rdm, "git", "init", "-q", "-b", "main")
self.call(rdm, "git", "config", "user.email", "fixture@example.invalid")
self.call(rdm, "git", "config", "user.name", "Fixture")
version = "v0.0.0-" + self.call(self.fixture.backend, "git", "show", "-s", "--format=%cd", "--date=format-local:%Y%m%d%H%M%S", self.fixture.backend_sha).stdout.strip() + "-" + self.fixture.backend_sha[:12]
proxy = self.base / "proxy/example.com/backend/@v"
proxy.mkdir(parents=True)
(proxy / (version + ".mod")).write_text((self.fixture.backend / "go.mod").read_text())
(proxy / (version + ".info")).write_text(json.dumps({"Version": version, "Time": "2026-10-13T00:00:00Z"}))
(proxy / "list").write_text(version + "\n")
with zipfile.ZipFile(proxy / (version + ".zip"), "w") as archive:
for relative in self.call(self.fixture.backend, "git", "ls-files").stdout.splitlines():
archive.write(self.fixture.backend / relative, "example.com/backend@" + version + "/" + relative)
(rdm / "go.mod").write_text("module git-ssh.u1680832.nyat.app/weichen/Agent-System-RDM.git\n\ngo 1.23\nrequire example.com/backend " + version + "\n")
(rdm / "main.go").write_text('package main\nimport("fmt";"example.com/backend/web")\nfunc main(){fmt.Print(web.Page())}\n')
(rdm / "Makefile").write_text('build:\n\t@test -n "$(COMPOSITION_ROOT)"\n\tpython3 "$(COMPOSITION_ROOT)/scripts/build-composition.py" --root "$(COMPOSITION_ROOT)" --target build --rdm-source . --output bin\n\tcp bin/agent-system-rdm-product bin/agent-system-rdm\n')
env = self.env | {"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")}
self.call(rdm, "go", "mod", "download", "all", env=env)
rdm_sha = self.fixture.commit(rdm)
self.adapter('RDM_SRC_DIR="' + str(rdm) + '"\nmaterialize_rdm_commit "' + rdm_sha + '" rdm', env=env)
binary = self.runtime / ".worktrees/rdm-rdm/bin/agent-system-rdm"
self.assertEqual(self.call(self.base, str(binary)).stdout, "immutable-page")
self.assertEqual(json.loads((binary.parent / "resolved-manifest.json").read_text())["root"]["commit"], self.sha)
def test_e2e_build_only_uses_versioned_sources_without_runtime_or_api_calls(self):
self.test_versioned_rdm_adapter_passes_fixed_dev_root_to_product_wrapper()
rdm = self.base / "rdm"
rdm_sha = self.call(rdm, "git", "rev-parse", "HEAD").stdout.strip()
self.call(rdm, "git", "update-ref", "refs/remotes/origin/main", rdm_sha)
shutil.copytree(SOURCE / "scripts/workspace", self.root / "scripts/workspace", dirs_exist_ok=True)
for name in ("workspace.py", "component-source.py", "svc.sh", "e2e.sh"):
shutil.copy2(SOURCE / "scripts" / name, self.root / "scripts" / name)
(self.root / ".gitignore").write_text("bin/\nprivate/\n.workspace/\ngo.work\n")
self.fixture.commit(self.root)
curl = self.base / "tools/curl"
curl.write_text('#!/usr/bin/env bash\necho "runtime API must not execute in build-only mode" >&2\nexit 71\n')
curl.chmod(0o755)
output = self.base / "e2e"
env = self.env | {"RDM_REPO": str(rdm), "E2E_OUT": str(output), "GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")}
unrelated = subprocess.Popen(["sleep", "60"])
self.addCleanup(lambda: (unrelated.terminate(), unrelated.wait()) if unrelated.poll() is None else None)
env.update(API_PID=str(unrelated.pid), UI_PID=str(unrelated.pid))
result = self.call(self.root, "bash", "scripts/e2e.sh", "--build-only", env=env)
self.assertIsNone(unrelated.poll(), "build-only must not kill inherited process identities")
self.assertIn("runtime/browser/model acceptance NOT_RUN", result.stdout)
self.assertEqual(self.call(self.base, str(output / "agent-system-rdm")).stdout, "immutable-page")
self.assertEqual((self.root / "bin/frontend-dist/index.html").read_text(), "immutable-page")
self.assertFalse((output / "data").exists())
self.assertFalse((output / "cookies.txt").exists())
if __name__ == "__main__":
unittest.main()
+81
View File
@@ -236,5 +236,86 @@ class TaskRecoveryTests(unittest.TestCase):
self.assertNotEqual(result.returncode, 0, result.stdout)
class WorkspaceRecoveryTests(unittest.TestCase):
"""New-format recovery keeps its branch and fixes startup rule availability."""
def setUp(self):
import test_workspace_cutover as cutover
self.fixture = cutover.WorkspaceCutoverTests(methodName="runTest")
self.fixture.setUp()
self.addCleanup(self.fixture.doCleanups)
f = self.fixture
f.adopt()
self.wt = f.worktree()
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
self.card = self.wt / ".task/feature-old.md"
self.card.parent.mkdir()
self.card.write_text("# Recovery\n任务主记录:fixture;状态:进行中;owner:Cutover Test\n"
f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {r0}\n"
f"- 工作区:{self.wt}(worktree);分支:feature/old\n")
f.git(self.wt, "add", ".task")
f.git(self.wt, "commit", "-m", "Register current task")
self.env = f.env | {"TASK_WORK_MODE": "local"}
def precheck(self):
return self.fixture.task(self.wt, "precheck", env=self.env, check=False)
def test_local_recovery_verifies_fixed_rules_without_sync_or_application_materialization(self):
f = self.fixture
head = f.git(self.wt, "rev-parse", "HEAD").stdout
(f.repo / "new-main").write_text("main advanced\n")
f.git(f.repo, "add", "new-main")
f.git(f.repo, "commit", "-m", "Advance main")
f.git(f.repo, "push", "origin", "main")
result = self.precheck()
self.assertEqual(result.returncode, 0, result.stdout)
self.assertIn("[verdict] LOCAL_READY", result.stdout)
self.assertIn("[rules]", result.stdout)
self.assertNotIn("[verdict] READY", result.stdout)
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head)
self.assertFalse((self.wt / ".workspace").exists())
def test_local_recovery_refuses_missing_fixed_rules_before_readiness(self):
f = self.fixture
lock = self.wt / "components.lock.json"
record = json.loads(lock.read_text())
record["components"]["Rules"]["commit"] = "0" * 40
lock.write_text(json.dumps(record))
f.git(self.wt, "commit", "-am", "Pin unavailable rules")
before = self.card.read_bytes()
result = self.precheck()
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertNotIn("[verdict] LOCAL_READY", result.stdout)
self.assertEqual(self.card.read_bytes(), before)
self.assertFalse((self.wt / ".workspace").exists())
def test_resume_rechecks_fixed_rules_before_importing_receipt(self):
f = self.fixture
receipt = f.base / "resume.json"
f.call(self.wt, "bash", "scripts/evidence.sh", env=self.env | {"RECEIPT": str(receipt), "CMD": "make precheck"})
# The cache is not an application overlay; a changed rule source cannot
# turn a formerly valid local precheck into current startup authority.
rules = list((f.base / "cache").rglob("core/baseline.md"))
self.assertTrue(rules, "fixed startup rules were never prepared")
rules[0].write_text("owner changed rules\n")
before = self.card.read_bytes()
result = f.task(self.wt, "resume", env=self.env | {"PRECHECK_RECEIPT": str(receipt)}, check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertNotIn("[resumed]", result.stdout)
self.assertEqual(self.card.read_bytes(), before)
self.assertEqual(rules[0].read_text(), "owner changed rules\n")
def test_local_resume_preserves_registered_edit_branch_and_unsaved_component(self):
f = self.fixture
f.workspace(self.wt, "edit", "--name", "App", "--branch", "feature/component-owner")
child = self.wt / ".workspace/App"
(child / "content").write_text("preserve author edit\n")
receipt = f.base / "resume-edit.json"
f.call(self.wt, "bash", "scripts/evidence.sh", env=self.env | {"RECEIPT": str(receipt), "CMD": "make precheck"})
result = f.task(self.wt, "resume", env=self.env | {"PRECHECK_RECEIPT": str(receipt)})
self.assertIn("[resumed]", result.stdout)
self.assertEqual((child / "content").read_text(), "preserve author edit\n")
self.assertEqual(f.git(child, "branch", "--show-current").stdout.strip(), "feature/component-owner")
if __name__ == "__main__":
unittest.main()
+138
View File
@@ -0,0 +1,138 @@
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
from unittest.mock import patch
SCRIPT = Path(__file__).resolve().parents[1] / 'scripts/workspace/transport.py'
class TransportTests(unittest.TestCase):
def load(self):
self.assertTrue(SCRIPT.is_file(), 'process-scoped module transport is required')
spec = importlib.util.spec_from_file_location('workspace_transport', SCRIPT)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_remote_revision_download_without_global_git_change(self):
transport = self.load()
with tempfile.TemporaryDirectory() as td:
root = Path(td)
source = root / 'provider'
source.mkdir()
subprocess.run(['git', 'init', '-q', str(source)], check=True)
subprocess.run(['git', '-C', str(source), 'config', 'user.name', 'Fixture'], check=True)
subprocess.run(['git', '-C', str(source), 'config', 'user.email', 'fixture@example.test'], check=True)
(source / 'go.mod').write_text('module example.test/provider.git\n\ngo 1.27\n')
(source / 'answer.go').write_text('package provider\nfunc Answer() int { return 42 }\n')
subprocess.run(['git', '-C', str(source), 'add', 'go.mod', 'answer.go'], check=True)
subprocess.run(['git', '-C', str(source), 'commit', '-qm', 'Add provider'], check=True)
sha = subprocess.check_output(['git', '-C', str(source), 'rev-parse', 'HEAD'], text=True).strip()
(root / 'components.json').write_text(json.dumps({'schema': 1, 'components': [], 'transport': {
'go_private': ['example.test'], 'git_rewrites': [{'from': 'https://example.test/provider', 'to': source.as_uri()}]}}))
consumer = root / 'consumer'
consumer.mkdir()
(consumer / 'go.mod').write_text('module example.test/consumer\n\ngo 1.27\n')
env = transport.environment(root)
env['GOMODCACHE'] = str(root / 'module-cache')
before = subprocess.run(['git', 'config', '--global', '--list'], capture_output=True).stdout
result = subprocess.run(['go', 'get', 'example.test/provider.git@' + sha], cwd=consumer, env=env, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
(consumer / 'consumer_test.go').write_text('package consumer\nimport("testing"; "example.test/provider.git")\nfunc TestValue(t *testing.T) { if provider.Answer()!=42 { t.Fatal("wrong provider") } }\n')
result = subprocess.run(['go', 'test', '-mod=readonly', './...'], cwd=consumer, env=env, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
after = subprocess.run(['git', 'config', '--global', '--list'], capture_output=True).stdout
self.assertEqual(before, after)
self.assertNotIn('replace ', (consumer / 'go.mod').read_text())
def test_credentials_in_transport_url_are_rejected(self):
transport = self.load()
with tempfile.TemporaryDirectory() as td:
root = Path(td)
(root / 'components.json').write_text(json.dumps({'transport': {'git_rewrites': [
{'from': 'https://example.test/', 'to': 'https://secret-token@example.test/'}]}}))
with self.assertRaises(ValueError):
transport.environment(root)
def test_verified_local_mirror_mapping_survives_without_authentication(self):
transport = self.load()
with tempfile.TemporaryDirectory() as td:
root = Path(td)
mirror = root / 'mirrors'
mirror.mkdir()
target = mirror / 'Provider.git'
target.mkdir()
(root / 'components.json').write_text(json.dumps({'transport': {'git_rewrites': [
{'from':'https://example.test/', 'to':'ssh://git@example.test:31411/'}]}}))
inherited = {'CI_DEPENDENCY_MIRRORS':str(mirror), 'GIT_CONFIG_COUNT':'3',
'GIT_CONFIG_KEY_0':f'url.{target.as_uri()}.insteadOf','GIT_CONFIG_VALUE_0':'https://example.test/provider',
'GIT_CONFIG_KEY_1':'protocol.file.allow','GIT_CONFIG_VALUE_1':'always',
'GIT_CONFIG_KEY_2':'http.https://example.test/.extraheader','GIT_CONFIG_VALUE_2':'AUTHORIZATION: token fake-private'}
with patch.dict(os.environ,inherited):
env=transport.environment(root)
pairs=[(env[f'GIT_CONFIG_KEY_{i}'],env[f'GIT_CONFIG_VALUE_{i}']) for i in range(int(env['GIT_CONFIG_COUNT']))]
self.assertIn((f'url.{target.as_uri()}.insteadOf','https://example.test/provider'),pairs)
self.assertIn(('protocol.file.allow','always'),pairs)
self.assertFalse(any('extraheader' in key or 'fake-private' in value for key,value in pairs))
def test_injected_http_server_maps_to_verified_local_mirror_without_authentication(self):
transport = self.load()
with tempfile.TemporaryDirectory() as td:
root = Path(td)
mirrors = root / 'mirrors'
mirrors.mkdir()
target = mirrors / 'Provider.git'
subprocess.run(['git', 'init', '--bare', '-q', str(target)], check=True)
(root / 'components.json').write_text(json.dumps({'transport': {'git_rewrites': [
{'from': 'https://code.example/weichen/Provider', 'to': 'ssh://git@code.example:31411/weichen/Provider.git'}]}}))
origin = 'http://127.0.0.1:3000/weichen/Provider.git'
inherited = {'CI_DEPENDENCY_MIRRORS': str(mirrors), 'GITHUB_SERVER_URL': 'http://127.0.0.1:3000',
'GITHUB_REPOSITORY': 'weichen/Dev', 'GIT_CONFIG_COUNT': '3',
'GIT_CONFIG_KEY_0': f'url.{target.as_uri()}.insteadOf', 'GIT_CONFIG_VALUE_0': origin,
'GIT_CONFIG_KEY_1': 'protocol.file.allow', 'GIT_CONFIG_VALUE_1': 'always',
'GIT_CONFIG_KEY_2': 'http.http://127.0.0.1:3000/.extraheader',
'GIT_CONFIG_VALUE_2': 'AUTHORIZATION: token synthetic-private'}
before = subprocess.run(['git', 'config', '--global', '--list'], capture_output=True).stdout
with patch.dict(os.environ, inherited):
env = transport.environment(root)
pairs = [(env[f'GIT_CONFIG_KEY_{i}'], env[f'GIT_CONFIG_VALUE_{i}']) for i in range(int(env['GIT_CONFIG_COUNT']))]
self.assertIn((f'url.{target.as_uri()}.insteadOf', origin), pairs)
self.assertFalse(any('extraheader' in key or 'synthetic-private' in value for key, value in pairs))
cloned = subprocess.run(['git', 'clone', '--no-checkout', origin, str(root / 'cloned')], env=env, capture_output=True, text=True)
self.assertEqual(cloned.returncode, 0, cloned.stdout + cloned.stderr)
after = subprocess.run(['git', 'config', '--global', '--list'], capture_output=True).stdout
self.assertEqual(before, after)
def test_inherited_http_source_must_match_injected_server_owner_and_repository(self):
transport = self.load()
with tempfile.TemporaryDirectory() as td:
root = Path(td)
mirrors = root / 'mirrors'
mirrors.mkdir()
target = mirrors / 'Provider.git'
target.mkdir()
(root / 'components.json').write_text(json.dumps({'transport': {'git_rewrites': [
{'from': 'https://code.example/Provider', 'to': 'ssh://git@code.example:31411/Provider.git'}]}}))
inherited = {'CI_DEPENDENCY_MIRRORS': str(mirrors), 'GITHUB_SERVER_URL': 'http://127.0.0.1:3000',
'GITHUB_REPOSITORY': 'weichen/Dev', 'GIT_CONFIG_COUNT': '1',
'GIT_CONFIG_KEY_0': f'url.{target.as_uri()}.insteadOf'}
for source in ['http://foreign.test/weichen/Provider.git', 'http://127.0.0.1:3000/other/Provider.git',
'http://127.0.0.1:3000/weichen/Other.git', 'http://user:password@127.0.0.1:3000/weichen/Provider.git']:
with self.subTest(source=source), patch.dict(os.environ, inherited | {'GIT_CONFIG_VALUE_0': source}):
with self.assertRaises(ValueError):
transport.environment(root)
def test_ordinary_config_cannot_enable_http_transport(self):
transport = self.load()
with tempfile.TemporaryDirectory() as td:
root = Path(td)
(root / 'components.json').write_text(json.dumps({'transport': {'git_rewrites': [
{'from': 'http://127.0.0.1:3000/weichen/Provider.git', 'to': 'file:///tmp/mirrors/Provider.git'}]}}))
with self.assertRaises(ValueError):
transport.environment(root)
if __name__ == '__main__':
unittest.main()
+823
View File
@@ -0,0 +1,823 @@
"""Workspace behavior against disposable real Git repositories and Go modules."""
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
import zipfile
SOURCE = Path(__file__).resolve().parents[1]
class WorkspaceTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix="workspace-test-")
self.addCleanup(self.tmp.cleanup)
self.base = Path(self.tmp.name)
self.root = self.base / "project"
self.root.mkdir()
self.env = os.environ.copy()
for key in list(self.env):
if key.startswith("GIT_CONFIG_") or key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GOWORK"):
self.env.pop(key, None)
self.env.update({"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": "/dev/null",
"GIT_ALLOW_PROTOCOL": "file", "GIT_TERMINAL_PROMPT": "0",
"GIT_AUTHOR_NAME": "Workspace Test", "GIT_AUTHOR_EMAIL": "test@example.invalid",
"GIT_COMMITTER_NAME": "Workspace Test", "GIT_COMMITTER_EMAIL": "test@example.invalid",
"GOTOOLCHAIN": "local", "GOPROXY": "off", "GOSUMDB": "off"})
self.git(self.root, "init", "--initial-branch=main")
(self.root / ".gitignore").write_text(".workspace/\ngo.work\ngo.work.sum\n")
self.git(self.root, "add", ".gitignore")
self.git(self.root, "commit", "-m", "Create project")
self.remote, self.author, self.first = self.component("App")
self.manifest = {"schema": 1, "components": [self.entry("App", self.remote)]}
self.lock = {"schema": 1, "components": {"App": {"commit": self.first}}}
self.save_manifest()
def command(self, cwd, *args, check=True, env=None, separate_stderr=False):
result = subprocess.run(args, cwd=cwd, env=env or self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE if separate_stderr else subprocess.STDOUT, timeout=60)
if check:
self.assertEqual(result.returncode, 0, result.stdout + (result.stderr or ""))
return result
def git(self, cwd, *args, **kwargs):
return self.command(cwd, "git", *args, **kwargs)
def cli(self, *args, check=True, env=None):
return self.command(self.root, sys.executable, str(SOURCE / "scripts/workspace.py"),
"--root", str(self.root), *args, check=check, env=env)
def component(self, name, files=None):
remote = self.base / (name + ".git")
author = self.base / (name + "-author")
self.git(self.base, "init", "--bare", "--initial-branch=main", str(remote))
self.git(remote, "config", "gc.auto", "0")
self.git(remote, "config", "receive.autogc", "false")
self.git(self.base, "init", "--initial-branch=main", str(author))
for name, value in (files or {"content": "first\n", ".gitignore": "private/\n"}).items():
path = author / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(value)
self.git(author, "add", ".")
self.git(author, "commit", "-m", "First component")
self.git(author, "remote", "add", "origin", str(remote))
self.git(author, "push", "-u", "origin", "main")
return remote, author, self.git(author, "rev-parse", "HEAD").stdout.strip()
def entry(self, name, remote, role="application", modules=None):
return {"id": name, "repo": str(remote), "path": ".workspace/" + name,
"legacy_path": "modules/" + name, "role": role,
"modules": modules or [], "target": "main"}
def save_manifest(self):
(self.root / "components.json").write_text(json.dumps(self.manifest))
(self.root / "components.lock.json").write_text(json.dumps(self.lock))
def state(self):
return json.loads((self.root / ".workspace/state.json").read_text())
def test_prepare_pins_fixed_commit_and_reuses_without_advancing(self):
(self.author / "content").write_text("latest\n")
self.git(self.author, "commit", "-am", "Advance remote")
self.git(self.author, "push", "origin", "main")
self.cli("prepare")
clone = self.root / ".workspace/App"
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
self.assertEqual((clone / "content").read_text(), "first\n")
self.cli("prepare")
self.assertEqual(self.state()["components"]["App"]["baseline"], self.first)
self.assertTrue((self.root / ".workspace/resolved.json").is_file())
def test_edit_registers_branch_and_prepare_preserves_work(self):
self.cli("edit", "--name", "App", "--branch", "feature/change")
clone = self.root / ".workspace/App"
(clone / "content").write_text("my change\n")
self.git(clone, "commit", "-am", "Local work")
self.cli("prepare")
self.assertEqual(self.git(clone, "branch", "--show-current").stdout.strip(), "feature/change")
self.assertEqual((clone / "content").read_text(), "my change\n")
self.cli("unlink", "--name", "App")
self.assertTrue(clone.is_dir())
def test_rejects_unknown_component_and_bad_branch_without_source_creation(self):
result = self.cli("edit", "--name", "missing", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("unknown component", result.stdout.lower())
result = self.cli("edit", "--name", "App", "--branch", "feature/../../escape", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertFalse((self.root / ".workspace/App").exists())
def test_rejects_paths_and_symlink_escape(self):
for path in ("../escape", "/tmp/escape", ".workspace/../escape"):
with self.subTest(path=path):
self.manifest["components"][0]["path"] = path
self.save_manifest()
result = self.cli("prepare", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertFalse((self.base / "escape").exists())
self.manifest["components"][0]["path"] = ".workspace/App"
self.save_manifest()
(self.root / ".workspace").symlink_to(self.author, target_is_directory=True)
result = self.cli("prepare", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertFalse((self.author / "App").exists())
def test_rejects_mismatched_origin_dirty_and_git_lock_without_changes(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
self.git(clone, "remote", "set-url", "origin", str(self.base / "wrong.git"))
self.assertNotEqual(self.cli("prepare", check=False).returncode, 0)
self.git(clone, "remote", "set-url", "origin", str(self.remote))
(clone / "unique").write_text("unsaved\n")
self.assertNotEqual(self.cli("edit", "--name", "App", check=False).returncode, 0)
self.assertEqual((clone / "unique").read_text(), "unsaved\n")
(clone / "unique").unlink()
lock = clone / ".git/index.lock"
lock.write_text("owned by another operation\n")
self.assertNotEqual(self.cli("edit", "--name", "App", check=False).returncode, 0)
self.assertEqual(lock.read_text(), "owned by another operation\n")
def test_ignored_unique_files_and_shared_worktrees_block_branch_changes(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
(clone / "private").mkdir()
(clone / "private/key").write_text("unique\n")
self.assertNotEqual(self.cli("edit", "--name", "App", check=False).returncode, 0)
shutil.rmtree(clone / "private")
external = self.base / "other-owner"
self.git(clone, "worktree", "add", "-b", "feature/other", str(external))
result = self.cli("edit", "--name", "App", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.git(external, "branch", "--show-current").stdout.strip(), "feature/other")
def test_workspace_operation_lock_and_user_go_work_are_preserved(self):
area = self.root / ".workspace"
area.mkdir()
lock = area / ".operation.lock"
lock.write_text("other writer\n")
result = self.cli("prepare", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(lock.read_text(), "other writer\n")
lock.unlink()
(self.root / "go.work").write_text("go 1.27\n// manual\n")
result = self.cli("edit", "--name", "App", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((self.root / "go.work").read_text(), "go 1.27\n// manual\n")
def test_partial_initialization_is_retained_and_retry_is_safe(self):
self.lock["components"]["App"]["commit"] = "a" * 40
self.save_manifest()
result = self.cli("prepare", check=False)
self.assertNotEqual(result.returncode, 0)
clone = self.root / ".workspace/App"
self.assertTrue((clone / ".git").exists(), result.stdout)
self.assertFalse((self.root / ".workspace/.operation.lock").exists())
self.lock["components"]["App"]["commit"] = self.first
self.save_manifest()
self.cli("prepare")
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
def legacy(self):
self.git(self.root, "submodule", "add", str(self.remote), "modules/App")
self.git(self.root, "commit", "-am", "Pin legacy component")
return self.root / "modules/App"
def fingerprint(self, path):
files = {}
for file in path.rglob("*"):
if ".git" not in file.relative_to(path).parts and file.is_file():
files[str(file.relative_to(path))] = hashlib.sha256(file.read_bytes()).hexdigest()
return (self.git(path, "show-ref").stdout, self.git(path, "status", "--porcelain=v1").stdout, files)
def test_transition_report_is_readonly_apply_preserves_unique_commits_refs_and_files(self):
legacy = self.legacy()
self.git(legacy, "checkout", "-b", "feature/legacy")
(legacy / "content").write_text("unpublished commit\n")
self.git(legacy, "commit", "-am", "Unpublished")
unique_head = self.git(legacy, "rev-parse", "HEAD").stdout.strip()
self.git(legacy, "branch", "feature/other-ref")
(legacy / "content").write_text("uncommitted\n")
(legacy / "unique").write_text("untracked\n")
(legacy / "private").mkdir()
(legacy / "private/data").write_text("ignored\n")
before = self.fingerprint(legacy)
report = json.loads(self.cli("transition", "--name", "App").stdout)
self.assertEqual(report["components"]["App"]["legacy"]["head"], unique_head)
self.assertFalse((self.root / ".workspace").exists())
self.cli("transition", "--name", "App", "--apply")
clone = self.root / ".workspace/App"
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), unique_head)
self.assertEqual(self.git(clone, "remote", "get-url", "origin").stdout.strip(), str(self.remote))
self.assertEqual((clone / "content").read_text(), "uncommitted\n")
self.assertEqual((clone / "unique").read_text(), "untracked\n")
self.assertEqual((clone / "private/data").read_text(), "ignored\n")
self.assertEqual(self.git(clone, "rev-parse", "refs/migration/heads/feature/other-ref").stdout.strip(), unique_head)
self.assertEqual(self.fingerprint(legacy), before)
self.assertFalse((clone / ".git/objects/info/alternates").exists())
def test_transition_rejects_external_symlinks_preserving_both_locations(self):
legacy = self.legacy()
outside = self.base / "outside"
outside.write_text("owner data\n")
(legacy / "escape").symlink_to(outside)
before = self.fingerprint(legacy)
result = self.cli("transition", "--name", "App", "--apply", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(outside.read_text(), "owner data\n")
self.assertEqual(self.fingerprint(legacy), before)
self.assertFalse((self.root / ".workspace/App").exists())
def test_library_legacy_baseline_uses_gitlink_not_current_head(self):
legacy = self.legacy()
(legacy / "content").write_text("local newer commit\n")
self.git(legacy, "commit", "-am", "Local advance")
self.manifest["components"][0]["role"] = "go-library"
self.lock["components"] = {}
self.save_manifest()
self.cli("edit", "--name", "App")
self.assertEqual(self.git(self.root / ".workspace/App", "rev-parse", "HEAD").stdout.strip(), self.first)
self.assertEqual((legacy / "content").read_text(), "local newer commit\n")
def test_status_reports_dirty_and_verify_rejects_it(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
(clone / "content").write_text("unsaved\n")
result = json.loads(self.cli("status").stdout)
self.assertTrue(result["components"]["App"]["actual"]["dirty"])
self.assertNotEqual(self.cli("verify", check=False).returncode, 0)
self.assertEqual((clone / "content").read_text(), "unsaved\n")
def test_transition_preserves_staged_blob_internal_symlink_and_reflog_commit(self):
legacy = self.legacy()
(legacy / "content").write_text("orphan commit\n")
self.git(legacy, "commit", "-am", "Reflog-only work")
orphan = self.git(legacy, "rev-parse", "HEAD").stdout.strip()
self.git(legacy, "checkout", "--detach", self.first)
(legacy / "content").write_text("staged value\n")
self.git(legacy, "add", "content")
(legacy / "content").write_text("working value\n")
(legacy / "link").symlink_to("content")
before = self.fingerprint(legacy)
self.cli("transition", "--name", "App", "--apply")
clone = self.root / ".workspace/App"
self.assertEqual(self.git(clone, "show", ":content").stdout, "staged value\n")
self.assertEqual((clone / "content").read_text(), "working value\n")
self.assertTrue((clone / "link").is_symlink())
self.assertEqual(os.readlink(clone / "link"), "content")
self.assertEqual(self.git(clone, "rev-parse", "refs/migration/reflog/" + orphan).stdout.strip(), orphan)
self.assertEqual(self.fingerprint(legacy), before)
def test_transition_refuses_initialized_nested_repository(self):
legacy = self.legacy()
nested = legacy / "child"
nested.mkdir()
self.git(nested, "init", "--initial-branch=main")
(nested / "unique").write_text("owned by nested repository\n")
result = self.cli("transition", "--name", "App", "--apply", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("nested repository", result.stdout)
self.assertFalse((self.root / ".workspace/App").exists())
self.assertEqual((nested / "unique").read_text(), "owned by nested repository\n")
def nested_legacy(self):
legacy = self.legacy()
errors_remote, _, _ = self.component("Errors", {
"go.mod": "module example.invalid/errors\n\ngo 1.27\n",
"value.go": 'package errors\nfunc Value() string { return "errors" }\n',
".gitignore": "private/\n"})
logging_remote, _, _ = self.component("Logging", {
"go.mod": "module example.invalid/logging\n\ngo 1.27\n",
"value.go": 'package logging\nfunc Value() string { return "logging" }\n'})
self.git(legacy, "submodule", "add", str(errors_remote), "deps/errors")
self.git(legacy, "submodule", "add", str(logging_remote), "deps/logging")
(legacy / "go.mod").write_text("module example.invalid/native\n\ngo 1.27\n\nrequire (\n example.invalid/errors v0.0.0\n example.invalid/logging v0.0.0\n)\nreplace example.invalid/errors => ./deps/errors\nreplace example.invalid/logging => ./deps/logging\n")
(legacy / "main.go").write_text('package main\nimport ("fmt"; errors "example.invalid/errors"; logging "example.invalid/logging")\nfunc main() { fmt.Print(errors.Value()+":"+logging.Value()) }\n')
self.git(legacy, "add", ".")
self.git(legacy, "commit", "-m", "Add registered nested dependencies")
self.manifest["components"][0]["modules"] = [{"path": "example.invalid/native", "directory": "."}]
self.manifest["components"] += [
self.entry("Errors", errors_remote, "go-library", [{"path": "example.invalid/errors", "directory": "."}]),
self.entry("Logging", logging_remote, "go-library", [{"path": "example.invalid/logging", "directory": "."}])]
self.save_manifest()
return legacy, legacy / "deps/errors", legacy / "deps/logging"
def test_transition_known_nested_repositories_preserves_external_worktree_and_runs_go(self):
legacy, errors, logging = self.nested_legacy()
self.git(errors, "checkout", "-b", "feature/nested")
(errors / "value.go").write_text('package errors\nfunc Value() string { return "committed" }\n')
self.git(errors, "commit", "-am", "Unpushed nested commit")
errors_head = self.git(errors, "rev-parse", "HEAD").stdout.strip()
(errors / "value.go").write_text('package errors\nfunc Value() string { return "staged" }\n')
self.git(errors, "add", "value.go")
(errors / "value.go").write_text('package errors\nfunc Value() string { return "working" }\n')
(errors / "private").mkdir()
(errors / "private/unique").write_text("nested ignored data\n")
external = self.base / "nested-dependent-worktree"
self.git(errors, "worktree", "add", "-b", "feature/external", str(external))
before = {str(path): self.fingerprint(path) for path in (legacy, errors, logging, external)}
self.cli("transition", "--name", "App", "--apply")
clone = self.root / ".workspace/Errors"
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), errors_head)
self.assertIn('return "staged"', self.git(clone, "show", ":value.go").stdout)
self.assertEqual((clone / "private/unique").read_text(), "nested ignored data\n")
self.assertTrue((clone / ".git").is_dir())
self.assertFalse((clone / ".git/objects/info/alternates").exists())
self.assertFalse((self.root / ".workspace/App/deps/errors/.git").exists())
self.assertEqual(self.command(self.root / ".workspace/App", "go", "run", ".").stdout, "working:logging")
for path in (legacy, errors, logging, external):
self.assertEqual(self.fingerprint(path), before[str(path)])
self.assertTrue(Path(self.git(external, "rev-parse", "--absolute-git-dir").stdout.strip()).exists())
mapping = self.state()["components"]["App"]["source"]["nested_components"]
self.assertEqual({item["id"] for item in mapping}, {"Errors", "Logging"})
def test_conflicting_duplicate_nested_component_stops_before_cloning(self):
legacy, errors, logging = self.nested_legacy()
remote = next(item["repo"] for item in self.manifest["components"] if item["id"] == "Errors")
self.git(legacy, "submodule", "add", remote, "deps/duplicate-errors")
self.git(legacy, "commit", "-am", "Add duplicate logical dependency")
duplicate = legacy / "deps/duplicate-errors"
(duplicate / "value.go").write_text("conflicting unsaved source\n")
before = self.fingerprint(legacy)
result = self.cli("transition", "--name", "App", "--apply", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("conflicting duplicate", result.stdout)
self.assertIn("Errors", result.stdout)
self.assertFalse((self.root / ".workspace/App").exists())
self.assertFalse((self.root / ".workspace/Errors").exists())
self.assertEqual(self.fingerprint(legacy), before)
def test_partial_nested_transition_keeps_originals_and_owner_recovery_state(self):
legacy, errors, logging = self.nested_legacy()
self.git(errors, "checkout", "-b", "feature/partial-source")
(errors / "value.go").write_text('package errors\nfunc Value() string { return "staged" }\n')
self.git(errors, "add", "value.go")
(errors / "value.go").write_text('package errors\nfunc Value() string { return "working" }\n')
(errors / "private").mkdir()
(errors / "private/unique").write_text("ignored owner data")
external = self.base / "external-owner"
self.git(errors, "worktree", "add", "--detach", str(external), "HEAD")
originals = {str(path): self.fingerprint(path) for path in (legacy, errors, logging, external)}
metadata = {str(path): self.git_metadata(path) for path in (legacy, errors, logging)}
sys.path.insert(0, str(SOURCE / "scripts"))
self.addCleanup(sys.path.remove, str(SOURCE / "scripts"))
from workspace.cli import main
copy = shutil.copy2
copied = []
def interrupt(source, destination, *args, **kwargs):
result = copy(source, destination, *args, **kwargs)
if Path(source).is_relative_to(logging):
copied.append(str(source))
raise OSError("injected partial nested copy failure")
return result
with patch.dict(os.environ, self.env, clear=True), patch("workspace.transition.shutil.copy2", side_effect=interrupt):
result = main(["--root", str(self.root), "transition", "--name", "App", "--apply"])
self.assertNotEqual(result, 0)
self.assertTrue(copied)
state = self.state()["components"]
self.assertEqual(state["App"]["mode"], "editable")
# Errors has not been copied when Logging fails; its complete original
# index/worktree/ignored data is still the owner's recovery source.
self.assertIn("staged", self.git(errors, "show", ":value.go").stdout)
self.assertIn("working", (errors / "value.go").read_text())
self.assertEqual((errors / "private/unique").read_text(), "ignored owner data")
partial = self.root / ".workspace/Logging"
self.assertTrue((partial / ".git").is_dir())
self.assertIn("partial nested copy", state["Logging"]["error"])
self.assertNotEqual(self.cli("transition", "--name", "App", "--apply", check=False).returncode, 0)
self.assertTrue(partial.exists(), "owner recovery data must not be removed on retry")
for path in (legacy, errors, logging, external):
self.assertEqual(self.fingerprint(path), originals[str(path)])
for path in (legacy, errors, logging):
self.assertEqual(self.git_metadata(path), metadata[str(path)])
def test_equivalent_recursive_nested_sources_share_an_independent_component(self):
legacy, errors, logging = self.nested_legacy()
remote = next(item["repo"] for item in self.manifest["components"] if item["id"] == "Errors")
self.git(logging, "submodule", "add", remote, "deps/errors")
(logging / "go.mod").write_text("module example.invalid/logging\n\ngo 1.27\n\nrequire example.invalid/errors v0.0.0\nreplace example.invalid/errors => ./deps/errors\n")
(logging / "value.go").write_text('package logging\nimport errors "example.invalid/errors"\nfunc Value() string { return "logging:"+errors.Value() }\n')
self.git(logging, "add", ".")
self.git(logging, "commit", "-m", "Add recursive registered dependency")
self.git(legacy, "add", "deps/logging")
self.git(legacy, "commit", "-m", "Pin recursive logging candidate")
duplicate = logging / "deps/errors"
before = {str(path): self.fingerprint(path) for path in (legacy, errors, logging, duplicate)}
self.cli("transition", "--name", "App", "--apply")
self.assertEqual(len(self.state()["components"]["Errors"]["legacy_snapshots"]), 2)
self.assertEqual(self.command(self.root / ".workspace/App", "go", "run", ".").stdout, "errors:logging:errors")
for path in (legacy, errors, logging, duplicate):
self.assertEqual(self.fingerprint(path), before[str(path)])
def git_metadata(self, path):
common = Path(self.git(path, "rev-parse", "--path-format=absolute", "--git-common-dir").stdout.strip())
return {str(file.relative_to(common)): hashlib.sha256(file.read_bytes()).hexdigest()
for file in common.rglob("*") if file.is_file()}
def preserved_source_refs(self, path):
output = self.git(path, "for-each-ref", "--format=%(refname) %(objectname)", "refs/migration/sources/").stdout
return dict(row.split() for row in output.splitlines())
def test_repeated_transition_keeps_alias_tag_versions_and_snapshot_history(self):
legacy, errors, logging = self.nested_legacy()
remote = next(item["repo"] for item in self.manifest["components"] if item["id"] == "Errors")
self.git(logging, "submodule", "add", remote, "deps/errors")
self.git(logging, "commit", "-am", "Add equivalent dependency alias")
self.git(legacy, "add", "deps/logging")
self.git(legacy, "commit", "-m", "Pin alias source")
alias = logging / "deps/errors"
self.git(alias, "tag", "-a", "alias-note", "-m", "First unique alias annotation")
first_tag = self.git(alias, "rev-parse", "refs/tags/alias-note").stdout.strip()
self.cli("transition", "--name", "App", "--apply")
clone = self.root / ".workspace/Errors"
saved_refs = self.preserved_source_refs(clone)
self.assertIn(first_tag, saved_refs.values())
saved_snapshots = self.state()["components"]["Errors"]["legacy_snapshots"]
self.git(alias, "tag", "-f", "-a", "alias-note", "-m", "Second unique alias annotation")
second_tag = self.git(alias, "rev-parse", "refs/tags/alias-note").stdout.strip()
self.assertNotEqual(first_tag, second_tag)
original = {str(path): self.git_metadata(path) for path in (legacy, errors, logging, alias)}
self.cli("transition", "--name", "App", "--apply")
current_refs = self.preserved_source_refs(clone)
self.assertEqual({name: current_refs.get(name) for name in saved_refs}, saved_refs,
"a new source snapshot must not overwrite any previous preserved reference")
self.assertIn(second_tag, current_refs.values())
history = self.state()["components"]["Errors"]["legacy_snapshots"]
for snapshot in saved_snapshots:
self.assertIn(snapshot, history, "prior captured metadata must remain available")
# This object was present only through the secondary alias's annotated tag.
# Disposable GC proves it remains reachable after the source tag moves.
self.git(clone, "reflog", "expire", "--expire=now", "--all")
self.git(clone, "gc", "--prune=now")
self.assertEqual(self.git(clone, "cat-file", "-t", first_tag).stdout.strip(), "tag")
self.assertEqual(self.git(clone, "cat-file", "-t", second_tag).stdout.strip(), "tag")
for path in (legacy, errors, logging, alias):
self.assertEqual(self.git_metadata(path), original[str(path)])
def test_repeated_transition_preserves_dirty_index_ignored_and_external_git_metadata(self):
legacy = self.legacy()
self.git(legacy, "checkout", "-b", "feature/legacy")
(legacy / "content").write_text("staged source\n")
self.git(legacy, "add", "content")
(legacy / "content").write_text("working source\n")
(legacy / "private").mkdir()
(legacy / "private/unique").write_text("ignored source\n")
external = self.base / "external-owner"
self.git(legacy, "worktree", "add", "-b", "feature/external", str(external))
(external / "content").write_text("external owner work\n")
metadata = self.git_metadata(legacy)
self.cli("transition", "--name", "App", "--apply")
clone = self.root / ".workspace/App"
refs = self.preserved_source_refs(clone)
self.cli("transition", "--name", "App", "--apply")
self.assertEqual(self.git(clone, "show", ":content").stdout, "staged source\n")
self.assertEqual((clone / "content").read_text(), "working source\n")
self.assertEqual((clone / "private/unique").read_text(), "ignored source\n")
self.assertEqual((external / "content").read_text(), "external owner work\n")
self.assertEqual(self.git_metadata(legacy), metadata)
self.assertEqual(self.preserved_source_refs(clone), refs)
# A unique destination file belongs to its current owner and blocks reuse.
(clone / "private/new-owner-file").write_text("new workspace work\n")
state_before = (self.root / ".workspace/state.json").read_bytes()
result = self.cli("transition", "--name", "App", "--apply", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("conflicting duplicate destination", result.stdout)
self.assertEqual((clone / "private/new-owner-file").read_text(), "new workspace work\n")
self.assertEqual((self.root / ".workspace/state.json").read_bytes(), state_before)
self.assertEqual(self.git_metadata(legacy), metadata)
def test_repeated_transition_rejects_a_new_source_head_without_switching_destination(self):
legacy = self.legacy()
self.cli("transition", "--name", "App", "--apply")
clone = self.root / ".workspace/App"
refs = self.preserved_source_refs(clone)
state_before = (self.root / ".workspace/state.json").read_bytes()
(legacy / "content").write_text("new legacy head\n")
self.git(legacy, "commit", "-am", "Advance original owner branch")
new_head = self.git(legacy, "rev-parse", "HEAD").stdout.strip()
original_metadata = self.git_metadata(legacy)
result = self.cli("transition", "--name", "App", "--apply", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("conflicting duplicate destination", result.stdout)
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
self.assertEqual(self.git(legacy, "rev-parse", "HEAD").stdout.strip(), new_head)
self.assertEqual(self.preserved_source_refs(clone), refs)
self.assertEqual((self.root / ".workspace/state.json").read_bytes(), state_before)
self.assertEqual(self.git_metadata(legacy), original_metadata)
def npm_fixture(self):
remote, author, head = self.component("UI", {"package.json": '{"name":"@agent-system/ui","version":"0.0.0"}\n'})
(author / "package.json").write_text('{"name":"@agent-system/ui","version":"0.0.1"}\n')
self.git(author, "commit", "-am", "Advance UI remote beyond consumer lock")
self.git(author, "push", "origin", "main")
(self.author / "package-lock.json").write_text(json.dumps({"name": "frontend", "lockfileVersion": 3,
"packages": {"": {"dependencies": {"@agent-system/ui": "git+file://" + str(remote) + "#" + head}},
"node_modules/@agent-system/ui": {"version": "0.0.0", "resolved": "git+file://" + str(remote) + "#" + head}}}))
self.git(self.author, "add", "package-lock.json")
self.git(self.author, "commit", "-m", "Lock UI package")
self.git(self.author, "push", "origin", "main")
self.lock["components"]["App"]["commit"] = self.git(self.author, "rev-parse", "HEAD").stdout.strip()
entry = self.entry("UI", remote, "npm-library")
entry["package_name"] = "@agent-system/ui"
self.manifest["components"].append(entry)
self.save_manifest()
return remote, author, head
def test_npm_library_edit_uses_consumer_lock_git_sha_not_remote_main(self):
_, _, head = self.npm_fixture()
self.cli("edit", "--name", "UI")
clone = self.root / ".workspace/UI"
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), head)
self.assertEqual(json.loads((clone / "package.json").read_text())["version"], "0.0.0")
self.assertEqual(self.state()["components"]["UI"]["source"]["kind"], "npm-lock")
self.assertFalse((self.root / "go.work").exists())
def test_npm_library_rejects_wrong_lock_origin_and_competing_manual_lock(self):
remote, _, head = self.npm_fixture()
self.lock["components"]["UI"] = {"commit": head}
self.save_manifest()
result = self.cli("edit", "--name", "UI", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("competing", result.stdout)
self.lock["components"].pop("UI")
self.save_manifest()
self.cli("prepare")
lock_path = self.root / ".workspace/App/package-lock.json"
document = json.loads(lock_path.read_text())
document["packages"]["node_modules/@agent-system/ui"]["resolved"] = "git+file://" + str(self.base / "unapproved.git") + "#" + head
lock_path.write_text(json.dumps(document))
result = self.cli("edit", "--name", "UI", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("origin", result.stdout)
self.assertFalse((self.root / ".workspace/UI").exists())
def test_unlink_refuses_manual_overlay_changes_without_changing_registration(self):
self.go_fixture()
self.cli("prepare")
consumer = self.root / ".workspace/Consumer"
self.command(consumer, "go", "mod", "download", "all", env=self.env | {"GOWORK": "off"})
self.cli("edit", "--name", "Native")
overlay = self.root / "go.work"
overlay.write_text(overlay.read_text() + "// manual edit\n")
result = self.cli("unlink", "--name", "Native", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertTrue(self.state()["components"]["Native"]["linked"])
self.assertTrue(overlay.read_text().endswith("// manual edit\n"))
def test_missing_fixed_source_never_falls_back_to_remote_main(self):
self.lock["components"] = {}
self.save_manifest()
result = self.cli("prepare", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertFalse((self.root / ".workspace/App").exists())
def test_existing_fixed_checkout_is_not_switched_after_lock_change(self):
self.cli("prepare")
(self.author / "content").write_text("advanced version\n")
self.git(self.author, "commit", "-am", "Advance")
self.git(self.author, "push", "origin", "main")
self.lock["components"]["App"]["commit"] = self.git(self.author, "rev-parse", "HEAD").stdout.strip()
self.save_manifest()
result = self.cli("prepare", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.git(self.root / ".workspace/App", "rev-parse", "HEAD").stdout.strip(), self.first)
def advance_app_lock(self):
(self.author / "content").write_text("updated locked version\n")
counter = self.author / "revision-counter"
counter.write_text((counter.read_text() if counter.exists() else "") + "next\n")
self.git(self.author, "add", "revision-counter")
self.git(self.author, "commit", "-am", "Advance fixed source")
self.git(self.author, "push", "origin", "main")
head = self.git(self.author, "rev-parse", "HEAD").stdout.strip()
self.lock["components"]["App"]["commit"] = head
self.save_manifest()
return head
def test_explicit_fixed_refresh_preserves_old_commit_refs_and_reflog(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
refs_before = self.git(clone, "for-each-ref", "--format=%(refname) %(objectname)").stdout.splitlines()
reflog_before = self.git(clone, "reflog", "--format=%H", "HEAD").stdout.splitlines()
head = self.advance_app_lock()
self.cli("prepare", "--name", "App", "--refresh-fixed")
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), head)
self.assertEqual(self.git(clone, "branch", "--show-current").stdout, "")
self.assertEqual((clone / "content").read_text(), "updated locked version\n")
self.assertEqual(self.state()["components"]["App"]["baseline"], head)
for row in refs_before:
self.assertIn(row, self.git(clone, "for-each-ref", "--format=%(refname) %(objectname)").stdout.splitlines())
for row in reflog_before:
self.assertIn(row, self.git(clone, "reflog", "--format=%H", "HEAD").stdout.splitlines())
history = self.state()["components"]["App"]["previous_sources"]
self.assertEqual(history[0]["commit"], self.first)
self.assertEqual(self.git(clone, "rev-parse", history[0]["ref"]).stdout.strip(), self.first)
self.cli("prepare", "--name", "App", "--refresh-fixed")
self.assertEqual(len(self.state()["components"]["App"]["previous_sources"]), 1)
def test_explicit_fixed_refresh_refuses_dirty_ignored_edited_and_locked_sources(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
self.advance_app_lock()
state_before = (self.root / ".workspace/state.json").read_bytes()
(clone / "content").write_text("unsaved owner change\n")
self.assertNotEqual(self.cli("prepare", "--refresh-fixed", check=False).returncode, 0)
self.assertEqual((clone / "content").read_text(), "unsaved owner change\n")
(clone / "content").write_text("first\n")
(clone / "private").mkdir()
(clone / "private/unique").write_text("ignored owner data\n")
self.assertNotEqual(self.cli("prepare", "--refresh-fixed", check=False).returncode, 0)
self.assertEqual((clone / "private/unique").read_text(), "ignored owner data\n")
(clone / "private/unique").unlink()
(clone / "private").rmdir()
lock = clone / ".git/index.lock"
lock.write_text("another Git owner\n")
self.assertNotEqual(self.cli("prepare", "--refresh-fixed", check=False).returncode, 0)
self.assertEqual(lock.read_text(), "another Git owner\n")
lock.unlink()
self.assertEqual((self.root / ".workspace/state.json").read_bytes(), state_before)
self.lock["components"]["App"]["commit"] = self.first
self.save_manifest()
self.cli("edit", "--name", "App", "--branch", "feature/owner")
self.advance_app_lock()
result = self.cli("prepare", "--refresh-fixed", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.git(clone, "branch", "--show-current").stdout.strip(), "feature/owner")
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
def test_explicit_fixed_refresh_refuses_shared_or_unregistered_ownership(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
self.advance_app_lock()
external = self.base / "fixed-external-owner"
self.git(clone, "worktree", "add", "-b", "feature/external", str(external))
(external / "unique").write_text("external owner data\n")
result = self.cli("prepare", "--refresh-fixed", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
self.assertEqual((external / "unique").read_text(), "external owner data\n")
(external / "unique").unlink()
self.git(clone, "worktree", "remove", str(external))
data = self.state()
data["components"].pop("App")
(self.root / ".workspace/state.json").write_text(json.dumps(data))
result = self.cli("prepare", "--refresh-fixed", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
def test_failed_explicit_fixed_refresh_keeps_old_binding_and_can_retry(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
self.lock["components"]["App"]["commit"] = "a" * 40
self.save_manifest()
result = self.cli("prepare", "--refresh-fixed", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.state()["components"]["App"]["baseline"], self.first)
self.assertEqual(self.state()["components"]["App"]["mode"], "versioned")
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
head = self.advance_app_lock()
self.cli("prepare", "--refresh-fixed")
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), head)
def test_explicit_fixed_refresh_refuses_index_flags_hiding_owner_changes(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
self.git(clone, "update-index", "--skip-worktree", "content")
(clone / "content").write_text("hidden owner change\n")
self.advance_app_lock()
result = self.cli("prepare", "--refresh-fixed", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.git(clone, "rev-parse", "HEAD").stdout.strip(), self.first)
self.assertEqual((clone / "content").read_text(), "hidden owner change\n")
def test_private_git_metadata_symlink_is_not_adopted(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
external = self.base / "other-owner-metadata"
(clone / ".git").rename(external)
(clone / ".git").symlink_to(external, target_is_directory=True)
result = self.cli("edit", "--name", "App", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertFalse((external / "refs/heads/feature").exists())
def test_git_operation_is_reported_and_not_removed(self):
self.cli("prepare")
operation = self.root / ".workspace/App/.git/MERGE_HEAD"
operation.write_text(self.first + "\n")
result = self.cli("edit", "--name", "App", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(operation.read_text(), self.first + "\n")
def test_status_does_not_expose_credentials_from_an_unmatched_actual_origin(self):
self.cli("prepare")
clone = self.root / ".workspace/App"
self.git(clone, "remote", "set-url", "origin", "https://owner:workspace-fake-secret@example.invalid/repo.git")
result = self.cli("status")
self.assertNotIn("workspace-fake-secret", result.stdout)
self.assertIn("redacted", result.stdout)
def test_transition_preserves_committed_symlink_deletion_and_file_type_change(self):
legacy = self.legacy()
(legacy / "old-link").symlink_to("content")
(legacy / "regular").write_text("committed file\n")
self.git(legacy, "add", "old-link", "regular")
self.git(legacy, "commit", "-m", "Add two file types")
(legacy / "old-link").unlink()
(legacy / "regular").unlink()
(legacy / "regular").symlink_to("content")
before = self.fingerprint(legacy)
self.cli("transition", "--name", "App", "--apply")
clone = self.root / ".workspace/App"
self.assertFalse((clone / "old-link").is_symlink())
self.assertTrue((clone / "regular").is_symlink())
self.assertEqual(self.git(clone, "diff", "--raw").stdout, self.git(legacy, "diff", "--raw").stdout)
self.assertEqual(self.fingerprint(legacy), before)
def go_fixture(self):
module = "example.invalid/Native"
library_remote, author, head = self.component("Native", {
"go.mod": "module example.invalid/Native\n\ngo 1.27\n",
"value.go": 'package native\nfunc Value() string { return "versioned" }\n'})
version = "v0.0.0-20261013000000-" + head[:12]
proxy = self.base / "proxy/example.invalid/!native/@v"
proxy.mkdir(parents=True)
(proxy / (version + ".mod")).write_text((author / "go.mod").read_text())
# Go's real proxy protocol supplies Origin.Hash, avoiding a fake Go executable.
(proxy / (version + ".info")).write_text(json.dumps({"Version": version,
"Time": "2026-10-13T00:00:00Z", "Origin": {"VCS": "git", "URL": str(library_remote), "Hash": head}}))
with zipfile.ZipFile(proxy / (version + ".zip"), "w") as archive:
for filename in ("go.mod", "value.go"):
archive.write(author / filename, module + "@" + version + "/" + filename)
app_remote, _, app_head = self.component("Consumer", {
"go.mod": "module example.invalid/consumer\n\ngo 1.27\n\nrequire " + module + " " + version + "\n",
"main.go": 'package main\nimport ("fmt"; native "example.invalid/Native")\nfunc main() { fmt.Print(native.Value()) }\n'})
self.manifest["components"] = [self.entry("Consumer", app_remote, modules=[{"path": "example.invalid/consumer", "directory": "."}]),
self.entry("Native", library_remote, "go-library", [{"path": module, "legacy": "native", "directory": "."}])]
self.lock["components"] = {"Consumer": {"commit": app_head}}
self.save_manifest()
self.env.update({"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "modcache"),
"GOPATH": str(self.base / "gopath")})
return head
def test_actual_go_version_baseline_overlay_and_unlink_restore_version_consumption(self):
head = self.go_fixture()
self.cli("prepare")
consumer = self.root / ".workspace/Consumer"
native = self.root / ".workspace/Native"
self.command(consumer, "go", "mod", "download", "all", env=self.env | {"GOWORK": "off"})
self.assertEqual(self.command(consumer, "go", "run", "-mod=readonly", ".", env=self.env | {"GOWORK": "off"}).stdout, "versioned")
self.cli("edit", "--name", "Native", "--branch", "feature/value")
self.assertEqual(self.git(native, "rev-parse", "HEAD").stdout.strip(), head)
(native / "value.go").write_text('package native\nfunc Value() string { return "edited" }\n')
self.assertEqual(self.command(consumer, "go", "run", ".").stdout, "edited")
self.assertEqual(self.command(consumer, "go", "run", "-mod=readonly", ".", env=self.env | {"GOWORK": "off"}).stdout, "versioned")
self.cli("unlink", "--name", "Native")
self.assertFalse((self.root / "go.work").exists())
self.assertEqual(self.command(consumer, "go", "run", "-mod=readonly", ".").stdout, "versioned")
self.assertEqual((native / "value.go").read_text(), 'package native\nfunc Value() string { return "edited" }\n')
def test_fresh_library_edit_resolves_fixed_consumer_and_preserves_consumer_manifest(self):
head = self.go_fixture()
self.cli("edit", "--name", "Native")
native = self.root / ".workspace/Native"
consumer = self.root / ".workspace/Consumer"
self.assertEqual(self.git(native, "rev-parse", "HEAD").stdout.strip(), head)
self.assertFalse((consumer / "go.sum").exists(), "source resolution must not write a consumer's go.sum")
self.assertEqual(self.git(consumer, "status", "--porcelain=v1").stdout, "")
def test_library_baseline_resolves_transitive_selected_module(self):
head = self.go_fixture()
native_version = "v0.0.0-20261013000000-" + head[:12]
bridge_remote, bridge_author, bridge_head = self.component("Bridge", {
"go.mod": "module example.invalid/Bridge\n\ngo 1.27\n\nrequire example.invalid/Native " + native_version + "\n",
"value.go": 'package bridge\nimport native "example.invalid/Native"\nfunc Value() string { return native.Value() }\n'})
bridge_version = "v0.0.0-20261013000000-" + bridge_head[:12]
proxy = self.base / "proxy/example.invalid/!bridge/@v"
proxy.mkdir(parents=True)
(proxy / (bridge_version + ".mod")).write_text((bridge_author / "go.mod").read_text())
(proxy / (bridge_version + ".info")).write_text(json.dumps({"Version": bridge_version,
"Time": "2026-10-13T00:00:00Z", "Origin": {"VCS": "git", "URL": str(bridge_remote), "Hash": bridge_head}}))
with zipfile.ZipFile(proxy / (bridge_version + ".zip"), "w") as archive:
for filename in ("go.mod", "value.go"):
archive.write(bridge_author / filename, "example.invalid/Bridge@" + bridge_version + "/" + filename)
author = self.base / "Consumer-author"
(author / "go.mod").write_text("module example.invalid/consumer\n\ngo 1.27\n\nrequire example.invalid/Bridge " + bridge_version + "\n")
(author / "main.go").write_text('package main\nimport ("fmt"; bridge "example.invalid/Bridge")\nfunc main() { fmt.Print(bridge.Value()) }\n')
self.git(author, "commit", "-am", "Consume Native transitively")
self.git(author, "push", "origin", "main")
self.lock["components"]["Consumer"]["commit"] = self.git(author, "rev-parse", "HEAD").stdout.strip()
self.save_manifest()
self.cli("edit", "--name", "Native")
self.assertEqual(self.git(self.root / ".workspace/Native", "rev-parse", "HEAD").stdout.strip(), head)
self.assertFalse((self.root / ".workspace/Consumer/go.sum").exists())
(self.root / ".workspace/Native/value.go").write_text('package native\nfunc Value() string { return "transitive edit" }\n')
self.assertEqual(self.command(self.root / ".workspace/Consumer", "go", "run", ".", separate_stderr=True).stdout, "transitive edit")
if __name__ == "__main__":
unittest.main()
+403
View File
@@ -0,0 +1,403 @@
"""Task cutover protections with real repositories, gitlinks and worktrees."""
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
SOURCE = Path(__file__).resolve().parents[1]
class WorkspaceCutoverTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix="workspace-cutover-")
self.addCleanup(self.tmp.cleanup)
self.base = Path(self.tmp.name)
self.repo = self.base / "repo"
self.env = os.environ.copy()
for key in list(self.env):
if key.startswith("GIT_CONFIG_") or key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GOWORK", "PRECHECK_RECEIPT", "RECEIPT", "CMD", "IMPORT_RECEIPT", "COMPOSITION_SOURCE_MAP", "CI_DEPENDENCY_MIRRORS"):
self.env.pop(key, None)
self.env.update({"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": "/dev/null",
"GIT_ALLOW_PROTOCOL": "file", "GIT_TERMINAL_PROMPT": "0",
"GIT_AUTHOR_NAME": "Cutover Test", "GIT_AUTHOR_EMAIL": "test@example.invalid",
"GIT_COMMITTER_NAME": "Cutover Test", "GIT_COMMITTER_EMAIL": "test@example.invalid",
"XDG_CACHE_HOME": str(self.base / "cache")})
self.call(self.base, "git", "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 / ".gitignore").write_text(".workspace/\ngo.work\ngo.work.sum\nmodules/\n")
(self.repo / "tracked").write_text("root\n")
self.git(self.repo, "add", ".")
self.git(self.repo, "commit", "-m", "Old project")
self.remote = self.base / "project.git"
self.call(self.base, "git", "init", "--bare", "--initial-branch=main", str(self.remote))
self.git(self.remote, "config", "gc.auto", "0")
self.git(self.remote, "config", "receive.autogc", "false")
self.git(self.repo, "remote", "add", "origin", str(self.remote))
self.git(self.repo, "push", "-u", "origin", "main")
self.app, self.app_sha = self.component("App", {"content": "app v1\n", ".gitignore": "private/\n"})
self.rules, self.rules_sha = self.component("Rules", {"core/baseline.md": "# Baseline\n", "workflows/delivery.md": "# Delivery\n"})
def call(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=60)
if check:
self.assertEqual(result.returncode, 0, result.stdout)
return result
def git(self, cwd, *args, **kwargs):
return self.call(cwd, "git", *args, **kwargs)
def task(self, cwd, command, *args, **kwargs):
return self.call(cwd, "bash", str(cwd / "scripts/task.sh"), command, *args, **kwargs)
def component(self, name, files):
path = self.base / name
self.call(self.base, "git", "init", "--initial-branch=main", str(path))
for relative, content in files.items():
target = path / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content)
self.git(path, "add", ".")
self.git(path, "commit", "-m", name + " v1")
return path, self.git(path, "rev-parse", "HEAD").stdout.strip()
def adopt(self):
records = [{"id": name, "repo": str(path), "path": ".workspace/" + name,
"legacy_path": "modules/" + name, "role": role, "modules": [], "target": "main"}
for name, path, role in (("App", self.app, "application"), ("Rules", self.rules, "guidelines"))]
(self.repo / "components.json").write_text(json.dumps({"schema": 1, "components": records}))
(self.repo / "components.lock.json").write_text(json.dumps({"schema": 1, "components": {"App": {"commit": self.app_sha}, "Rules": {"commit": self.rules_sha}}}))
self.git(self.repo, "add", "components.json", "components.lock.json")
self.git(self.repo, "commit", "-m", "Adopt source manifests")
self.git(self.repo, "push", "origin", "main")
def worktree(self, name="feature/old"):
path = self.base / "task"
self.git(self.repo, "worktree", "add", "-b", name, str(path), "origin/main")
return path
def workspace(self, root, *args):
return self.call(root, "python3", "scripts/workspace.py", "--root", str(root), *args)
def test_precheck_prepares_fixed_rules_outside_project_before_ready(self):
self.adopt()
result = self.task(self.repo, "precheck")
self.assertIn("[verdict] READY", result.stdout)
self.assertFalse((self.repo / ".workspace").exists())
self.assertIn("[rules]", result.stdout)
reading = next(line for line in result.stdout.splitlines() if line.startswith("[rules]"))
self.assertIn(self.rules_sha, reading)
self.assertTrue(list((self.base / "cache").rglob("core/baseline.md")))
self.assertTrue(list((self.base / "cache").rglob("workflows/delivery.md")))
def test_precheck_never_claims_ready_for_unavailable_fixed_rules(self):
self.adopt()
(self.rules / "core/baseline.md").unlink()
self.git(self.rules, "commit", "-am", "Rules missing baseline")
document = json.loads((self.repo / "components.lock.json").read_text())
document["components"]["Rules"]["commit"] = self.git(self.rules, "rev-parse", "HEAD").stdout.strip()
(self.repo / "components.lock.json").write_text(json.dumps(document))
self.git(self.repo, "commit", "-am", "Pin invalid rules")
self.git(self.repo, "push", "origin", "main")
result = self.task(self.repo, "precheck", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertNotIn("[verdict] READY", result.stdout)
self.assertFalse((self.repo / ".workspace").exists())
def test_start_prepares_fixed_sources_without_recursive_submodule_init(self):
nested, _ = self.component("Nested", {"value": "nested\n"})
self.git(self.app, "submodule", "add", str(nested), "deps/Nested")
self.git(self.app, "commit", "-am", "Pin nested source")
self.app_sha = self.git(self.app, "rev-parse", "HEAD").stdout.strip()
self.adopt()
receipt = self.base / "precheck.json"
self.call(self.repo, "bash", "scripts/evidence.sh", env=self.env | {"RECEIPT": str(receipt), "CMD": "make precheck"})
self.task(self.repo, "start", "feature/new", env=self.env | {"PRECHECK_RECEIPT": str(receipt)})
wt = self.base / "repo-wt-feature-new"
self.assertTrue((wt / ".workspace/App/.git").is_dir(), "fixed App checkout is missing")
self.assertEqual(self.git(wt / ".workspace/App", "rev-parse", "HEAD").stdout.strip(), self.app_sha)
self.assertEqual(self.git(wt / ".workspace/Rules", "rev-parse", "HEAD").stdout.strip(), self.rules_sha)
self.assertFalse((wt / ".workspace/App/deps/Nested/.git").exists())
self.assertTrue((wt / ".task/feature-new.md").exists())
def test_old_task_sync_adopts_new_sources_and_reports_explicit_transition(self):
self.git(self.repo, "-c", "core.excludesfile=/dev/null", "submodule", "add", "--force", str(self.app), "modules/App")
self.git(self.repo, "commit", "-m", "Pin old source")
self.git(self.repo, "push", "origin", "main")
wt = self.worktree()
self.git(wt, "submodule", "update", "--init")
legacy = wt / "modules/App"
self.git(legacy, "switch", "-c", "feature/owned")
before = self.git(legacy, "show-ref").stdout
self.git(self.repo, "rm", "--cached", "modules/App")
self.git(self.repo, "rm", ".gitmodules")
self.git(self.repo, "commit", "-m", "Retire gitlink without removing checkout")
self.adopt()
result = self.task(wt, "sync")
self.assertIn("[transition]", result.stdout)
self.assertEqual(self.git(legacy, "show-ref").stdout, before)
self.assertEqual(self.git(legacy, "branch", "--show-current").stdout.strip(), "feature/owned")
self.assertTrue((wt / ".workspace/App/.git").is_dir())
self.assertEqual((legacy / "content").read_text(), "app v1\n")
def test_sync_preserves_edit_branch_and_refuses_changed_declared_source(self):
self.adopt()
wt = self.worktree()
self.workspace(wt, "edit", "--name", "App", "--branch", "feature/owned")
edited = wt / ".workspace/App"
(edited / "content").write_text("local unsaved\n")
self.task(wt, "sync")
(self.app / "content").write_text("app v2\n")
self.git(self.app, "commit", "-am", "App v2")
lock = json.loads((self.repo / "components.lock.json").read_text())
lock["components"]["App"]["commit"] = self.git(self.app, "rev-parse", "HEAD").stdout.strip()
(self.repo / "components.lock.json").write_text(json.dumps(lock))
self.git(self.repo, "commit", "-am", "Update application lock")
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((edited / "content").read_text(), "local unsaved\n")
self.assertEqual(self.git(edited, "branch", "--show-current").stdout.strip(), "feature/owned")
def test_finish_preserves_external_worktree_of_retired_legacy_checkout(self):
self.adopt()
wt = self.worktree()
legacy = wt / "modules/App"
self.git(wt, "clone", str(self.app), str(legacy))
external = self.base / "external"
self.git(legacy, "worktree", "add", "-b", "feature/external", str(external))
(external / "content").write_text("external unique\n")
self.git(external, "add", "content")
before = self.git(external, "status", "--porcelain").stdout
result = self.task(wt, "finish", check=False, env=self.env | {"TASK_WRITERS_STOPPED": "1"})
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("registered component worktree", result.stdout)
self.assertEqual(self.git(external, "status", "--porcelain").stdout, before)
self.assertEqual((external / "content").read_text(), "external unique\n")
self.assertTrue(wt.exists())
def test_verify_detects_active_git_operation_in_nested_retired_root(self):
self.adopt()
wt = self.worktree()
legacy = wt / "modules/App"
self.git(wt, "clone", str(self.app), str(legacy))
nested = legacy / "retired/nested"
self.git(wt, "clone", str(self.rules), str(nested))
lock = nested / ".git/index.lock"
lock.write_text("another writer\n")
result = self.call(wt, "python3", "scripts/check-worktree.py", "verify", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("active Git operation/lock", result.stdout)
self.assertEqual(lock.read_text(), "another writer\n")
def test_make_workspace_commands_prepare_edit_unlink_and_report(self):
self.adopt()
shutil.copyfile(SOURCE / "Makefile", self.repo / "Makefile")
self.call(self.repo, "make", "workspace-prepare", "NAME=App")
source = self.repo / ".workspace/App"
self.assertEqual((source / "content").read_text(), "app v1\n")
self.call(self.repo, "make", "component-edit", "NAME=App", "BRANCH=feature/from-make")
self.assertEqual(self.git(source, "branch", "--show-current").stdout.strip(), "feature/from-make")
self.call(self.repo, "make", "component-unlink", "NAME=App")
self.assertTrue(source.is_dir())
status = self.call(self.repo, "make", "workspace-status", "NAME=App")
self.assertIn('"feature/from-make"', status.stdout)
transition = self.call(self.repo, "make", "workspace-transition", "NAME=App")
self.assertIn('"legacy"', transition.stdout)
self.assertFalse((self.repo / "modules/App").exists())
def test_finish_preserves_absorbed_metadata_after_legacy_checkout_disappears(self):
self.git(self.repo, "submodule", "add", "--force", str(self.app), "modules/App")
self.git(self.repo, "commit", "-m", "Old gitlink")
self.git(self.repo, "push", "origin", "main")
wt = self.worktree()
self.git(wt, "submodule", "update", "--init")
legacy = wt / "modules/App"
external = self.base / "external"
self.git(legacy, "worktree", "add", "-b", "feature/external", str(external))
(external / "content").write_text("independent owner\n")
self.git(external, "add", "content")
before = self.git(external, "status", "--porcelain").stdout
self.git(self.repo, "rm", "--cached", "modules/App")
self.git(self.repo, "rm", ".gitmodules")
self.git(self.repo, "commit", "-m", "Remove legacy index entries")
self.adopt()
self.task(wt, "sync")
shutil.rmtree(legacy) # Simulate an absent checkout while its Git metadata remains.
result = self.task(wt, "finish", check=False, env=self.env | {"TASK_WRITERS_STOPPED": "1"})
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("retained metadata", result.stdout)
self.assertEqual(self.git(external, "status", "--porcelain").stdout, before)
self.assertEqual((external / "content").read_text(), "independent owner\n")
def test_finish_preserves_nonbranch_legacy_refs_without_remote_backup(self):
self.adopt()
wt = self.worktree()
legacy = wt / "modules/App"
self.git(wt, "clone", str(self.app), str(legacy))
(legacy / "content").write_text("unpublished\n")
self.git(legacy, "commit", "-am", "Unique migration object")
unique = self.git(legacy, "rev-parse", "HEAD").stdout.strip()
self.git(legacy, "update-ref", "refs/migration/unique", unique)
self.git(legacy, "reset", "--hard", "origin/main")
self.git(legacy, "reflog", "expire", "--expire=now", "--all")
result = self.task(wt, "finish", check=False, env=self.env | {"TASK_WRITERS_STOPPED": "1"})
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("unpreserved local refs", result.stdout)
self.assertEqual(self.git(legacy, "rev-parse", "refs/migration/unique").stdout.strip(), unique)
@unittest.skipUnless(shutil.which("go"), "Go is required for architecture gate")
def test_architecture_gate_preserves_edges_under_workspace_paths_and_new_module_identity(self):
self.adopt()
policy = self.repo / "policy"
policy.mkdir(exist_ok=True)
(policy / "architecture.yaml").write_text("backend:\n root: .workspace/Agent-System-Backend/internal\n import_prefix: git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/\nfrontend:\n root: .workspace/Agent-System-Frontend/src\n groups: components\n")
(policy / "architecture-baseline.json").write_text('["backend/app backend/store"]')
backend = self.repo / ".workspace/Agent-System-Backend"
(backend / "internal/app").mkdir(parents=True)
(backend / "internal/store").mkdir()
(backend / "go.mod").write_text("module git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git\n\ngo 1.23\n")
source = backend / "internal/app/app.go"
source.write_text('package app\nimport _ "git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/store"\n')
(self.repo / ".workspace/Agent-System-Frontend/src").mkdir(parents=True)
self.call(self.repo, "bash", "scripts/lint-architecture.sh")
(backend / "internal/server").mkdir()
source.write_text(source.read_text() + 'import _ "git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/server"\n')
result = self.call(self.repo, "bash", "scripts/lint-architecture.sh", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("backend/app backend/server", result.stdout)
self.assertEqual(json.loads((policy / "architecture-baseline.json").read_text()), ["backend/app backend/store"])
@unittest.skipUnless(shutil.which("go"), "Go is required for boundary gates")
def test_boundary_gate_uses_isolated_map_and_rejects_new_backend_internal_import(self):
self.adopt()
names = ("Agent-System-Backend", "Agent-System-Frontend", "Agent-Intent", "Agent-Native", "Agent-Squad")
modules = {name: "git-ssh.u1680832.nyat.app/weichen/" + name + ".git" for name in names}
entries = [{"id": name, "repo": str(self.app), "path": ".workspace/" + name,
"role": "application" if name in names[:2] else "go-library", "modules": [], "target": "main"} for name in names]
(self.repo / "components.json").write_text(json.dumps({"schema": 1, "components": entries}))
(self.repo / "components.lock.json").write_text(json.dumps({"schema": 1, "components": {name: {"commit": self.app_sha} for name in names[:2]}}))
policy = self.repo / "policy"
policy.mkdir()
(policy / "architecture.yaml").write_text("backend: .workspace/Agent-System-Backend/internal\nfrontend: .workspace/Agent-System-Frontend/src\ncomponents: {}\n")
(policy / "architecture-baseline.json").write_text("[]")
(policy / "lib-deps-allowlist.txt").write_text("# No sibling-library imports\n")
mapping = {"schema": 1, "root_commit": self.git(self.repo, "rev-parse", "HEAD").stdout.strip(), "components": {}}
for name in names:
path = self.base / "isolated" / name
path.mkdir(parents=True)
if name != "Agent-System-Frontend":
(path / "go.mod").write_text("module " + modules[name] + "\n\ngo 1.23\n")
(path / "source.go").write_text("package fixture\n")
mapping["components"][name] = {"path": str(path), "commit": self.app_sha, "repo": str(self.app)}
backend = self.base / "isolated/Agent-System-Backend"
(backend / "internal/store").mkdir(parents=True)
(backend / "internal/store/store.go").write_text("package store\n")
sdk = backend / "pkg/extension"
sdk.mkdir(parents=True)
source = sdk / "sdk.go"
source.write_text("package extension\n")
(self.base / "isolated/Agent-System-Frontend/src").mkdir()
source_map = self.base / "source-map.json"
source_map.write_text(json.dumps(mapping))
env = self.env | {"COMPOSITION_SOURCE_MAP": str(source_map)}
self.call(self.repo, "bash", "scripts/lint-boundaries.sh", env=env)
source.write_text('package extension\nimport _ "' + modules["Agent-System-Backend"] + '/internal/store"\n')
result = self.call(self.repo, "bash", "scripts/lint-boundaries.sh", env=env, check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("boundary: pkg/extension imports", result.stdout)
# A forged source identity cannot substitute for the locked application.
source.write_text("package extension\n")
mapping["components"]["Agent-System-Backend"]["commit"] = "0" * 40
source_map.write_text(json.dumps(mapping))
result = self.call(self.repo, "bash", "scripts/lint-boundaries.sh", env=env, check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("locked source", result.stdout)
def test_verify_detects_nested_git_operation_inside_new_workspace(self):
self.adopt()
wt = self.worktree()
self.workspace(wt, "prepare")
nested = wt / ".workspace/App/private/Rules"
self.git(wt, "clone", str(self.rules), str(nested))
lock = nested / ".git/index.lock"
lock.write_text("active nested writer\n")
result = self.call(wt, "python3", "scripts/check-worktree.py", "verify", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("active Git operation/lock", result.stdout)
self.assertEqual(lock.read_text(), "active nested writer\n")
def test_old_task_sync_preserves_unique_legacy_commit_staging_dirty_and_ignored_files(self):
self.git(self.repo, "submodule", "add", "--force", str(self.app), "modules/App")
self.git(self.repo, "commit", "-m", "Old gitlink")
self.git(self.repo, "push", "origin", "main")
wt = self.worktree()
self.git(wt, "submodule", "update", "--init")
legacy = wt / "modules/App"
self.git(legacy, "switch", "-c", "feature/unique")
(legacy / "content").write_text("unpublished commit\n")
self.git(legacy, "commit", "-am", "Unique component history")
(legacy / "content").write_text("staged component change\n")
self.git(legacy, "add", "content")
(legacy / "content").write_text("unstaged component change\n")
(legacy / "private").mkdir()
(legacy / "private/unique").write_text("ignored unique data\n")
before = (self.git(legacy, "show-ref").stdout, self.git(legacy, "ls-files", "--stage").stdout,
self.git(wt, "rev-parse", "HEAD").stdout)
self.git(self.repo, "rm", "--cached", "modules/App")
self.git(self.repo, "rm", ".gitmodules")
self.git(self.repo, "commit", "-m", "Retire old gitlink")
self.adopt()
result = self.task(wt, "sync", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertNotIn("[synced]", result.stdout)
self.assertEqual((self.git(legacy, "show-ref").stdout, self.git(legacy, "ls-files", "--stage").stdout,
self.git(wt, "rev-parse", "HEAD").stdout), before)
self.assertEqual((legacy / "content").read_text(), "unstaged component change\n")
self.assertEqual((legacy / "private/unique").read_text(), "ignored unique data\n")
def test_old_task_text_conflict_keeps_legacy_source_and_reports_no_success(self):
self.git(self.repo, "submodule", "add", "--force", str(self.app), "modules/App")
self.git(self.repo, "commit", "-m", "Old gitlink")
self.git(self.repo, "push", "origin", "main")
wt = self.worktree()
self.git(wt, "submodule", "update", "--init")
(wt / "tracked").write_text("task branch choice\n")
self.git(wt, "commit", "-am", "Task text change")
legacy = wt / "modules/App"
before = self.git(legacy, "show-ref").stdout
(self.repo / "tracked").write_text("main branch choice\n")
self.git(self.repo, "commit", "-am", "Main text change")
self.git(self.repo, "rm", "--cached", "modules/App")
self.git(self.repo, "rm", ".gitmodules")
self.git(self.repo, "commit", "-m", "Retire legacy index")
self.adopt()
result = self.task(wt, "sync", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertNotIn("[synced]", result.stdout)
self.assertTrue(self.git(wt, "ls-files", "-u").stdout.strip())
self.assertEqual(self.git(legacy, "show-ref").stdout, before)
self.assertEqual((legacy / "content").read_text(), "app v1\n")
def test_precheck_rejects_borrowed_rules_cache_symlink(self):
self.adopt()
self.task(self.repo, "precheck")
source = next(path for path in (self.base / "cache/agent-system/rules").iterdir() if path.is_dir())
borrowed = self.base / "borrowed-rules"
source.rename(borrowed)
source.symlink_to(borrowed, target_is_directory=True)
before = self.git(borrowed, "show-ref").stdout
result = self.task(self.repo, "precheck", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertNotIn("[verdict] READY", result.stdout)
self.assertEqual(self.git(borrowed, "show-ref").stdout, before)
self.assertEqual((borrowed / "core/baseline.md").read_text(), "# Baseline\n")
if __name__ == "__main__":
unittest.main()