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:
@@ -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
@@ -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/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user