Configure immutable component sources and process scoped Go transport

Why: Prepare ordinary component workspaces and real module downloads without changing global Git configuration or other feature branches.

Evidence: SELF clean-cache module consumption and real temporary-Git transport tests are recorded by make evidence in the task card, including expected RED and passing GREEN runs.

Impact: Add component identities/source locks and non-secret transport configuration. Existing submodule layout remains supported during migration.

Refs: Versioned dependencies U1/U2; R15; feature/dependency-workspace
This commit is contained in:
2026-10-13 15:25:37 +00:00
parent e0982c8428
commit f71058cb7e
6 changed files with 746 additions and 3 deletions
+6
View File
@@ -21,3 +21,9 @@ runtime-backups/
# e2e.sh 运行工件(构建/种子/日志;scripts/e2e.sh 输出)
.tmp/
# Isolated migration candidates and local component workspaces.
/.migration/
/.workspace/
/go.work
/go.work.sum
File diff suppressed because one or more lines are too long
+192
View File
@@ -0,0 +1,192 @@
{
"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"
},
{
"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"
}
],
"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/"
}
]
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"schema": 1,
"components": {
"Agent-System-UI": {
"commit": "2c4322ef56df64ad72f65907d25b048a74bb5ffc"
},
"Agent-System-Frontend": {
"commit": "eb2d6d32f9323094d568bc9d80b4e5ed4f0af25b"
},
"Agent-System-Backend": {
"commit": "5d5fc9891c0eb8994545ccb8b740535d9b99fb0e"
},
"Development-Guidelines": {
"commit": "2f5b19872be2a4597838a4131ad75d1be089ee5e"
},
"Agent-System-Documents": {
"commit": "129cc3579d9c9e3edfb46c99270d65f3e1dad0a0"
}
}
}
+49
View File
@@ -0,0 +1,49 @@
"""Process-local module transport; configuration contains no credentials."""
import json
import os
from pathlib import Path
from urllib.parse import urlsplit
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 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:
# 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]
for i, rewrite in enumerate(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'])
env[f'GIT_CONFIG_KEY_{i}'] = f'url.{target}.insteadOf'
env[f'GIT_CONFIG_VALUE_{i}'] = source
env['GIT_CONFIG_COUNT'] = str(len(rewrites))
return env
+60
View File
@@ -0,0 +1,60 @@
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
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)
if __name__ == '__main__':
unittest.main()