feat: implement GEWU service and local agent workflows
CI / quality (push) Canceled after 0s

This commit is contained in:
vasily committed 2026-10-14 19:21:35 +08:00
1 parent 4db3f45164
commit d25c04cdd2
175 files changed
+56243 -15

No files matched your search

+227
View File
@@ -0,0 +1,227 @@
import asyncio
import hashlib
import json
import time
import httpx2
import pyotp
from cryptography.fernet import Fernet
from mcp import Client
from mcp.client.streamable_http import streamable_http_client
from gewu.adapters.git_repository import GitCanonicalRepository
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
from gewu.application.reading import CanonicalNodeReader
from gewu.bootstrap.app import create_app
def test_owner_login_returns_a_short_lived_non_agent_token(tmp_path) -> None:
async def exercise() -> None:
repository = GitCanonicalRepository(tmp_path / "knowledge.git")
identities = SQLiteIdentityStore(
tmp_path / "identity.sqlite3", owner_encryption_key=Fernet.generate_key()
)
secret = pyotp.random_base32()
identities.provision_owner(
principal_id="owner-one",
password="correct horse battery staple",
totp_secret=secret,
project_ids=("dome-performance",),
)
now = int(time.time())
identities.issue_owner_challenge(
challenge_id="challenge-one",
owner_principal_id="owner-one",
proposal_id="proposal-one",
proposal_revision=1,
payload_digest="a" * 64,
diff_digest="b" * 64,
domains=("dome-performance",),
expires_at=now + 300,
now=now,
)
agent_token = identities.provision(
credential_id="agent-key",
principal_id="agent-one",
roles=("agent",),
project_ids=("dome-performance",),
)
app = create_app(
identity_provider=identities,
owner_identity_provider=identities,
owner_service_audience="gewu-local-test",
node_reader=CanonicalNodeReader(repository),
)
async with httpx2.AsyncClient(
transport=httpx2.ASGITransport(app=app), base_url="http://127.0.0.1"
) as client:
response = await client.post(
"/auth/owner-session",
json={
"principal_id": "owner-one",
"password": "correct horse battery staple",
"totp_code": pyotp.TOTP(secret).now(),
"challenge_id": "challenge-one",
},
)
assert response.status_code == 200, response.text
assert response.headers["cache-control"] == "no-store"
token = response.json()["session_token"]
assert token.startswith("gwo_")
assert identities.authenticate(token) is None
assert identities.authenticate(agent_token) is not None
assert (
identities.authenticate_owner_session(
token,
challenge_id="challenge-one",
audience="gewu-local-test",
)
is not None
)
wrong_origin = await client.post(
"/auth/owner-session",
headers={"Origin": "https://attacker.invalid"},
json={
"principal_id": "owner-one",
"password": "correct horse battery staple",
"totp_code": pyotp.TOTP(secret).now(),
"challenge_id": "challenge-two",
},
)
assert wrong_origin.status_code == 403
oversized = await client.post(
"/auth/owner-session",
content='{"padding":"' + ("x" * 5000) + '"}',
headers={"Content-Type": "application/json"},
)
assert oversized.status_code == 413
async with httpx2.AsyncClient(
transport=httpx2.ASGITransport(app=app), base_url="http://gewu.example"
) as remote_http:
insecure = await remote_http.post(
"/auth/owner-session",
json={
"principal_id": "owner-one",
"password": "correct horse battery staple",
"totp_code": "000000",
"challenge_id": "challenge-three",
},
)
assert insecure.status_code == 403
asyncio.run(exercise())
def test_owner_login_is_fail_closed_without_encryption_key_or_audience(tmp_path) -> None:
async def exercise() -> None:
repository = GitCanonicalRepository(tmp_path / "knowledge.git")
identities = SQLiteIdentityStore(tmp_path / "identity.sqlite3")
app = create_app(
identity_provider=identities,
owner_identity_provider=identities,
node_reader=CanonicalNodeReader(repository),
)
async with httpx2.AsyncClient(
transport=httpx2.ASGITransport(app=app), base_url="http://127.0.0.1"
) as client:
response = await client.post(
"/auth/owner-session",
json={
"principal_id": "owner-one",
"password": "correct horse battery staple",
"totp_code": "000000",
"challenge_id": "challenge-one",
},
)
assert response.status_code == 503
asyncio.run(exercise())
def test_dedicated_owner_mcp_reads_diff_and_records_decision(tmp_path) -> None:
async def exercise() -> None:
repository = GitCanonicalRepository(tmp_path / "knowledge.git")
identities = SQLiteIdentityStore(
tmp_path / "identity.sqlite3", owner_encryption_key=Fernet.generate_key()
)
secret = pyotp.random_base32()
identities.provision_owner(
principal_id="owner-one",
password="correct horse battery staple",
totp_secret=secret,
project_ids=("dome-performance",),
)
diff_json = '{"files":[{"path":"projects/dome/nodes/a.yaml"}]}'
diff_digest = hashlib.sha256(diff_json.encode()).hexdigest()
identities.issue_owner_challenge(
challenge_id="challenge-review",
owner_principal_id="owner-one",
proposal_id="proposal-one",
proposal_revision=1,
payload_digest="a" * 64,
diff_digest=diff_digest,
domains=("dome-performance",),
expires_at=int(time.time()) + 300,
diff_json=diff_json,
)
app = create_app(
identity_provider=identities,
owner_identity_provider=identities,
owner_service_audience="gewu-owner-test",
node_reader=CanonicalNodeReader(repository),
)
async with app.router.lifespan_context(app):
async with httpx2.AsyncClient(
transport=httpx2.ASGITransport(app=app), base_url="http://127.0.0.1"
) as login_http:
login = await login_http.post(
"/auth/owner-session",
json={
"principal_id": "owner-one",
"password": "correct horse battery staple",
"totp_code": pyotp.TOTP(secret).now(),
"challenge_id": "challenge-review",
},
)
assert login.status_code == 200, login.text
token = login.json()["session_token"]
async with httpx2.AsyncClient(
transport=httpx2.ASGITransport(app=app),
base_url="http://localhost",
headers={"Authorization": f"Bearer {token}"},
) as owner_http:
async with Client(
streamable_http_client("http://localhost/owner-mcp", http_client=owner_http)
) as owner:
names = {tool.name for tool in (await owner.list_tools()).tools}
assert names == {
"owner_challenge_get",
"owner_diff_get",
"owner_decide",
}
challenge = await owner.call_tool(
"owner_challenge_get", {"challenge_id": "challenge-review"}
)
assert not challenge.is_error
diff = await owner.call_tool(
"owner_diff_get", {"challenge_id": "challenge-review"}
)
assert not diff.is_error
decision = await owner.call_tool(
"owner_decide",
{
"challenge_id": "challenge-review",
"expected_proposal_revision": 1,
"expected_payload_digest": "a" * 64,
"expected_diff_digest": diff_digest,
"decision": "authorize",
"rationale": "Reviewed this exact change and approve it.",
},
)
assert not decision.is_error
assert json.loads(decision.content[0].text)["status"] == "authorized"
asyncio.run(exercise())