Issue #985. The production launcher could mint one trusted
GITEA_MCP_CLIENT_INSTANCE per launch, but two gaps kept real launches on
untrusted legacy-pid-* identities:
1. build_application_mcp_servers required a profile for all five sanctioned
namespaces, so a project-scoped configuration exposing only author,
reviewer, and merger could not use it without inventing controller and
reconciler profiles that must not exist.
2. The module produced configuration data but had no runnable entry point, so
every real launch bypassed it entirely.
Changes:
- resolve_launch_namespaces() validates an explicit namespace subset as an
allow-list; unknown, duplicate, and empty selections are refused rather than
silently narrowing a launch. Omitting it preserves five-namespace behaviour.
- build_application_mcp_servers() accepts that subset, requires profiles only
for the launched namespaces, starts only those workers, and reports
excluded_namespaces / project_scoped.
- collect_instance_ids_from_mcp_servers() inspects the launch's own namespaces
instead of an assumed five, and reports missing_servers, so a three-namespace
launch can prove shared attribution without reading as two absent workers.
- Runnable entry point: python3 -m mcp_application_launcher mints one trusted
identity, writes a per-launch 0600 mcpServers config, and execs the client.
CLIENT_LAUNCH_SPECS is a data-driven registry so other supported clients use
the same mint-once/propagate-to-all mechanism. --dry-run prints the plan.
- Provenance sealing now fails closed. The inst- format is public and
reproducible, so format alone could previously let anyone who set one
environment variable manufacture a trusted identity. Trust now additionally
requires GITEA_MCP_INSTANCE_PROVENANCE=trusted_launcher, which only the
launcher writes; a well-formed but unsealed value is classified
unsealed_launcher and refused, while still being reported for diagnosis.
Deliberate behaviour change: tests/test_issue_978_instance_fleet_snapshot.py
test_client_hints_trusted_when_set previously asserted that a well-formed ID
alone was trusted. It now supplies the launcher seal, and a new companion test
asserts the unsealed case fails closed. This tightens the contract; no
assertion was weakened.
No static or persistent per-project instance IDs are introduced, duplicate
worker and cohort detection are untouched, and no fleet or mutation gate is
relaxed.
Tests: tests/test_issue_985_project_scoped_launcher.py, 47 passed, 3 subtests.
Full suite from a branches/ worktree: 28 failed, 6252 passed, 6 skipped against
a master baseline at 32ab8392 of 28 failed, 6204 passed, 6 skipped; the failing
sets are byte-identical, so zero regressions and zero masked failures.
Closes #985
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
1475 lines
53 KiB
Python
1475 lines
53 KiB
Python
"""Instance-level fleet identity and health snapshots (#978).
|
||
|
||
Covers the acceptance criteria for multi-instance fleets: two Codex launches
|
||
remain distinct, ten workers attribute correctly, same client_type is not a
|
||
duplicate, collisions fail closed, historical rows stay non-blocking, and the
|
||
controller/reconciler read surface grants no mutation authority.
|
||
|
||
All identifiers are synthetic.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import inspect
|
||
import json
|
||
import os
|
||
import tempfile
|
||
import unittest
|
||
from datetime import datetime, timedelta, timezone
|
||
from typing import Any
|
||
from unittest import mock
|
||
|
||
import mcp_fleet_snapshot as fleet
|
||
import mcp_worker_identity as mwi
|
||
|
||
|
||
NOW = datetime(2026, 7, 30, 7, 0, 0, tzinfo=timezone.utc)
|
||
TTL = 900.0
|
||
NAMESPACES = ("author", "reviewer", "merger", "controller", "reconciler")
|
||
|
||
|
||
def _registry() -> mwi.WorkerRegistry:
|
||
handle, path = tempfile.mkstemp(suffix=".sqlite3")
|
||
os.close(handle)
|
||
os.unlink(path)
|
||
return mwi.WorkerRegistry(path)
|
||
|
||
|
||
def _alive(_pid: int | None) -> bool:
|
||
return True
|
||
|
||
|
||
def _dead(_pid: int | None) -> bool:
|
||
return False
|
||
|
||
|
||
def _register(
|
||
registry: mwi.WorkerRegistry,
|
||
*,
|
||
client: str,
|
||
instance: str,
|
||
session: str,
|
||
generation: str,
|
||
namespace: str,
|
||
pid: int,
|
||
now: datetime = NOW,
|
||
ttl: float = TTL,
|
||
fleet_run_id: str | None = "run-canary",
|
||
status_after: str | None = None,
|
||
worker_identity: str | None = None,
|
||
process_identity: str | None = None,
|
||
repository_binding: str = "/repo/Gitea-Tools",
|
||
startup_revision: str = "rev-live",
|
||
instance_id_provenance: str = fleet.INSTANCE_ID_PROVENANCE_TRUSTED,
|
||
role: str | None = None,
|
||
) -> dict[str, Any]:
|
||
identity = worker_identity or mwi.generate_worker_identity(
|
||
client, f"{session}-{namespace}", now=now, nonce=f"{pid}-{namespace}"
|
||
)
|
||
outcome = registry.register(
|
||
worker_identity=identity,
|
||
client_name=client,
|
||
client_instance_id=instance,
|
||
session_id=session,
|
||
generation_id=generation,
|
||
role=role or namespace,
|
||
profile=f"prgs-{namespace}",
|
||
namespace=namespace,
|
||
remote="prgs",
|
||
repository_binding=repository_binding,
|
||
pid=pid,
|
||
heartbeat_ttl_seconds=ttl,
|
||
now=now,
|
||
fleet_run_id=fleet_run_id,
|
||
process_identity=process_identity or f"pid-{pid}",
|
||
startup_revision=startup_revision,
|
||
loaded_revision=startup_revision,
|
||
parity_revision=startup_revision,
|
||
live_revision=startup_revision,
|
||
instance_id_provenance=instance_id_provenance,
|
||
pid_alive_probe=_alive,
|
||
)
|
||
outcome["identity"] = identity
|
||
if status_after and outcome.get("registered"):
|
||
if status_after == mwi.STATUS_RELEASED:
|
||
registry.release(worker_identity=identity, now=now)
|
||
elif status_after == mwi.STATUS_SUPERSEDED:
|
||
# Direct status write for historical rows.
|
||
with registry._tx() as conn: # noqa: SLF001 — test-only fixture
|
||
conn.execute(
|
||
"UPDATE worker_registrations SET status = ? WHERE worker_identity = ?",
|
||
(mwi.STATUS_SUPERSEDED, identity),
|
||
)
|
||
return outcome
|
||
|
||
|
||
def _five_workers(
|
||
registry: mwi.WorkerRegistry,
|
||
*,
|
||
client: str,
|
||
instance: str,
|
||
session_prefix: str,
|
||
gen_prefix: str,
|
||
pid_base: int,
|
||
now: datetime = NOW,
|
||
) -> list[dict[str, Any]]:
|
||
out = []
|
||
for i, ns in enumerate(NAMESPACES):
|
||
out.append(
|
||
_register(
|
||
registry,
|
||
client=client,
|
||
instance=instance,
|
||
session=f"{session_prefix}-{ns}",
|
||
generation=f"{gen_prefix}-{ns}",
|
||
namespace=ns,
|
||
pid=pid_base + i,
|
||
now=now,
|
||
)
|
||
)
|
||
return out
|
||
|
||
|
||
class InstanceIdentityTests(unittest.TestCase):
|
||
def test_generate_distinct_instance_ids(self):
|
||
a = fleet.generate_client_instance_id("codex", launch_nonce="a", now=NOW)
|
||
b = fleet.generate_client_instance_id("codex", launch_nonce="b", now=NOW)
|
||
self.assertNotEqual(a, b)
|
||
self.assertTrue(a.startswith("inst-codex-"))
|
||
self.assertTrue(fleet.assess_instance_identity(a)["trusted"])
|
||
|
||
def test_legacy_pid_fallback_not_trusted(self):
|
||
a = fleet.assess_instance_identity("pid-1234")
|
||
self.assertFalse(a["trusted"])
|
||
self.assertEqual(a["provenance"], fleet.INSTANCE_ID_PROVENANCE_LEGACY)
|
||
|
||
def test_missing_instance_id(self):
|
||
a = fleet.assess_instance_identity(None)
|
||
self.assertFalse(a["complete"])
|
||
self.assertEqual(a["provenance"], fleet.INSTANCE_ID_PROVENANCE_MISSING)
|
||
|
||
|
||
class TwoCodexInstancesTests(unittest.TestCase):
|
||
"""AC1–AC3: two Codex instances, ten workers, same type not duplicate."""
|
||
|
||
def test_two_codex_instances_ten_workers(self):
|
||
registry = _registry()
|
||
inst_a = fleet.generate_client_instance_id("codex", launch_nonce="A", now=NOW)
|
||
inst_b = fleet.generate_client_instance_id("codex", launch_nonce="B", now=NOW)
|
||
_five_workers(
|
||
registry,
|
||
client="codex",
|
||
instance=inst_a,
|
||
session_prefix="sessA",
|
||
gen_prefix="genA",
|
||
pid_base=1000,
|
||
)
|
||
_five_workers(
|
||
registry,
|
||
client="codex",
|
||
instance=inst_b,
|
||
session_prefix="sessB",
|
||
gen_prefix="genB",
|
||
pid_base=2000,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(status=None),
|
||
now=NOW + timedelta(seconds=10),
|
||
pid_alive_probe=_alive,
|
||
canonical_repository="/repo/Gitea-Tools",
|
||
expected_live_revision="rev-live",
|
||
)
|
||
self.assertEqual(snap["live_worker_count"], 10)
|
||
self.assertEqual(snap["instance_count"], 2)
|
||
self.assertIn("codex", snap["multi_instance_same_client_type"])
|
||
self.assertEqual(len(snap["multi_instance_same_client_type"]["codex"]), 2)
|
||
self.assertTrue(snap["same_client_type_not_duplicate"])
|
||
# No finding that treats same client_type alone as a duplicate.
|
||
for f in snap["active_blockers"]:
|
||
self.assertNotIn("client_type alone", f["detail"].lower())
|
||
by_inst = {i["client_instance_id"]: i for i in snap["instances"]}
|
||
self.assertEqual(by_inst[inst_a]["worker_count"], 5)
|
||
self.assertEqual(by_inst[inst_b]["worker_count"], 5)
|
||
self.assertEqual(set(by_inst[inst_a]["namespaces"]), set(NAMESPACES))
|
||
self.assertEqual(set(by_inst[inst_b]["namespaces"]), set(NAMESPACES))
|
||
self.assertTrue(snap["live_fleet_safe"])
|
||
|
||
def test_mixed_client_types(self):
|
||
registry = _registry()
|
||
_five_workers(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-codex-1",
|
||
session_prefix="c",
|
||
gen_prefix="gc",
|
||
pid_base=10,
|
||
)
|
||
_five_workers(
|
||
registry,
|
||
client="claude_code",
|
||
instance="inst-claude-1",
|
||
session_prefix="l",
|
||
gen_prefix="gl",
|
||
pid_base=20,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=5),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
self.assertEqual(snap["instance_count"], 2)
|
||
self.assertTrue(snap["live_fleet_safe"])
|
||
|
||
|
||
class CollisionTests(unittest.TestCase):
|
||
def test_live_reuse_of_instance_id_different_client_types(self):
|
||
registry = _registry()
|
||
shared = "inst-shared-collision"
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance=shared,
|
||
session="s1",
|
||
generation="g1",
|
||
namespace="author",
|
||
pid=1,
|
||
)
|
||
# Second client type forced onto same instance id via direct SQL to
|
||
# bypass register's same-namespace guard and exercise classification.
|
||
with registry._tx() as conn: # noqa: SLF001
|
||
conn.execute(
|
||
"""
|
||
INSERT INTO worker_registrations (
|
||
worker_identity, client_name, client_instance_id, session_id,
|
||
generation_id, role, profile, namespace, remote,
|
||
repository_binding, pid, transport, token_fingerprint,
|
||
started_at, last_heartbeat_at, heartbeat_ttl_seconds,
|
||
fencing_epoch, status, process_identity, instance_id_provenance
|
||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||
""",
|
||
(
|
||
"claude_code-20260730T070000Z-aaaaaaaaaaaa",
|
||
"claude_code",
|
||
shared,
|
||
"s2",
|
||
"g2",
|
||
"author",
|
||
"prgs-author",
|
||
"reviewer",
|
||
"prgs",
|
||
"/repo/Gitea-Tools",
|
||
2,
|
||
None,
|
||
None,
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
TTL,
|
||
1,
|
||
mwi.STATUS_ACTIVE,
|
||
"pid-2",
|
||
fleet.INSTANCE_ID_PROVENANCE_TRUSTED,
|
||
),
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_INSTANCE_ID_COLLISION, classes)
|
||
self.assertFalse(snap["live_fleet_safe"])
|
||
|
||
def test_duplicate_namespace_within_instance(self):
|
||
registry = _registry()
|
||
inst = "inst-dup-ns"
|
||
first = _register(
|
||
registry,
|
||
client="codex",
|
||
instance=inst,
|
||
session="s-a",
|
||
generation="g-a",
|
||
namespace="author",
|
||
pid=11,
|
||
)
|
||
self.assertTrue(first["registered"], first)
|
||
# Direct second author under same instance (bypass register guard).
|
||
with registry._tx() as conn: # noqa: SLF001
|
||
conn.execute(
|
||
"""
|
||
INSERT INTO worker_registrations (
|
||
worker_identity, client_name, client_instance_id, session_id,
|
||
generation_id, role, profile, namespace, remote,
|
||
repository_binding, pid, transport, token_fingerprint,
|
||
started_at, last_heartbeat_at, heartbeat_ttl_seconds,
|
||
fencing_epoch, status, process_identity, instance_id_provenance
|
||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||
""",
|
||
(
|
||
"codex-20260730T070000Z-bbbbbbbbbbbb",
|
||
"codex",
|
||
inst,
|
||
"s-b",
|
||
"g-b",
|
||
"author",
|
||
"prgs-author",
|
||
"author",
|
||
"prgs",
|
||
"/repo/Gitea-Tools",
|
||
12,
|
||
None,
|
||
None,
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
TTL,
|
||
2,
|
||
mwi.STATUS_ACTIVE,
|
||
"pid-12",
|
||
fleet.INSTANCE_ID_PROVENANCE_TRUSTED,
|
||
),
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_DUPLICATE_NAMESPACE, classes)
|
||
|
||
def test_register_refuses_duplicate_namespace(self):
|
||
registry = _registry()
|
||
inst = "inst-reg-dup"
|
||
a = _register(
|
||
registry,
|
||
client="codex",
|
||
instance=inst,
|
||
session="s1",
|
||
generation="g1",
|
||
namespace="author",
|
||
pid=21,
|
||
)
|
||
self.assertTrue(a["registered"])
|
||
b = _register(
|
||
registry,
|
||
client="codex",
|
||
instance=inst,
|
||
session="s2",
|
||
generation="g2",
|
||
namespace="author",
|
||
pid=22,
|
||
)
|
||
self.assertFalse(b["registered"])
|
||
self.assertEqual(b.get("collision_kind"), "duplicate_namespace_worker")
|
||
|
||
def test_reused_worker_identity(self):
|
||
registry = _registry()
|
||
identity = mwi.generate_worker_identity("codex", "sess", now=NOW, nonce="fixed")
|
||
first = _register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-1",
|
||
session="s1",
|
||
generation="g1",
|
||
namespace="author",
|
||
pid=31,
|
||
worker_identity=identity,
|
||
)
|
||
self.assertTrue(first["registered"])
|
||
second = registry.register(
|
||
worker_identity=identity,
|
||
client_name="codex",
|
||
client_instance_id="inst-2",
|
||
session_id="s2",
|
||
generation_id="g2",
|
||
namespace="author",
|
||
pid=32,
|
||
now=NOW,
|
||
pid_alive_probe=_alive,
|
||
)
|
||
self.assertFalse(second["registered"])
|
||
self.assertTrue(second["collision"])
|
||
|
||
def test_reused_session_identity(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-1",
|
||
session="shared-session",
|
||
generation="g1",
|
||
namespace="author",
|
||
pid=41,
|
||
)
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-2",
|
||
session="shared-session",
|
||
generation="g2",
|
||
namespace="reviewer",
|
||
pid=42,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_SESSION_COLLISION, classes)
|
||
|
||
def test_reused_generation_across_instances(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-1",
|
||
session="s1",
|
||
generation="gen-shared",
|
||
namespace="author",
|
||
pid=51,
|
||
)
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-2",
|
||
session="s2",
|
||
generation="gen-shared",
|
||
namespace="author",
|
||
pid=52,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_GENERATION_COLLISION, classes)
|
||
|
||
def test_reused_pid_and_process_identity(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-1",
|
||
session="s1",
|
||
generation="g1",
|
||
namespace="author",
|
||
pid=61,
|
||
process_identity="proc-same",
|
||
)
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-2",
|
||
session="s2",
|
||
generation="g2",
|
||
namespace="reviewer",
|
||
pid=61,
|
||
process_identity="proc-same",
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_PID_COLLISION, classes)
|
||
self.assertIn(fleet.CLASS_PROCESS_COLLISION, classes)
|
||
|
||
def test_ownership_fencing_collision(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-1",
|
||
session="s1",
|
||
generation="gen-fence",
|
||
namespace="author",
|
||
pid=71,
|
||
)
|
||
# Second instance forced onto same generation+epoch via SQL.
|
||
with registry._tx() as conn: # noqa: SLF001
|
||
conn.execute(
|
||
"""
|
||
INSERT INTO worker_registrations (
|
||
worker_identity, client_name, client_instance_id, session_id,
|
||
generation_id, role, profile, namespace, remote,
|
||
repository_binding, pid, transport, token_fingerprint,
|
||
started_at, last_heartbeat_at, heartbeat_ttl_seconds,
|
||
fencing_epoch, status, process_identity, instance_id_provenance
|
||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||
""",
|
||
(
|
||
"codex-20260730T070000Z-cccccccccccc",
|
||
"codex",
|
||
"inst-2",
|
||
"s2",
|
||
"gen-fence",
|
||
"reviewer",
|
||
"prgs-reviewer",
|
||
"reviewer",
|
||
"prgs",
|
||
"/repo/Gitea-Tools",
|
||
72,
|
||
None,
|
||
None,
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
TTL,
|
||
1, # same epoch as first (first got epoch 1)
|
||
mwi.STATUS_ACTIVE,
|
||
"pid-72",
|
||
fleet.INSTANCE_ID_PROVENANCE_TRUSTED,
|
||
),
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertTrue(
|
||
{fleet.CLASS_OWNERSHIP_COLLISION, fleet.CLASS_GENERATION_COLLISION}
|
||
& classes
|
||
)
|
||
|
||
|
||
class ManifestAndEdgeTests(unittest.TestCase):
|
||
def test_missing_and_unmanifested(self):
|
||
registry = _registry()
|
||
live = "inst-live"
|
||
expected = "inst-expected-missing"
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance=live,
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=81,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
expected_manifest=[
|
||
{"client_instance_id": expected, "client_type": "codex"},
|
||
],
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
self.assertEqual(snap["missing_expected_instance_ids"], [expected])
|
||
self.assertEqual(snap["unmanifested_instance_ids"], [live])
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_MISSING, classes)
|
||
self.assertIn(fleet.CLASS_UNMANIFESTED, classes)
|
||
|
||
def test_unknown_client(self):
|
||
registry = _registry()
|
||
with registry._tx() as conn: # noqa: SLF001
|
||
conn.execute(
|
||
"""
|
||
INSERT INTO worker_registrations (
|
||
worker_identity, client_name, client_instance_id, session_id,
|
||
generation_id, role, profile, namespace, remote,
|
||
repository_binding, pid, transport, token_fingerprint,
|
||
started_at, last_heartbeat_at, heartbeat_ttl_seconds,
|
||
fencing_epoch, status, process_identity, instance_id_provenance
|
||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||
""",
|
||
(
|
||
"unknown_client-20260730T070000Z-dddddddddddd",
|
||
"unknown_client",
|
||
"inst-unk",
|
||
"s",
|
||
"g",
|
||
"author",
|
||
"prgs-author",
|
||
"author",
|
||
"prgs",
|
||
"/repo/Gitea-Tools",
|
||
91,
|
||
None,
|
||
None,
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
mwi._ts(NOW), # noqa: SLF001
|
||
TTL,
|
||
1,
|
||
mwi.STATUS_ACTIVE,
|
||
"pid-91",
|
||
fleet.INSTANCE_ID_PROVENANCE_TRUSTED,
|
||
),
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_UNKNOWN_CLIENT, classes)
|
||
|
||
def test_foreign_repository(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-fr",
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=101,
|
||
repository_binding="/other/repo",
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
canonical_repository="/repo/Gitea-Tools",
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_FOREIGN_REPOSITORY, classes)
|
||
|
||
def test_old_revision(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-old",
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=111,
|
||
startup_revision="rev-old",
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
expected_live_revision="rev-live",
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_OLD_REVISION, classes)
|
||
|
||
def test_stale_worker(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-stale",
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=121,
|
||
now=NOW,
|
||
ttl=60.0,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=120),
|
||
pid_alive_probe=_dead,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_STALE_WORKER, classes)
|
||
|
||
def test_historical_not_active_blocker(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-hist",
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=131,
|
||
status_after=mwi.STATUS_RELEASED,
|
||
)
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-live",
|
||
session="s2",
|
||
generation="g2",
|
||
namespace="author",
|
||
pid=132,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(status=None),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
self.assertEqual(snap["historical_worker_count"], 1)
|
||
self.assertTrue(
|
||
all(f["classification"] == fleet.CLASS_HISTORICAL for f in snap["historical_findings"])
|
||
)
|
||
# Only historical finding for the dead row — live fleet otherwise safe.
|
||
hist_blockers = [
|
||
f
|
||
for f in snap["active_blockers"]
|
||
if f["classification"] == fleet.CLASS_HISTORICAL
|
||
]
|
||
self.assertEqual(hist_blockers, [])
|
||
self.assertTrue(snap["live_fleet_safe"])
|
||
|
||
def test_legacy_incomplete_identity(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="pid-999",
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=141,
|
||
instance_id_provenance=fleet.INSTANCE_ID_PROVENANCE_LEGACY,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
classes = {f["classification"] for f in snap["active_blockers"]}
|
||
self.assertIn(fleet.CLASS_LEGACY_INCOMPLETE, classes)
|
||
self.assertFalse(snap["mutation_safe"])
|
||
|
||
|
||
class LifecycleTests(unittest.TestCase):
|
||
def test_heartbeat_continuity_across_snapshots(self):
|
||
registry = _registry()
|
||
outcome = _register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-hb",
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=151,
|
||
)
|
||
identity = outcome["identity"]
|
||
earlier = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
later_time = NOW + timedelta(seconds=30)
|
||
registry.heartbeat(
|
||
worker_identity=identity,
|
||
fencing_epoch=outcome["fencing_epoch"],
|
||
now=later_time,
|
||
expected_session_id="s",
|
||
expected_generation_id="g",
|
||
expected_client_name="codex",
|
||
expected_pid=151,
|
||
)
|
||
later = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=later_time + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
cmp = fleet.compare_snapshot_heartbeats(earlier, later)
|
||
self.assertTrue(cmp["stable_ownership"])
|
||
self.assertEqual(cmp["shared_live_workers"], [identity])
|
||
self.assertTrue(cmp["continuity"][0]["heartbeat_non_decreasing"])
|
||
|
||
def test_worker_reconnect_preserves_instance(self):
|
||
"""Reconnect = heartbeat renew; same instance/worker/session."""
|
||
registry = _registry()
|
||
o = _register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-rc",
|
||
session="s-rc",
|
||
generation="g-rc",
|
||
namespace="author",
|
||
pid=161,
|
||
)
|
||
before = registry.get(o["identity"])
|
||
registry.heartbeat(
|
||
worker_identity=o["identity"],
|
||
fencing_epoch=o["fencing_epoch"],
|
||
now=NOW + timedelta(seconds=20),
|
||
expected_session_id="s-rc",
|
||
expected_generation_id="g-rc",
|
||
expected_client_name="codex",
|
||
expected_pid=161,
|
||
)
|
||
after = registry.get(o["identity"])
|
||
self.assertEqual(before["client_instance_id"], after["client_instance_id"])
|
||
self.assertEqual(before["session_id"], after["session_id"])
|
||
self.assertEqual(before["worker_identity"], after["worker_identity"])
|
||
|
||
def test_worker_restart_new_worker_same_instance(self):
|
||
registry = _registry()
|
||
inst = "inst-restart"
|
||
old = _register(
|
||
registry,
|
||
client="codex",
|
||
instance=inst,
|
||
session="s-old",
|
||
generation="g-old",
|
||
namespace="author",
|
||
pid=171,
|
||
)
|
||
registry.release(worker_identity=old["identity"], now=NOW + timedelta(seconds=5))
|
||
new = _register(
|
||
registry,
|
||
client="codex",
|
||
instance=inst,
|
||
session="s-new",
|
||
generation="g-new",
|
||
namespace="author",
|
||
pid=172,
|
||
now=NOW + timedelta(seconds=10),
|
||
)
|
||
self.assertTrue(new["registered"])
|
||
self.assertNotEqual(old["identity"], new["identity"])
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(status=None),
|
||
now=NOW + timedelta(seconds=15),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
live = [w for w in snap["live_workers"] if w["client_instance_id"] == inst]
|
||
self.assertEqual(len(live), 1)
|
||
self.assertEqual(live[0]["worker_identity"], new["identity"])
|
||
self.assertEqual(snap["historical_worker_count"], 1)
|
||
|
||
def test_full_application_restart_new_instance(self):
|
||
registry = _registry()
|
||
old_inst = fleet.generate_client_instance_id("codex", launch_nonce="old", now=NOW)
|
||
new_inst = fleet.generate_client_instance_id(
|
||
"codex", launch_nonce="new", now=NOW + timedelta(hours=1)
|
||
)
|
||
for ns, pid in zip(NAMESPACES, range(181, 186)):
|
||
o = _register(
|
||
registry,
|
||
client="codex",
|
||
instance=old_inst,
|
||
session=f"old-{ns}",
|
||
generation=f"oldg-{ns}",
|
||
namespace=ns,
|
||
pid=pid,
|
||
)
|
||
registry.release(worker_identity=o["identity"], now=NOW + timedelta(minutes=1))
|
||
for ns, pid in zip(NAMESPACES, range(191, 196)):
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance=new_inst,
|
||
session=f"new-{ns}",
|
||
generation=f"newg-{ns}",
|
||
namespace=ns,
|
||
pid=pid,
|
||
now=NOW + timedelta(minutes=2),
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(status=None),
|
||
now=NOW + timedelta(minutes=3),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
live_ids = {i["client_instance_id"] for i in snap["instances"]}
|
||
self.assertEqual(live_ids, {new_inst})
|
||
self.assertEqual(snap["historical_worker_count"], 5)
|
||
|
||
|
||
class PermissionAndSingleClientTests(unittest.TestCase):
|
||
def test_single_client_still_supported(self):
|
||
registry = _registry()
|
||
_five_workers(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-single",
|
||
session_prefix="solo",
|
||
gen_prefix="solo-g",
|
||
pid_base=300,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
self.assertEqual(snap["instance_count"], 1)
|
||
self.assertEqual(snap["live_worker_count"], 5)
|
||
self.assertTrue(snap["live_fleet_safe"])
|
||
self.assertTrue(snap["mutation_safe"])
|
||
|
||
def test_snapshot_has_consistency_token(self):
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-tok",
|
||
session="s",
|
||
generation="g",
|
||
namespace="author",
|
||
pid=400,
|
||
)
|
||
snap = fleet.snapshot_instance_fleet(
|
||
registry.list_workers(),
|
||
now=NOW + timedelta(seconds=1),
|
||
pid_alive_probe=_alive,
|
||
)
|
||
self.assertTrue(snap["consistency_token"].startswith("fleetrev-"))
|
||
self.assertEqual(snap["consistency_token"], snap["registry_revision"])
|
||
self.assertIn("snapshot_at", snap)
|
||
|
||
def test_tool_role_gate_denies_author(self):
|
||
import gitea_mcp_server as server
|
||
|
||
with mock.patch.object(server, "_profile_operation_gate", return_value=[]):
|
||
with mock.patch.object(
|
||
server,
|
||
"get_profile",
|
||
return_value={
|
||
"profile_name": "prgs-author",
|
||
"role": "author",
|
||
"allowed_operations": ["gitea.read"],
|
||
"forbidden_operations": [],
|
||
},
|
||
):
|
||
result = server.gitea_snapshot_instance_fleet(remote="prgs")
|
||
self.assertFalse(result.get("success"))
|
||
self.assertEqual(result.get("denied_role"), "author")
|
||
self.assertTrue(result.get("read_only"))
|
||
self.assertFalse(result.get("mutation_performed", True))
|
||
|
||
def test_tool_allows_controller(self):
|
||
import gitea_mcp_server as server
|
||
|
||
registry = _registry()
|
||
_register(
|
||
registry,
|
||
client="codex",
|
||
instance="inst-ctrl",
|
||
session="s",
|
||
generation="g",
|
||
namespace="controller",
|
||
pid=410,
|
||
)
|
||
with mock.patch.object(server, "_profile_operation_gate", return_value=[]):
|
||
with mock.patch.object(
|
||
server,
|
||
"get_profile",
|
||
return_value={
|
||
"profile_name": "prgs-controller",
|
||
"role": "controller",
|
||
"allowed_operations": ["gitea.read"],
|
||
"forbidden_operations": [],
|
||
},
|
||
):
|
||
with mock.patch.object(server, "_worker_registry", return_value=registry):
|
||
with mock.patch.object(
|
||
server, "_current_master_parity", return_value={}
|
||
):
|
||
result = server.gitea_snapshot_instance_fleet(remote="prgs")
|
||
self.assertTrue(result.get("success"), result)
|
||
self.assertTrue(result.get("read_only"))
|
||
self.assertTrue(result["permission_scope"]["denied_unrelated_mutations"])
|
||
self.assertEqual(result["permission_scope"]["granted_operations"], ["gitea.read"])
|
||
|
||
def test_tool_allows_reconciler(self):
|
||
import gitea_mcp_server as server
|
||
|
||
registry = _registry()
|
||
with mock.patch.object(server, "_profile_operation_gate", return_value=[]):
|
||
with mock.patch.object(
|
||
server,
|
||
"get_profile",
|
||
return_value={
|
||
"profile_name": "prgs-reconciler",
|
||
"role": "reconciler",
|
||
"allowed_operations": ["gitea.read"],
|
||
"forbidden_operations": [],
|
||
},
|
||
):
|
||
with mock.patch.object(server, "_worker_registry", return_value=registry):
|
||
with mock.patch.object(
|
||
server, "_current_master_parity", return_value={}
|
||
):
|
||
result = server.gitea_snapshot_instance_fleet(remote="prgs")
|
||
self.assertTrue(result.get("success"), result)
|
||
|
||
def test_capability_map_entry(self):
|
||
import task_capability_map as tcm
|
||
|
||
entry = tcm.TASK_CAPABILITY_MAP["snapshot_instance_fleet"]
|
||
self.assertEqual(entry["permission"], "gitea.read")
|
||
self.assertEqual(entry["role"], "controller")
|
||
self.assertIn("gitea_snapshot_instance_fleet", tcm.TASK_CAPABILITY_MAP)
|
||
|
||
def test_fleet_run_env_allowlisted(self):
|
||
import gitea_config
|
||
|
||
self.assertIn("GITEA_MCP_FLEET_RUN_ID", gitea_config.RECOGNIZED_GITEA_ENV_KEYS)
|
||
|
||
def test_docs_exist(self):
|
||
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
path = os.path.join(root, "docs", "instance-fleet-identity.md")
|
||
self.assertTrue(os.path.isfile(path))
|
||
with open(path, encoding="utf-8") as fh:
|
||
text = fh.read()
|
||
self.assertIn("client_instance_id", text)
|
||
self.assertIn("exactly_one_per_profile", text)
|
||
self.assertIn("gitea_snapshot_instance_fleet", text)
|
||
|
||
def test_tool_is_registered(self):
|
||
import gitea_mcp_server as server
|
||
|
||
self.assertTrue(callable(server.gitea_snapshot_instance_fleet))
|
||
src = inspect.getsource(server.gitea_snapshot_instance_fleet)
|
||
self.assertIn("controller", src)
|
||
self.assertIn("reconciler", src)
|
||
|
||
|
||
class ClientHintsTests(unittest.TestCase):
|
||
def test_client_hints_legacy_when_unset(self):
|
||
import gitea_mcp_server as server
|
||
|
||
env = {"GITEA_MCP_CLIENT": "codex"}
|
||
with mock.patch.dict(os.environ, env, clear=False):
|
||
# Ensure instance key is absent.
|
||
os.environ.pop("GITEA_MCP_CLIENT_INSTANCE", None)
|
||
hints = server._client_identity_hints()
|
||
self.assertFalse(hints["instance_identity_trusted"])
|
||
self.assertTrue(
|
||
str(hints["client_instance_id"]).startswith("legacy-pid-")
|
||
or hints["instance_id_provenance"] == fleet.INSTANCE_ID_PROVENANCE_LEGACY
|
||
)
|
||
|
||
def test_client_hints_trusted_when_set(self):
|
||
"""Trusted requires the launcher seal as well as the format (#985).
|
||
|
||
Before #985 a well-formed ``inst-…`` value alone was accepted as
|
||
trusted. The format is public and reproducible, so anyone able to set
|
||
one environment variable could hand-write a valid-looking ID; #985
|
||
therefore additionally requires the provenance marker that only the
|
||
production launcher writes. This case now supplies the full sealed env
|
||
the launcher actually emits.
|
||
"""
|
||
import gitea_mcp_server as server
|
||
|
||
inst = fleet.generate_client_instance_id("codex", launch_nonce="t", now=NOW)
|
||
with mock.patch.dict(
|
||
os.environ,
|
||
{
|
||
"GITEA_MCP_CLIENT": "codex",
|
||
"GITEA_MCP_CLIENT_INSTANCE": inst,
|
||
"GITEA_MCP_INSTANCE_PROVENANCE": (
|
||
fleet.INSTANCE_ID_PROVENANCE_TRUSTED
|
||
),
|
||
"GITEA_MCP_FLEET_RUN_ID": "run-1",
|
||
},
|
||
clear=False,
|
||
):
|
||
hints = server._client_identity_hints()
|
||
self.assertTrue(hints["instance_identity_trusted"])
|
||
self.assertTrue(hints["instance_launcher_sealed"])
|
||
self.assertEqual(hints["client_instance_id"], inst)
|
||
self.assertEqual(hints["fleet_run_id"], "run-1")
|
||
|
||
def test_client_hints_wellformed_but_unsealed_not_trusted(self):
|
||
"""#985: a valid-looking ID without the launcher seal fails closed."""
|
||
import gitea_mcp_server as server
|
||
|
||
inst = fleet.generate_client_instance_id(
|
||
"codex", launch_nonce="unsealed", now=NOW
|
||
)
|
||
with mock.patch.dict(
|
||
os.environ,
|
||
{
|
||
"GITEA_MCP_CLIENT": "codex",
|
||
"GITEA_MCP_CLIENT_INSTANCE": inst,
|
||
"GITEA_MCP_FLEET_RUN_ID": "run-1",
|
||
},
|
||
clear=False,
|
||
):
|
||
os.environ.pop("GITEA_MCP_INSTANCE_PROVENANCE", None)
|
||
hints = server._client_identity_hints()
|
||
self.assertFalse(hints["instance_identity_trusted"])
|
||
self.assertFalse(hints["instance_launcher_sealed"])
|
||
# Still reported for diagnosis rather than silently discarded.
|
||
self.assertEqual(hints["client_instance_id"], inst)
|
||
self.assertEqual(
|
||
hints["instance_id_provenance"],
|
||
fleet.INSTANCE_ID_PROVENANCE_UNSEALED,
|
||
)
|
||
|
||
def test_malformed_user_supplied_instance_not_trusted(self):
|
||
import gitea_mcp_server as server
|
||
|
||
with mock.patch.dict(
|
||
os.environ,
|
||
{
|
||
"GITEA_MCP_CLIENT": "codex",
|
||
"GITEA_MCP_CLIENT_INSTANCE": "user-spoofed-not-launcher",
|
||
},
|
||
clear=False,
|
||
):
|
||
hints = server._client_identity_hints()
|
||
self.assertFalse(hints["instance_identity_trusted"])
|
||
self.assertEqual(hints["client_instance_id"], "user-spoofed-not-launcher")
|
||
|
||
|
||
class ProductionLauncherB1Tests(unittest.TestCase):
|
||
"""#978 review 655 B1: production launcher mints + propagates instance ID."""
|
||
|
||
def test_one_launch_shares_id_across_five_namespaces(self):
|
||
import mcp_application_launcher as launcher
|
||
|
||
profiles = {ns: f"prgs-{ns}" for ns in NAMESPACES}
|
||
built = launcher.build_application_mcp_servers(
|
||
profiles,
|
||
client_type="codex",
|
||
config_path="/cfg/profiles.json",
|
||
launch_nonce="launch-one",
|
||
now=NOW,
|
||
command="/venv/bin/python3",
|
||
args=["mcp_server.py"],
|
||
)
|
||
servers = built["mcpServers"]
|
||
self.assertEqual(len(servers), 5)
|
||
ids = {
|
||
servers[f"gitea-{ns}"]["env"]["GITEA_MCP_CLIENT_INSTANCE"]
|
||
for ns in NAMESPACES
|
||
}
|
||
self.assertEqual(len(ids), 1, ids)
|
||
shared = next(iter(ids))
|
||
self.assertTrue(fleet.assess_instance_identity(shared)["trusted"])
|
||
self.assertEqual(built["client_instance_id"], shared)
|
||
# No legacy placeholder on any production worker env.
|
||
for ns in NAMESPACES:
|
||
env = servers[f"gitea-{ns}"]["env"]
|
||
self.assertEqual(env["GITEA_MCP_CLIENT_INSTANCE"], shared)
|
||
self.assertEqual(
|
||
env["GITEA_MCP_INSTANCE_PROVENANCE"],
|
||
fleet.INSTANCE_ID_PROVENANCE_TRUSTED,
|
||
)
|
||
self.assertEqual(env["GITEA_MCP_CLIENT"], "codex")
|
||
self.assertEqual(env["GITEA_CLIENT_MANAGED"], "1")
|
||
self.assertFalse(env["GITEA_MCP_CLIENT_INSTANCE"].startswith("legacy-"))
|
||
|
||
proof = launcher.collect_instance_ids_from_mcp_servers(servers)
|
||
self.assertTrue(proof["shared_single_trusted_id"], proof)
|
||
|
||
def test_two_launches_receive_different_ids(self):
|
||
import mcp_application_launcher as launcher
|
||
|
||
profiles = {ns: f"prgs-{ns}" for ns in NAMESPACES}
|
||
a = launcher.build_application_mcp_servers(
|
||
profiles,
|
||
client_type="codex",
|
||
launch_nonce="L1",
|
||
now=NOW,
|
||
command="python3",
|
||
args=["mcp_server.py"],
|
||
)
|
||
b = launcher.build_application_mcp_servers(
|
||
profiles,
|
||
client_type="codex",
|
||
launch_nonce="L2",
|
||
now=NOW,
|
||
command="python3",
|
||
args=["mcp_server.py"],
|
||
)
|
||
self.assertNotEqual(a["client_instance_id"], b["client_instance_id"])
|
||
|
||
def test_resume_reuses_supplied_trusted_id(self):
|
||
import mcp_application_launcher as launcher
|
||
|
||
profiles = {ns: f"prgs-{ns}" for ns in NAMESPACES}
|
||
first = launcher.build_application_mcp_servers(
|
||
profiles,
|
||
client_type="claude_code",
|
||
launch_nonce="resume-src",
|
||
now=NOW,
|
||
command="python3",
|
||
args=["mcp_server.py"],
|
||
)
|
||
resumed = launcher.build_application_mcp_servers(
|
||
profiles,
|
||
client_type="claude_code",
|
||
client_instance_id=first["client_instance_id"],
|
||
command="python3",
|
||
args=["mcp_server.py"],
|
||
)
|
||
self.assertEqual(first["client_instance_id"], resumed["client_instance_id"])
|
||
|
||
def test_refuses_untrusted_instance_id_on_production_path(self):
|
||
import mcp_application_launcher as launcher
|
||
|
||
profiles = {ns: f"prgs-{ns}" for ns in NAMESPACES}
|
||
with self.assertRaises(ValueError):
|
||
launcher.build_application_mcp_servers(
|
||
profiles,
|
||
client_type="codex",
|
||
client_instance_id="pid-999",
|
||
command="python3",
|
||
args=["mcp_server.py"],
|
||
)
|
||
with self.assertRaises(ValueError):
|
||
launcher.namespace_worker_env(
|
||
profile_name="prgs-author",
|
||
client_type="codex",
|
||
client_instance_id="user-supplied-garbage",
|
||
)
|
||
|
||
def test_extra_env_cannot_override_trusted_instance_keys(self):
|
||
import mcp_application_launcher as launcher
|
||
|
||
trusted = fleet.generate_client_instance_id(
|
||
"codex", launch_nonce="seal", now=NOW
|
||
)
|
||
env = launcher.namespace_worker_env(
|
||
profile_name="prgs-author",
|
||
client_type="codex",
|
||
client_instance_id=trusted,
|
||
extra_env={
|
||
"GITEA_MCP_CLIENT_INSTANCE": "inst-spoofed-20260730T000000Z-deadbeefdead",
|
||
"GITEA_MCP_INSTANCE_PROVENANCE": "trusted_launcher",
|
||
"GITEA_CLIENT_MANAGED": "0",
|
||
"GITEA_MCP_CLIENT": "gemini",
|
||
"GITEA_AUTHOR_WORKTREE": "/ok/extra",
|
||
},
|
||
)
|
||
self.assertEqual(env["GITEA_MCP_CLIENT_INSTANCE"], trusted)
|
||
self.assertEqual(env["GITEA_CLIENT_MANAGED"], "1")
|
||
self.assertEqual(env["GITEA_MCP_CLIENT"], "codex")
|
||
self.assertEqual(env["GITEA_AUTHOR_WORKTREE"], "/ok/extra")
|
||
|
||
def test_gitea_config_launcher_entry_uses_production_path(self):
|
||
"""Production launcher_entry — not a test helper — mints trusted IDs."""
|
||
import gitea_config
|
||
|
||
entry_a = gitea_config.launcher_entry(
|
||
"prgs-author",
|
||
"/cfg/profiles.json",
|
||
client_type="codex",
|
||
launch_nonce="cfg-a",
|
||
)["gitea-tools"]
|
||
entry_b = gitea_config.launcher_entry(
|
||
"prgs-author",
|
||
"/cfg/profiles.json",
|
||
client_type="codex",
|
||
launch_nonce="cfg-b",
|
||
)["gitea-tools"]
|
||
id_a = entry_a["env"]["GITEA_MCP_CLIENT_INSTANCE"]
|
||
id_b = entry_b["env"]["GITEA_MCP_CLIENT_INSTANCE"]
|
||
self.assertNotEqual(id_a, id_b)
|
||
self.assertTrue(fleet.assess_instance_identity(id_a)["trusted"])
|
||
self.assertTrue(fleet.assess_instance_identity(id_b)["trusted"])
|
||
|
||
def test_multi_namespace_launcher_entries_api(self):
|
||
import gitea_config
|
||
|
||
profiles = {ns: f"prgs-{ns}" for ns in NAMESPACES}
|
||
built = gitea_config.multi_namespace_launcher_entries(
|
||
profiles,
|
||
client_type="grok",
|
||
config_path="/cfg/profiles.json",
|
||
launch_nonce="multi-api",
|
||
)
|
||
self.assertTrue(built["shared_instance_id_across_namespaces"])
|
||
self.assertEqual(built["namespace_count"], 5)
|
||
proof = __import__(
|
||
"mcp_application_launcher", fromlist=["*"]
|
||
).collect_instance_ids_from_mcp_servers(built["mcpServers"])
|
||
self.assertTrue(proof["shared_single_trusted_id"], proof)
|
||
|
||
def test_malformed_and_missing_fail_safely(self):
|
||
self.assertFalse(fleet.assess_instance_identity("")["trusted"])
|
||
self.assertFalse(fleet.assess_instance_identity(None)["trusted"])
|
||
self.assertFalse(fleet.assess_instance_identity("legacy-pid-1")["trusted"])
|
||
self.assertFalse(fleet.assess_instance_identity("not-an-inst-id")["trusted"])
|
||
self.assertFalse(fleet.assess_instance_identity("inst-")["trusted"])
|
||
self.assertFalse(fleet.assess_instance_identity("user-spoofed")["trusted"])
|
||
# Reserved inst- prefix from the launcher is trusted.
|
||
self.assertTrue(fleet.assess_instance_identity("inst-codex-1")["trusted"])
|
||
|
||
|
||
class InstanceAwareMutationGateB2Tests(unittest.TestCase):
|
||
"""#978 review 655 B2: mutation gate is instance-aware, not profile-only."""
|
||
|
||
def _ps_lines(self, rows):
|
||
# rows: list of (pid, lstart, command) — command ignored beyond mcp_server.py
|
||
header = " PID LSTART COMMAND\n"
|
||
body = "".join(
|
||
f"{pid} {lstart} /path/to/python mcp_server.py\n"
|
||
for pid, lstart, _env in rows
|
||
)
|
||
return header + body
|
||
|
||
def _side_effect(self, rows, *, self_pid=1000):
|
||
ps_out = self._ps_lines(rows)
|
||
env_by_pid = {str(pid): env for pid, _ls, env in rows}
|
||
|
||
def side_effect(args, **kwargs):
|
||
mock_run = mock.MagicMock()
|
||
if args[0] == "ps" and "eww" in args:
|
||
mock_run.stdout = env_by_pid.get(str(args[2]), "")
|
||
return mock_run
|
||
if args[0] == "ps":
|
||
mock_run.stdout = ps_out
|
||
return mock_run
|
||
if args[0] == "git":
|
||
mock_run.stdout = "deadbeef"
|
||
return mock_run
|
||
raise ValueError(args)
|
||
|
||
return side_effect
|
||
|
||
def test_two_valid_instances_same_profile_allowed(self):
|
||
import gitea_mcp_server as server
|
||
|
||
inst_a = fleet.generate_client_instance_id(
|
||
"codex", launch_nonce="gate-a", now=NOW
|
||
)
|
||
inst_b = fleet.generate_client_instance_id(
|
||
"codex", launch_nonce="gate-b", now=NOW
|
||
)
|
||
lstart = "Wed Jul 8 15:00:00 2026"
|
||
rows = [
|
||
(
|
||
2001,
|
||
lstart,
|
||
(
|
||
f"GITEA_MCP_PROFILE=prgs-author GITEA_CLIENT_MANAGED=1 "
|
||
f"GITEA_MCP_CLIENT_INSTANCE={inst_a} "
|
||
f"GITEA_MCP_WORKER_IDENTITY=worker-a "
|
||
f"GITEA_MCP_GENERATION_ID=gen-a"
|
||
),
|
||
),
|
||
(
|
||
2002,
|
||
lstart,
|
||
(
|
||
f"GITEA_MCP_PROFILE=prgs-author GITEA_CLIENT_MANAGED=1 "
|
||
f"GITEA_MCP_CLIENT_INSTANCE={inst_b} "
|
||
f"GITEA_MCP_WORKER_IDENTITY=worker-b "
|
||
f"GITEA_MCP_GENERATION_ID=gen-b"
|
||
),
|
||
),
|
||
]
|
||
with mock.patch("subprocess.run") as mock_run, mock.patch(
|
||
"os.path.getmtime", return_value=datetime(2026, 7, 8, 11, 0, 0).timestamp()
|
||
), mock.patch("os.path.exists", return_value=True), mock.patch(
|
||
"os.getpid", return_value=9999
|
||
):
|
||
mock_run.side_effect = self._side_effect(rows)
|
||
reasons = server._check_mcp_runtimes_diagnostics(
|
||
"create_issue", ["prgs-author"]
|
||
)
|
||
self.assertFalse(
|
||
any("Duplicate MCP server process(es) detected" in r for r in reasons),
|
||
reasons,
|
||
)
|
||
|
||
def test_duplicate_same_instance_and_namespace_blocked(self):
|
||
import gitea_mcp_server as server
|
||
|
||
inst = fleet.generate_client_instance_id(
|
||
"codex", launch_nonce="dup", now=NOW
|
||
)
|
||
lstart = "Wed Jul 8 15:00:00 2026"
|
||
rows = [
|
||
(
|
||
3001,
|
||
lstart,
|
||
(
|
||
f"GITEA_MCP_PROFILE=prgs-author GITEA_CLIENT_MANAGED=1 "
|
||
f"GITEA_MCP_CLIENT_INSTANCE={inst} "
|
||
f"GITEA_MCP_WORKER_IDENTITY=worker-1 "
|
||
f"GITEA_MCP_GENERATION_ID=gen-1"
|
||
),
|
||
),
|
||
(
|
||
3002,
|
||
lstart,
|
||
(
|
||
f"GITEA_MCP_PROFILE=prgs-author GITEA_CLIENT_MANAGED=1 "
|
||
f"GITEA_MCP_CLIENT_INSTANCE={inst} "
|
||
f"GITEA_MCP_WORKER_IDENTITY=worker-2 "
|
||
f"GITEA_MCP_GENERATION_ID=gen-2"
|
||
),
|
||
),
|
||
]
|
||
with mock.patch("subprocess.run") as mock_run, mock.patch(
|
||
"os.path.getmtime", return_value=datetime(2026, 7, 8, 11, 0, 0).timestamp()
|
||
), mock.patch("os.path.exists", return_value=True), mock.patch(
|
||
"os.getpid", return_value=9999
|
||
):
|
||
mock_run.side_effect = self._side_effect(rows)
|
||
reasons = server._check_mcp_runtimes_diagnostics(
|
||
"create_issue", ["prgs-author"]
|
||
)
|
||
self.assertTrue(
|
||
any("Duplicate MCP server process(es) detected" in r for r in reasons),
|
||
reasons,
|
||
)
|
||
self.assertTrue(
|
||
any("duplicate namespace" in r.lower() or "client_instance_id" in r for r in reasons),
|
||
reasons,
|
||
)
|
||
|
||
def test_missing_instance_evidence_still_fail_closed(self):
|
||
"""Two profile-sharing processes without trusted IDs remain blocked."""
|
||
import gitea_mcp_server as server
|
||
|
||
lstart = "Wed Jul 8 15:00:00 2026"
|
||
rows = [
|
||
(
|
||
4001,
|
||
lstart,
|
||
"GITEA_MCP_PROFILE=prgs-author GITEA_CLIENT_MANAGED=1",
|
||
),
|
||
(
|
||
4002,
|
||
lstart,
|
||
"GITEA_MCP_PROFILE=prgs-author GITEA_CLIENT_MANAGED=1",
|
||
),
|
||
]
|
||
with mock.patch("subprocess.run") as mock_run, mock.patch(
|
||
"os.path.getmtime", return_value=datetime(2026, 7, 8, 11, 0, 0).timestamp()
|
||
), mock.patch("os.path.exists", return_value=True), mock.patch(
|
||
"os.getpid", return_value=9999
|
||
):
|
||
mock_run.side_effect = self._side_effect(rows)
|
||
reasons = server._check_mcp_runtimes_diagnostics(
|
||
"create_issue", ["prgs-author"]
|
||
)
|
||
self.assertTrue(
|
||
any("Duplicate MCP server process(es) detected" in r for r in reasons),
|
||
reasons,
|
||
)
|
||
|
||
def test_single_instance_still_ok(self):
|
||
import gitea_mcp_server as server
|
||
|
||
inst = fleet.generate_client_instance_id(
|
||
"codex", launch_nonce="single", now=NOW
|
||
)
|
||
lstart = "Wed Jul 8 15:00:00 2026"
|
||
rows = [
|
||
(
|
||
5001,
|
||
lstart,
|
||
(
|
||
f"GITEA_MCP_PROFILE=prgs-author GITEA_CLIENT_MANAGED=1 "
|
||
f"GITEA_MCP_CLIENT_INSTANCE={inst} "
|
||
f"GITEA_MCP_WORKER_IDENTITY=worker-only "
|
||
f"GITEA_MCP_GENERATION_ID=gen-only"
|
||
),
|
||
),
|
||
]
|
||
with mock.patch("subprocess.run") as mock_run, mock.patch(
|
||
"os.path.getmtime", return_value=datetime(2026, 7, 8, 11, 0, 0).timestamp()
|
||
), mock.patch("os.path.exists", return_value=True), mock.patch(
|
||
"os.getpid", return_value=9999
|
||
):
|
||
mock_run.side_effect = self._side_effect(rows)
|
||
reasons = server._check_mcp_runtimes_diagnostics(
|
||
"create_issue", ["prgs-author"]
|
||
)
|
||
self.assertFalse(
|
||
any("Duplicate MCP server process(es) detected" in r for r in reasons),
|
||
reasons,
|
||
)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|