fix(controller): production client_instance_id launch path and instance-aware mutation gate

Remediate review 655 blockers on PR #979 (issue #978):

B1 — Production launcher (mcp_application_launcher) mints one trusted
client_instance_id per application launch and propagates it to all five
namespace workers via GITEA_MCP_CLIENT_INSTANCE. launcher_entry and
multi_namespace_launcher_entries use that path. Workers never invent a
trusted ID; missing/malformed/user-supplied values fail closed.

B2 — _check_mcp_runtimes_diagnostics is instance-aware: two legitimate
instances sharing a profile are allowed when each has a distinct trusted
client_instance_id; duplicate workers for the same (instance, profile)
still fail closed. Worker identity/generation exported for peer scans.

Tests cover shared ID across five namespaces, distinct launches, multi-
instance same profile, same-instance duplicates, untrusted attribution,
and the production launcher path.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-30 10:12:16 -04:00
co-authored by Claude Opus 4.8
parent 4a1cc63e94
commit 0dbff6dcd5
7 changed files with 1055 additions and 71 deletions
+32 -5
View File
@@ -49,27 +49,54 @@ model (#949 assumption) as the operating rule for multi-instance fleets.
## How five workers join one instance ## How five workers join one instance
1. The host starts one application instance (for example one Codex session). 1. The host starts one application instance (for example one Codex session).
2. The trusted launcher mints `client_instance_id` (see 2. The **production application launcher**
`mcp_fleet_snapshot.generate_client_instance_id`) and injects: (`mcp_application_launcher.build_application_mcp_servers` /
`gitea_config.multi_namespace_launcher_entries`) mints exactly one
`client_instance_id` via `mcp_fleet_snapshot.generate_client_instance_id`
and injects the same env into every namespace worker:
```text ```text
GITEA_MCP_CLIENT=<client_type> GITEA_MCP_CLIENT=<client_type>
GITEA_MCP_CLIENT_INSTANCE=<client_instance_id> GITEA_MCP_CLIENT_INSTANCE=<client_instance_id>
GITEA_MCP_INSTANCE_PROVENANCE=trusted_launcher
GITEA_MCP_CLIENT_SESSION=<session_id> # optional but recommended GITEA_MCP_CLIENT_SESSION=<session_id> # optional but recommended
GITEA_MCP_FLEET_RUN_ID=<enrollment id> # when on an approved canary GITEA_MCP_FLEET_RUN_ID=<enrollment id> # when on an approved canary
GITEA_CLIENT_MANAGED=1
GITEA_MCP_PROFILE=<role-profile>
``` ```
3. The launcher starts the five MCP namespace processes (or attaches five 3. The MCP client starts the five namespace processes (`gitea-author`,
role profiles) with **that same environment**. `gitea-reviewer`, `gitea-merger`, `gitea-controller`, `gitea-reconciler`)
from that generated `mcpServers` map — each entry carries the **same**
instance ID.
4. Each worker registers once into the worker registry with its own 4. Each worker registers once into the worker registry with its own
`worker_identity`, `namespace`, `generation_id`, and PID, but the shared `worker_identity`, `namespace`, `generation_id`, and PID, but the shared
`client_instance_id`. `client_instance_id`. Workers never mint a trusted instance ID themselves.
Only IDs matching the launcher format `inst-<client>-<timestamp>-<digest>`
are trusted. Missing, `legacy-pid-*` / `pid-*` placeholders, and ordinary
user-supplied strings fail closed as untrusted and cannot authorize
multi-instance fleet mutation safety.
Worker reconnect (same process, same registration) keeps the instance ID. Worker reconnect (same process, same registration) keeps the instance ID.
Worker restart (new process) mints a new worker identity and generation but Worker restart (new process) mints a new worker identity and generation but
must still receive the same `GITEA_MCP_CLIENT_INSTANCE` from the parent must still receive the same `GITEA_MCP_CLIENT_INSTANCE` from the parent
application if it is the same launch. Full application restart mints a new application if it is the same launch. Full application restart mints a new
`client_instance_id` (call the launcher without a prior ID).
### Mutation gate (instance-aware)
The capability / runtime diagnostic gate
(`_check_mcp_runtimes_diagnostics`) is **instance-aware**:
* Two legitimate application instances that share a profile (same
`GITEA_MCP_PROFILE`) are allowed when each has a distinct trusted
`client_instance_id`. `client_instance_id`.
* More than one live worker for the same
`(client_instance_id, profile/namespace)` fails closed as a duplicate
namespace worker.
* Multiple processes sharing a profile **without** trusted instance
evidence still fail closed (indistinguishable from a duplicate).
## Approved fleet manifest ## Approved fleet manifest
+67 -15
View File
@@ -1206,6 +1206,12 @@ RECOGNIZED_GITEA_ENV_KEYS = frozenset({
# #978: operator-approved fleet enrollment identity shared by one launch. # #978: operator-approved fleet enrollment identity shared by one launch.
"GITEA_MCP_FLEET_RUN_ID", "GITEA_MCP_FLEET_RUN_ID",
"GITEA_MCP_PROCESS_IDENTITY", "GITEA_MCP_PROCESS_IDENTITY",
# #978 B1/B2: launcher-sealed provenance + peer-visible worker identity so
# the instance-aware mutation gate can distinguish two legitimate
# application instances that share a profile from a true duplicate worker.
"GITEA_MCP_INSTANCE_PROVENANCE",
"GITEA_MCP_WORKER_IDENTITY",
"GITEA_MCP_GENERATION_ID",
# #975 review 652 B1: production also consumes HEARTBEAT_INTERVAL_ENV from # #975 review 652 B1: production also consumes HEARTBEAT_INTERVAL_ENV from
# mcp_worker_identity via gitea_mcp_server._start_worker_heartbeat. Omitting # mcp_worker_identity via gitea_mcp_server._start_worker_heartbeat. Omitting
# it reproduced the same unsupported-env → runtime_reconnect_required # it reproduced the same unsupported-env → runtime_reconnect_required
@@ -1239,24 +1245,70 @@ def get_unconsumed_gitea_env_overrides(env=None) -> dict[str, str]:
return unconsumed return unconsumed
def launcher_entry(profile_name, config_path=None): def launcher_entry(
profile_name,
config_path=None,
*,
client_type=None,
client_instance_id=None,
fleet_run_id=None,
session_id=None,
launch_nonce=None,
):
"""Return a thin MCP launcher entry for *profile_name*. """Return a thin MCP launcher entry for *profile_name*.
Contains command/args and the GITEA_MCP_* / GITEA_CLIENT_MANAGED env vars — never a token Contains command/args and the GITEA_MCP_* / GITEA_CLIENT_MANAGED env vars —
or password. Suitable for Claude / Gemini / Codex ``mcpServers`` blocks. never a token or password. Suitable for Claude / Gemini / Codex
``mcpServers`` blocks.
#978 B1: production launches mint (or reuse) a trusted
``GITEA_MCP_CLIENT_INSTANCE`` so workers never fall back to the legacy
placeholder identity on the real serve path. Pass *client_instance_id* to
resume the same launch; omit it for a fresh launch (new ID).
""" """
command, args = server_command() import mcp_application_launcher as app_launcher
return {
"gitea-tools": { built = app_launcher.launcher_entry_for_profile(
"command": command, profile_name,
"args": args, client_type=client_type or "unknown",
"env": { config_path=config_path or DEFAULT_CONFIG_PATH,
"GITEA_MCP_CONFIG": config_path or DEFAULT_CONFIG_PATH, client_instance_id=client_instance_id,
"GITEA_MCP_PROFILE": profile_name, fleet_run_id=fleet_run_id,
"GITEA_CLIENT_MANAGED": "1", session_id=session_id,
}, launch_nonce=launch_nonce,
} server_key="gitea-tools",
} )
# Public shape stays {server_key: {command, args, env}} for existing callers.
return {"gitea-tools": built["gitea-tools"]}
def multi_namespace_launcher_entries(
profile_by_namespace,
*,
client_type,
config_path=None,
client_instance_id=None,
fleet_run_id=None,
session_id=None,
launch_nonce=None,
):
"""Build production mcpServers for all five namespaces of one application launch.
One shared trusted ``client_instance_id`` is minted (or reused) and
propagated to every namespace worker env. See
:func:`mcp_application_launcher.build_application_mcp_servers`.
"""
import mcp_application_launcher as app_launcher
return app_launcher.build_application_mcp_servers(
profile_by_namespace,
client_type=client_type,
config_path=config_path or DEFAULT_CONFIG_PATH,
client_instance_id=client_instance_id,
fleet_run_id=fleet_run_id,
session_id=session_id,
launch_nonce=launch_nonce,
)
+130 -33
View File
@@ -15734,6 +15734,9 @@ CLIENT_NAME_ENV = "GITEA_MCP_CLIENT"
CLIENT_INSTANCE_ENV = "GITEA_MCP_CLIENT_INSTANCE" CLIENT_INSTANCE_ENV = "GITEA_MCP_CLIENT_INSTANCE"
CLIENT_SESSION_ENV = "GITEA_MCP_CLIENT_SESSION" CLIENT_SESSION_ENV = "GITEA_MCP_CLIENT_SESSION"
FLEET_RUN_ENV = "GITEA_MCP_FLEET_RUN_ID" FLEET_RUN_ENV = "GITEA_MCP_FLEET_RUN_ID"
INSTANCE_PROVENANCE_ENV = "GITEA_MCP_INSTANCE_PROVENANCE"
WORKER_IDENTITY_ENV = "GITEA_MCP_WORKER_IDENTITY"
GENERATION_ID_ENV = "GITEA_MCP_GENERATION_ID"
def _worker_registry(): def _worker_registry():
@@ -15760,28 +15763,51 @@ def _client_identity_hints() -> dict:
"""What the launcher told us about itself (#948 / #975 / #978). """What the launcher told us about itself (#948 / #975 / #978).
``client_instance_id`` is established by the trusted application launcher ``client_instance_id`` is established by the trusted application launcher
once per launch and shared by all five namespace workers. When the launcher once per launch and shared by all five namespace workers. Workers inherit
key is absent we still register under an explicit *legacy* placeholder so that value from the production launcher env and never mint a trusted ID
diagnostic reads work, but the registration is marked incomplete and cannot themselves. When the launcher key is absent or untrusted we still register
authorize multi-instance fleet mutation safety. under an explicit *legacy* placeholder so diagnostic reads work, but the
registration is marked incomplete and cannot authorize multi-instance fleet
mutation safety.
""" """
import mcp_application_launcher as app_launcher
import mcp_fleet_snapshot import mcp_fleet_snapshot
inherited = app_launcher.inherit_or_refuse_client_instance(os.environ)
client_name = (os.environ.get(CLIENT_NAME_ENV) or "").strip() or None client_name = (os.environ.get(CLIENT_NAME_ENV) or "").strip() or None
raw_instance = (os.environ.get(CLIENT_INSTANCE_ENV) or "").strip() or None instance = {
instance = mcp_fleet_snapshot.assess_instance_identity(raw_instance) "client_instance_id": inherited.get("client_instance_id"),
# Legacy placeholder only when the launcher omitted the key — never invent a "provenance": inherited.get("provenance"),
# trusted ID from PID proximity. "trusted": bool(inherited.get("trusted")),
"complete": bool(inherited.get("complete")),
"launcher_sealed": bool(inherited.get("launcher_sealed")),
}
# Legacy placeholder only when the launcher omitted a usable key — never
# invent a trusted ID from PID proximity or ordinary user env.
if not instance["client_instance_id"] or not instance["trusted"]:
if not instance["client_instance_id"]: if not instance["client_instance_id"]:
placeholder = f"legacy-pid-{os.getpid()}" placeholder = f"legacy-pid-{os.getpid()}"
instance = mcp_fleet_snapshot.assess_instance_identity( fallback = mcp_fleet_snapshot.assess_instance_identity(
placeholder, source="pid_fallback" placeholder, source="pid_fallback"
) )
instance = {
"client_instance_id": fallback["client_instance_id"],
"provenance": fallback["provenance"],
"trusted": False,
"complete": False,
"launcher_sealed": False,
}
else:
# Malformed / untrusted user-supplied value: keep it for diagnosis
# but never mark trusted.
instance["trusted"] = False
instance["launcher_sealed"] = False
return { return {
"client_name": client_name, "client_name": client_name,
"client_instance_id": instance["client_instance_id"], "client_instance_id": instance["client_instance_id"],
"instance_id_provenance": instance["provenance"], "instance_id_provenance": instance["provenance"],
"instance_identity_trusted": instance["trusted"], "instance_identity_trusted": instance["trusted"],
"instance_launcher_sealed": instance.get("launcher_sealed", False),
"session_id": (os.environ.get(CLIENT_SESSION_ENV) or "").strip() "session_id": (os.environ.get(CLIENT_SESSION_ENV) or "").strip()
or f"proc-{os.getpid()}-{_process_boot_head_sha or 'nohead'}", or f"proc-{os.getpid()}-{_process_boot_head_sha or 'nohead'}",
"fleet_run_id": (os.environ.get(FLEET_RUN_ENV) or "").strip() or None, "fleet_run_id": (os.environ.get(FLEET_RUN_ENV) or "").strip() or None,
@@ -15857,6 +15883,19 @@ def _active_worker_identity() -> str | None:
if outcome.get("registered"): if outcome.get("registered"):
_WORKER_IDENTITY = identity _WORKER_IDENTITY = identity
_WORKER_GENERATION = generation _WORKER_GENERATION = generation
# #978 B2: export worker/generation/instance into this process
# env so peer process scans (and the instance-aware mutation
# gate) can attribute live workers without treating shared
# profiles as duplicates. Never overwrite a launcher-sealed
# client_instance_id with a different value.
os.environ[WORKER_IDENTITY_ENV] = identity
os.environ[GENERATION_ID_ENV] = generation
if hints.get("client_instance_id") and not (
os.environ.get(CLIENT_INSTANCE_ENV) or ""
).strip():
os.environ[CLIENT_INSTANCE_ENV] = str(
hints["client_instance_id"]
)
# #975: registration is the only moment identity and fencing # #975: registration is the only moment identity and fencing
# epoch are both known, so the heartbeat supervisor is started # epoch are both known, so the heartbeat supervisor is started
# here. Without it ``last_heartbeat_at`` never left # here. Without it ``last_heartbeat_at`` never left
@@ -22375,6 +22414,20 @@ def _check_mcp_runtimes_diagnostics(task: str, matching_profiles: list[str]) ->
) )
peer_worker_identity = peer_env.get("GITEA_MCP_WORKER_IDENTITY") peer_worker_identity = peer_env.get("GITEA_MCP_WORKER_IDENTITY")
peer_generation = peer_env.get("GITEA_MCP_GENERATION_ID") peer_generation = peer_env.get("GITEA_MCP_GENERATION_ID")
# #978 B2: instance attribution for the live fleet gate. Two legitimate
# application instances may share a profile when each has a distinct
# trusted client_instance_id; only same-(instance, profile/namespace)
# duplicates remain blocked.
import mcp_fleet_snapshot as _fleet
peer_instance_raw = peer_env.get("GITEA_MCP_CLIENT_INSTANCE")
peer_instance_assess = _fleet.assess_instance_identity(peer_instance_raw)
peer_client_instance = (
peer_instance_assess["client_instance_id"]
if peer_instance_assess.get("trusted")
else None
)
peer_instance_trusted = bool(peer_instance_assess.get("trusted"))
for env_match in re.finditer(r'\b(GITEA_[A-Z0-9_]+)=([^\s]+)', env_out): for env_match in re.finditer(r'\b(GITEA_[A-Z0-9_]+)=([^\s]+)', env_out):
k, v = env_match.group(1), env_match.group(2) k, v = env_match.group(1), env_match.group(2)
@@ -22392,6 +22445,9 @@ def _check_mcp_runtimes_diagnostics(task: str, matching_profiles: list[str]) ->
"is_client_managed": is_client_managed, "is_client_managed": is_client_managed,
"worker_identity": peer_worker_identity, "worker_identity": peer_worker_identity,
"generation_id": peer_generation, "generation_id": peer_generation,
"client_instance_id": peer_client_instance,
"instance_identity_trusted": peer_instance_trusted,
"raw_client_instance_id": peer_instance_raw,
} }
if profile not in all_profile_procs: if profile not in all_profile_procs:
all_profile_procs[profile] = [] all_profile_procs[profile] = []
@@ -22399,44 +22455,85 @@ def _check_mcp_runtimes_diagnostics(task: str, matching_profiles: list[str]) ->
running_profiles = {} running_profiles = {}
for profile, procs in all_profile_procs.items(): for profile, procs in all_profile_procs.items():
# #948 AC40/AC43: sharing a profile is legitimate — profile is a # #978 B2 / #948 AC40: sharing a profile is legitimate when each live
# reusable capability definition, not a worker identity. What is *not* # process belongs to a distinct trusted application instance. The
# legitimate is reusing one worker identity, or two live sessions # permanent model is instance-aware — not exactly_one_per_profile.
# claiming one generation. Distinctness has to be proven, though: # Fail closed only when:
# processes carrying no identity evidence are indistinguishable, so # * more than one live worker claims the same (client_instance_id,
# they stay classified as duplicates and keep the #686 wall intact. # profile/namespace), or
identified = [p for p in procs if p.get("worker_identity")] # * worker identity / generation is reused, or
# * multiple processes share a profile without trusted instance
# evidence (indistinguishable → treat as duplicate).
# Group by trusted client_instance_id (untrusted/missing → one bucket).
by_instance: dict[str, list[dict]] = {}
for p in procs:
if p.get("instance_identity_trusted") and p.get("client_instance_id"):
key = str(p["client_instance_id"])
else:
key = "__untrusted_or_missing__"
by_instance.setdefault(key, []).append(p)
for instance_key, group in by_instance.items():
if len(group) <= 1:
continue
identified = [p for p in group if p.get("worker_identity")]
distinct_identities = {p["worker_identity"] for p in identified} distinct_identities = {p["worker_identity"] for p in identified}
all_identified = len(identified) == len(procs) all_identified = len(identified) == len(group)
duplicate_identity = len(identified) != len(distinct_identities) duplicate_identity = len(identified) != len(distinct_identities)
contested_generation = any( contested_generation = any(
len({p["worker_identity"] for p in identified if p.get("generation_id") == gen}) > 1 len(
for gen in {p.get("generation_id") for p in identified if p.get("generation_id")} {
p["worker_identity"]
for p in identified
if p.get("generation_id") == gen
}
) )
> 1
if len(procs) > 1 and ( for gen in {
p.get("generation_id")
for p in identified
if p.get("generation_id")
}
)
# Same trusted (instance, profile) with >1 live process is always a
# duplicate-namespace-worker, even if worker identities differ —
# one application launch gets exactly one worker per namespace.
same_trusted_instance = instance_key != "__untrusted_or_missing__"
if same_trusted_instance or (
not all_identified or duplicate_identity or contested_generation not all_identified or duplicate_identity or contested_generation
): ):
pids_str = ", ".join(str(p["pid"]) for p in procs) pids_str = ", ".join(str(p["pid"]) for p in group)
if duplicate_identity or contested_generation: if same_trusted_instance:
detail = ( detail = (
"The same worker identity or generation is claimed more than once, " f"More than one live worker for profile '{profile}' under "
"so these are genuine duplicates rather than independent workers." f"client_instance_id {instance_key!r} (duplicate namespace "
"worker). Two legitimate application instances with "
"distinct trusted client_instance_id values may share a "
"profile; this collision does not."
)
elif duplicate_identity or contested_generation:
detail = (
"The same worker identity or generation is claimed more "
"than once, so these are genuine duplicates rather than "
"independent workers."
) )
else: else:
detail = ( detail = (
"Manual or duplicate launches defeat staleness detection and cannot " "Multiple processes share this profile without distinct "
"receive client stdio." "trusted client_instance_id evidence, so they cannot be "
"told apart from a duplicate MCP process. Relaunch via "
"the production application launcher so each instance "
"receives a trusted GITEA_MCP_CLIENT_INSTANCE."
) )
reasons.append( reasons.append(
f"stale-runtime: Duplicate MCP server process(es) detected for profile '{profile}' (PIDs: {pids_str}). " f"stale-runtime: Duplicate MCP server process(es) detected "
f"for profile '{profile}' (PIDs: {pids_str}). "
+ detail + detail
) )
# Otherwise: several independently identified workers share one profile. # Multiple trusted instances sharing one profile intentionally produce
# No reason is appended, deliberately. Every reason this function # no reason here. Callers raise every reason as a hard RuntimeError, so
# returns is raised as a hard RuntimeError by its callers, so recording # treating legitimate multi-instance concurrency as "informational"
# legitimate concurrency here as "informational" would block exactly the # would re-introduce the profile-only wall #978 removes.
# case #948 exists to permit.
client_procs = [p for p in procs if p["is_client_managed"]] client_procs = [p for p in procs if p["is_client_managed"]]
if client_procs: if client_procs:
client_procs.sort(key=lambda p: p["start_time"], reverse=True) client_procs.sort(key=lambda p: p["start_time"], reverse=True)
+393
View File
@@ -0,0 +1,393 @@
"""Production application launcher for multi-namespace MCP fleets (#978 B1).
One real LLM application launch mints exactly one trusted
``client_instance_id`` and propagates it to every Gitea MCP namespace worker
started for that launch. Workers never invent a trusted instance identity from
PID proximity, timestamps, or ordinary untrusted environment values.
This module is the production serve-path authority for instance identity.
Tests and fixtures may call the same functions, but production registration
receives the identity from the env this launcher builds — not from a hand-set
test-only helper that bypasses it.
"""
from __future__ import annotations
import os
import secrets
from typing import Any, Mapping
import mcp_fleet_snapshot as fleet
import mcp_worker_identity as mwi
#: Canonical MCP server names for the five role namespaces.
NAMESPACE_SERVER_NAMES: tuple[str, ...] = (
"gitea-author",
"gitea-reviewer",
"gitea-merger",
"gitea-controller",
"gitea-reconciler",
)
#: Map MCP server name → role/namespace kind.
SERVER_TO_NAMESPACE: dict[str, str] = {
"gitea-author": "author",
"gitea-reviewer": "reviewer",
"gitea-merger": "merger",
"gitea-controller": "controller",
"gitea-reconciler": "reconciler",
}
SANCTIONED_NAMESPACES: tuple[str, ...] = (
"author",
"reviewer",
"merger",
"controller",
"reconciler",
)
# Env the launcher may set on every worker of one application launch.
CLIENT_NAME_ENV = "GITEA_MCP_CLIENT"
CLIENT_INSTANCE_ENV = fleet.CLIENT_INSTANCE_ENV
FLEET_RUN_ENV = fleet.FLEET_RUN_ENV
CLIENT_SESSION_ENV = "GITEA_MCP_CLIENT_SESSION"
CLIENT_MANAGED_ENV = "GITEA_CLIENT_MANAGED"
PROFILE_ENV = "GITEA_MCP_PROFILE"
CONFIG_ENV = "GITEA_MCP_CONFIG"
INSTANCE_PROVENANCE_ENV = "GITEA_MCP_INSTANCE_PROVENANCE"
WORKER_IDENTITY_ENV = "GITEA_MCP_WORKER_IDENTITY"
GENERATION_ID_ENV = "GITEA_MCP_GENERATION_ID"
# Marker the trusted launcher alone writes; ordinary user env without this
# marker is never classified as launcher-trusted provenance.
LAUNCHER_PROVENANCE_VALUE = fleet.INSTANCE_ID_PROVENANCE_TRUSTED
def mint_application_launch(
client_type: str | None,
*,
launch_nonce: str | None = None,
fleet_run_id: str | None = None,
session_id: str | None = None,
now=None,
) -> dict[str, Any]:
"""Mint one trusted application-instance identity for a production launch.
Called exactly once per real application launch. The returned
``client_instance_id`` is injected into every namespace worker environment
for that launch. A second call (separate launch) yields a different ID.
"""
client = mwi.normalize_client_name(client_type)
instance_id = fleet.generate_client_instance_id(
client, launch_nonce=launch_nonce, now=now
)
assessment = fleet.assess_instance_identity(instance_id)
if not assessment["trusted"]:
# generate_client_instance_id always produces a trusted format; fail
# closed if that invariant ever breaks rather than shipping untrusted.
raise RuntimeError(
f"launcher produced untrusted client_instance_id {instance_id!r}: "
f"{assessment.get('reasons')}"
)
session = (session_id or "").strip() or f"launch-{secrets.token_hex(12)}"
return {
"client_type": client,
"client_instance_id": instance_id,
"instance_id_provenance": LAUNCHER_PROVENANCE_VALUE,
"instance_identity_trusted": True,
"fleet_run_id": (fleet_run_id or "").strip() or None,
"session_id": session,
"namespaces": list(SANCTIONED_NAMESPACES),
"namespace_server_names": list(NAMESPACE_SERVER_NAMES),
}
def namespace_worker_env(
*,
profile_name: str,
client_type: str | None,
client_instance_id: str,
config_path: str | None = None,
fleet_run_id: str | None = None,
session_id: str | None = None,
extra_env: Mapping[str, str] | None = None,
) -> dict[str, str]:
"""Build the environment for one namespace worker of a trusted launch.
Never invents a client_instance_id. The caller must supply the launch-minted
identity so all five workers receive the same value.
"""
assessment = fleet.assess_instance_identity(client_instance_id)
if not assessment["trusted"]:
raise ValueError(
"namespace_worker_env refuses untrusted client_instance_id "
f"{client_instance_id!r}: {assessment.get('reasons')}"
)
client = mwi.normalize_client_name(client_type)
env: dict[str, str] = {
PROFILE_ENV: str(profile_name),
CLIENT_MANAGED_ENV: "1",
"GITEA_MCP_CLIENT": client,
CLIENT_INSTANCE_ENV: assessment["client_instance_id"],
INSTANCE_PROVENANCE_ENV: LAUNCHER_PROVENANCE_VALUE,
}
if config_path:
env[CONFIG_ENV] = str(config_path)
if fleet_run_id:
env[FLEET_RUN_ENV] = str(fleet_run_id)
if session_id:
env[CLIENT_SESSION_ENV] = str(session_id)
if extra_env:
# Never let untrusted callers override the trusted instance keys.
protected = {
CLIENT_INSTANCE_ENV,
INSTANCE_PROVENANCE_ENV,
"GITEA_MCP_CLIENT",
CLIENT_MANAGED_ENV,
}
for key, value in extra_env.items():
if key in protected:
continue
env[str(key)] = str(value)
return env
def build_application_mcp_servers(
profile_by_namespace: Mapping[str, str],
*,
client_type: str | None,
config_path: str | None = None,
client_instance_id: str | None = None,
fleet_run_id: str | None = None,
session_id: str | None = None,
launch_nonce: str | None = None,
command: str | None = None,
args: list[str] | None = None,
now=None,
) -> dict[str, Any]:
"""Build a production ``mcpServers`` map for one application launch.
Mints one ``client_instance_id`` when *client_instance_id* is omitted (fresh
launch). When the caller supplies a previously minted trusted ID (resume of
the same launch / config rewrite), that ID is reused so reconnect keeps
attribution. A full new application restart omits the ID and receives a
fresh mint.
Every namespace server entry receives the **same** instance ID. Separate
calls with no supplied ID receive distinct IDs.
"""
missing = [
ns for ns in SANCTIONED_NAMESPACES if not profile_by_namespace.get(ns)
]
if missing:
raise ValueError(
"build_application_mcp_servers requires a profile for every "
f"sanctioned namespace; missing: {missing}"
)
if client_instance_id is None:
launch = mint_application_launch(
client_type,
launch_nonce=launch_nonce,
fleet_run_id=fleet_run_id,
session_id=session_id,
now=now,
)
else:
assessment = fleet.assess_instance_identity(client_instance_id)
if not assessment["trusted"]:
raise ValueError(
"refusing to propagate untrusted client_instance_id "
f"{client_instance_id!r} into production launch envs: "
f"{assessment.get('reasons')}"
)
launch = {
"client_type": mwi.normalize_client_name(client_type),
"client_instance_id": assessment["client_instance_id"],
"instance_id_provenance": LAUNCHER_PROVENANCE_VALUE,
"instance_identity_trusted": True,
"fleet_run_id": (fleet_run_id or "").strip() or None,
"session_id": (session_id or "").strip()
or f"launch-{secrets.token_hex(12)}",
"namespaces": list(SANCTIONED_NAMESPACES),
"namespace_server_names": list(NAMESPACE_SERVER_NAMES),
}
# Resolve command/args from the production server entry when not provided.
if command is None or args is None:
import gitea_config
cmd, cmd_args = gitea_config.server_command()
command = command or cmd
args = args if args is not None else list(cmd_args)
servers: dict[str, Any] = {}
shared_id = launch["client_instance_id"]
for namespace in SANCTIONED_NAMESPACES:
server_name = f"gitea-{namespace}"
profile = profile_by_namespace[namespace]
env = namespace_worker_env(
profile_name=profile,
client_type=launch["client_type"],
client_instance_id=shared_id,
config_path=config_path,
fleet_run_id=launch.get("fleet_run_id"),
session_id=launch.get("session_id"),
)
servers[server_name] = {
"command": command,
"args": list(args),
"env": env,
}
return {
"mcpServers": servers,
"launch": launch,
"client_instance_id": shared_id,
"client_type": launch["client_type"],
"namespaces": list(SANCTIONED_NAMESPACES),
"shared_instance_id_across_namespaces": True,
"namespace_count": len(SANCTIONED_NAMESPACES),
}
def launcher_entry_for_profile(
profile_name: str,
*,
client_type: str | None = None,
config_path: str | None = None,
client_instance_id: str | None = None,
fleet_run_id: str | None = None,
session_id: str | None = None,
server_key: str = "gitea-tools",
launch_nonce: str | None = None,
now=None,
) -> dict[str, Any]:
"""Thin single-server production launcher entry with trusted instance ID.
Used when only one namespace is being configured. Still mints (or reuses)
a trusted ``client_instance_id`` so production never relies on the legacy
placeholder identity for normal launches.
"""
import gitea_config
if client_instance_id is None:
launch = mint_application_launch(
client_type or "unknown",
launch_nonce=launch_nonce,
fleet_run_id=fleet_run_id,
session_id=session_id,
now=now,
)
client_instance_id = launch["client_instance_id"]
client = launch["client_type"]
fleet_run = launch.get("fleet_run_id")
session = launch.get("session_id")
else:
assessment = fleet.assess_instance_identity(client_instance_id)
if not assessment["trusted"]:
raise ValueError(
f"untrusted client_instance_id {client_instance_id!r}"
)
client = mwi.normalize_client_name(client_type)
fleet_run = (fleet_run_id or "").strip() or None
session = (session_id or "").strip() or None
client_instance_id = assessment["client_instance_id"]
command, args = gitea_config.server_command()
env = namespace_worker_env(
profile_name=profile_name,
client_type=client,
client_instance_id=client_instance_id,
config_path=config_path or gitea_config.DEFAULT_CONFIG_PATH,
fleet_run_id=fleet_run,
session_id=session,
)
return {
server_key: {
"command": command,
"args": args,
"env": env,
},
"client_instance_id": client_instance_id,
"client_type": client,
}
def collect_instance_ids_from_mcp_servers(
mcp_servers: Mapping[str, Any],
) -> dict[str, Any]:
"""Inspect a production mcpServers map for shared instance attribution.
Returns the unique set of client_instance_id values across gitea-* servers
and whether all five namespaces share exactly one trusted ID.
"""
ids: list[str] = []
by_server: dict[str, str | None] = {}
for name in NAMESPACE_SERVER_NAMES:
entry = mcp_servers.get(name) or {}
env = entry.get("env") or {}
raw = (env.get(CLIENT_INSTANCE_ENV) or "").strip() or None
by_server[name] = raw
if raw:
ids.append(raw)
unique = sorted(set(ids))
trusted = [
i
for i in unique
if fleet.assess_instance_identity(i)["trusted"]
]
return {
"server_instance_ids": by_server,
"unique_instance_ids": unique,
"trusted_instance_ids": trusted,
"shared_single_trusted_id": (
len(unique) == 1
and len(trusted) == 1
and all(by_server.get(n) == unique[0] for n in NAMESPACE_SERVER_NAMES)
),
"namespace_server_count": sum(
1 for n in NAMESPACE_SERVER_NAMES if n in mcp_servers
),
}
def inherit_or_refuse_client_instance(
env: Mapping[str, str] | None = None,
) -> dict[str, Any]:
"""Resolve instance identity for a worker process at serve time.
Production workers inherit the launcher-issued ID. They never mint a
trusted ID themselves. Missing / legacy / malformed values fail soft into
an untrusted assessment so registration can still record a diagnostic row
without authorizing multi-instance fleet mutation.
"""
source = dict(env if env is not None else os.environ)
raw = (source.get(CLIENT_INSTANCE_ENV) or "").strip() or None
provenance_marker = (source.get(INSTANCE_PROVENANCE_ENV) or "").strip()
assessment = fleet.assess_instance_identity(raw)
# Ordinary user-supplied values without launcher provenance marker are
# still format-checked by assess_instance_identity. When the format is
# trusted but the launcher marker is absent, keep the ID but note that
# provenance is not launcher-sealed (operator hand-set or legacy config).
if assessment["trusted"] and provenance_marker != LAUNCHER_PROVENANCE_VALUE:
assessment = dict(assessment)
assessment["launcher_sealed"] = False
assessment["reasons"] = list(assessment.get("reasons") or []) + [
f"{INSTANCE_PROVENANCE_ENV} is not {LAUNCHER_PROVENANCE_VALUE!r}; "
"identity format is valid but not sealed by the production launcher"
]
else:
assessment = dict(assessment)
assessment["launcher_sealed"] = bool(
assessment["trusted"]
and provenance_marker == LAUNCHER_PROVENANCE_VALUE
)
assessment["fleet_run_id"] = (source.get(FLEET_RUN_ENV) or "").strip() or None
assessment["session_id"] = (
(source.get(CLIENT_SESSION_ENV) or "").strip() or None
)
assessment["client_type"] = mwi.normalize_client_name(
(source.get("GITEA_MCP_CLIENT") or "").strip() or None
)
return assessment
+23 -1
View File
@@ -88,8 +88,13 @@ INSTANCE_ID_PROVENANCE_MISSING = "missing"
CLIENT_INSTANCE_ENV = "GITEA_MCP_CLIENT_INSTANCE" CLIENT_INSTANCE_ENV = "GITEA_MCP_CLIENT_INSTANCE"
FLEET_RUN_ENV = "GITEA_MCP_FLEET_RUN_ID" FLEET_RUN_ENV = "GITEA_MCP_FLEET_RUN_ID"
PROCESS_IDENTITY_ENV = "GITEA_MCP_PROCESS_IDENTITY" PROCESS_IDENTITY_ENV = "GITEA_MCP_PROCESS_IDENTITY"
INSTANCE_PROVENANCE_ENV = "GITEA_MCP_INSTANCE_PROVENANCE"
_LEGACY_INSTANCE_PREFIXES = ("pid-", "proc-", "legacy-") _LEGACY_INSTANCE_PREFIXES = ("pid-", "proc-", "legacy-")
#: Trusted launcher-issued IDs use the reserved ``inst-`` prefix
#: (see :func:`generate_client_instance_id`). Ordinary user-supplied strings
#: without that prefix never count as trusted attribution.
_TRUSTED_INSTANCE_PREFIX = "inst-"
def _utc_now() -> datetime: def _utc_now() -> datetime:
@@ -131,7 +136,10 @@ def assess_instance_identity(
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Classify whether a client_instance_id is trusted enough for mutation. """Classify whether a client_instance_id is trusted enough for mutation.
Trusted instance IDs are non-empty and not the pre-#978 PID/proc fallbacks. Trusted instance IDs are non-empty, match the launcher-minted ``inst-…``
format from :func:`generate_client_instance_id`, and are not pre-#978
PID/proc/legacy placeholders. Ordinary user-supplied or malformed values
fail closed as untrusted so they cannot spoof multi-instance attribution.
Incomplete identities remain visible for diagnosis but cannot authorize Incomplete identities remain visible for diagnosis but cannot authorize
unsafe mutation. unsafe mutation.
""" """
@@ -159,6 +167,20 @@ def assess_instance_identity(
"not a trusted launcher-issued instance identity" "not a trusted launcher-issued instance identity"
], ],
} }
if not text.startswith(_TRUSTED_INSTANCE_PREFIX) or len(text) <= len(
_TRUSTED_INSTANCE_PREFIX
):
return {
"client_instance_id": text,
"complete": False,
"trusted": False,
"provenance": INSTANCE_ID_PROVENANCE_LEGACY,
"reasons": [
f"client_instance_id {text!r} is malformed or user-supplied and "
f"does not use the trusted launcher prefix "
f"{_TRUSTED_INSTANCE_PREFIX!r}; refusing trusted attribution"
],
}
return { return {
"client_instance_id": text, "client_instance_id": text,
"complete": True, "complete": True,
+14 -1
View File
@@ -175,8 +175,21 @@ class TestLauncherSnippets(unittest.TestCase):
def test_only_safe_keys_no_secrets(self): def test_only_safe_keys_no_secrets(self):
entry = gitea_config.launcher_entry("prgs", "/cfg/profiles.json")["gitea-tools"] entry = gitea_config.launcher_entry("prgs", "/cfg/profiles.json")["gitea-tools"]
self.assertEqual(set(entry), {"command", "args", "env"}) self.assertEqual(set(entry), {"command", "args", "env"})
self.assertEqual(set(entry["env"]), {"GITEA_MCP_CONFIG", "GITEA_MCP_PROFILE", "GITEA_CLIENT_MANAGED"}) # #978 B1: production launcher also injects trusted client identity.
required = {
"GITEA_MCP_CONFIG",
"GITEA_MCP_PROFILE",
"GITEA_CLIENT_MANAGED",
"GITEA_MCP_CLIENT",
"GITEA_MCP_CLIENT_INSTANCE",
"GITEA_MCP_INSTANCE_PROVENANCE",
}
self.assertTrue(required.issubset(set(entry["env"])), entry["env"])
self.assertEqual(entry["env"]["GITEA_MCP_PROFILE"], "prgs") self.assertEqual(entry["env"]["GITEA_MCP_PROFILE"], "prgs")
self.assertTrue(
entry["env"]["GITEA_MCP_CLIENT_INSTANCE"].startswith("inst-"),
entry["env"]["GITEA_MCP_CLIENT_INSTANCE"],
)
blob = json.dumps(entry).lower() blob = json.dumps(entry).lower()
for word in ("token", "password", "secret"): for word in ("token", "password", "secret"):
self.assertNotIn(word, blob) self.assertNotIn(word, blob)
@@ -1049,6 +1049,386 @@ class ClientHintsTests(unittest.TestCase):
self.assertEqual(hints["client_instance_id"], inst) self.assertEqual(hints["client_instance_id"], inst)
self.assertEqual(hints["fleet_run_id"], "run-1") self.assertEqual(hints["fleet_run_id"], "run-1")
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__": if __name__ == "__main__":
unittest.main() unittest.main()