Why: Replace repeated submodule source pointers with consumer-owned language versions and fixed application sources, keeping independent repository edits and existing branches intact. Evidence: SELF task receipts contain startup/sync evidence, real Go and npm consumption, Native overlay/unlink, safe transition/ref preservation, credential boundary and historical build regressions. Exact committed full composition and RDM checks follow this checkpoint; formal acceptance and GATE CI remain pending. Impact: Add isolated build/test composition, source provenance and safe owner refresh, adapt governance and service build callers, remove replaced index links while retaining original Git metadata, and adopt reusable dependency standards. Refs: Documents approved dependency-workspace r1/R1-R15 and U1-U7 implementation plan; feature/dependency-workspace task card.
118 lines
4.9 KiB
Bash
Executable File
118 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 组合指针一致:容器仓在 Dev 钉住的提交上,deps/ 里的每个 gitlink
|
|
# 必须等于 Dev 顶层对应指针。development-guidelines 只告警。
|
|
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() {
|
|
git rev-parse ":$1"
|
|
}
|
|
|
|
check_link() {
|
|
local container="$1" name="$2" sha="$3" expect_path="$4"
|
|
local expect
|
|
if ! expect=$(pin_of "$expect_path" 2>/dev/null); then
|
|
echo "pin: $container deps/$name has no Dev pin at $expect_path" >&2
|
|
fail=1
|
|
return
|
|
fi
|
|
if [ "$sha" != "$expect" ]; then
|
|
local sha_tree expect_tree
|
|
if ! sha_tree=$(git -C "$expect_path" rev-parse "${sha}^{tree}" 2>/dev/null); then
|
|
echo "pin: $container deps/$name ${sha:0:12} is missing locally; fetch $expect_path first" >&2
|
|
fail=1
|
|
return
|
|
fi
|
|
if ! expect_tree=$(git -C "$expect_path" rev-parse "${expect}^{tree}" 2>/dev/null); then
|
|
echo "pin: Dev $expect_path ${expect:0:12} is missing locally; fetch $expect_path first" >&2
|
|
fail=1
|
|
return
|
|
fi
|
|
if [ "$sha_tree" = "$expect_tree" ]; then
|
|
echo "ok $container $name (same tree)"
|
|
return
|
|
fi
|
|
echo "pin: $container deps/$name is ${sha:0:12}, Dev $expect_path is ${expect:0:12}" >&2
|
|
fail=1
|
|
return
|
|
fi
|
|
echo "ok $container $name"
|
|
}
|
|
|
|
containers=(modules/backend deps/agent-native deps/agent-squad deps/agent-intent)
|
|
for container in "${containers[@]}"; do
|
|
commit=$(pin_of "$container")
|
|
while read -r mode _typ sha name; do
|
|
[ "$mode" = "160000" ] || continue
|
|
base=${name#deps/}
|
|
if [ "$base" = "development-guidelines" ]; then
|
|
dev=$(pin_of deps/development-guidelines)
|
|
if [ "$sha" != "$dev" ]; then
|
|
echo "warn: $container deps/development-guidelines is ${sha:0:12}, Dev is ${dev:0:12}" >&2
|
|
fi
|
|
continue
|
|
fi
|
|
if [ "$base" = "agent-system-core" ]; then
|
|
check_link "$container" "$base" "$sha" modules/core
|
|
else
|
|
check_link "$container" "$base" "$sha" "deps/$base"
|
|
fi
|
|
done < <(git -C "$container" ls-tree "$commit" deps/)
|
|
done
|
|
|
|
if [ "$fail" -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
echo "pins ok"
|