"""The write surface derived from the MCP registry. The case that drove this: an employee asked for an email alias and got a support ticket, because the hand-written catalogue had three entries while ``create_email_alias`` sat in the registry unreachable. These tests pin both halves — that real flows are reachable, and that deriving them did not quietly widen the surface past what a chat sentence should trigger. """ from __future__ import annotations import uuid from datetime import UTC, datetime, timedelta import pytest from app.assistant.actions import ACTION_SUPPORT_TICKET, get_action from app.assistant.derived_actions import ( OPT_OUT, derived_catalogue, get_derived, search, ) from app.core.exceptions import AppException class TestWhatIsReachable: def test_the_email_alias_flow_is_proposable(self) -> None: """The exact request that used to become a support ticket.""" assert get_derived("create_email_alias") is not None def test_search_finds_it_from_the_employee_s_words(self) -> None: names = [t.name for t in search("email alias")] assert "create_email_alias" in names def test_the_catalogue_covers_the_real_self_service_surface(self) -> None: """A handful of hand-written entries was the bug, not the design.""" cat = derived_catalogue() for expected in ( "request_software_seat", "request_join_email_group", "request_permission", "request_api_key", "submit_ai_tool_application", ): assert expected in cat, expected class TestWhatIsNotReachable: def test_destructive_tools_are_never_proposable(self) -> None: from app.mcp.annotations import DESTRUCTIVE_TOOLS assert not (set(derived_catalogue()) & set(DESTRUCTIVE_TOOLS)) def test_opt_out_tools_are_never_proposable(self) -> None: assert not (set(derived_catalogue()) & OPT_OUT) @pytest.mark.parametrize( "tool", [ "approve_cloud_grant", # decides someone else's request "decide_group_join_request", # decides someone else's request "create_asset_transfer", # moves ownership between people "transfer_my_api_key", # moves ownership between people "finance_close_set_task_status", # administrative bookkeeping ], ) def test_named_surfaces_stay_on_their_own_screen(self, tool: str) -> None: """Reachability is the point, not RBAC: these have holders who ARE allowed to call them. They are excluded because a decision made from one sentence, without the surrounding evidence, is the hole the review screens exist to close.""" assert get_derived(tool) is None def test_read_only_tools_are_not_in_the_write_catalogue(self) -> None: """The two bridges partition the registry; an overlap would mean a read could be routed through a path that asks for confirmation, or a write through one that never does.""" from app.assistant.mcp_bridge import readonly_catalogue assert not (set(derived_catalogue()) & set(readonly_catalogue())) def test_a_tool_with_no_parameters_is_not_proposable(self) -> None: """A confirmation card with no rows cannot tell the employee what they are approving.""" for action in derived_catalogue().values(): assert (action.schema.get("properties") or {}), action.key class TestCuratedWins: def test_a_curated_action_is_preferred_over_the_derived_one(self) -> None: """``create_support_ticket`` exists in both. The hand-written card is better than a generic one, so the fallback must not shadow it.""" assert get_action(ACTION_SUPPORT_TICKET) is not None assert get_action(ACTION_SUPPORT_TICKET) is not get_derived(ACTION_SUPPORT_TICKET) # The curated card localises; the derived one echoes the tool text. assert get_action(ACTION_SUPPORT_TICKET).localized_title("zh") == "提交 IT 求助工单" class TestTheCardTellsTheTruth: def test_every_stored_parameter_appears_on_the_card(self) -> None: """The confirm contract is that what was approved is what runs, so a parameter stored but not shown would be an unreviewed input. Asserted as one row per parameter rather than as raw values: an enum is deliberately rendered through the lexicon (``user`` → 用户邮箱), so matching on the stored string would fail on the localisation that is the improvement, not on the omission that is the bug. """ action = get_derived("create_email_alias") params = action.normalise({ "target_kind": "user", "alias_local_part": "alias", "alias_suffix": "pingcap.com", "reason": "project mail", }) rows = action.summarise(params, "zh") # Every stored parameter, plus the server-owned destination rendered as # the relationship it will be bound to. assert len(rows) == len(params) + 1 shown = {row.localized("zh")["value"] for row in rows} # Free-text and identifiers are never rewritten — only enums are. for field in ("alias_local_part", "alias_suffix", "reason"): assert params[field] in shown assert "用户邮箱" in shown # target_kind, through the lexicon assert "你的公司邮箱" in shown # target_email, bound by the server def test_an_extra_parameter_still_appears(self) -> None: action = get_derived("create_email_alias") rows = action.summarise({"surprise": "value-not-in-schema"}, "zh") assert any(r.localized("zh")["value"] == "value-not-in-schema" for r in rows) def test_missing_required_fields_fail_before_the_card_is_built(self) -> None: """Being asked one more question beats being asked to approve a request that cannot run.""" from app.core.exceptions import AppException action = get_derived("create_email_alias") with pytest.raises(AppException): action.normalise({"target_kind": "user"}) def test_unknown_keys_are_dropped_rather_than_stored(self) -> None: action = get_derived("create_email_alias") cleaned = action.normalise({ "target_kind": "user", "target_email": "someone@pingcap.com", "alias_local_part": "alias", "alias_suffix": "pingcap.com", "reason": "project mail", "not_a_field": "x", }) assert "not_a_field" not in cleaned class TestTheCardLooksNative: """A derived card must read like the product, not like a schema dump. The generic renderer's first cut showed FastMCP's machine titles — "Target Kind", "Alias Local Part" — on the one surface whose whole job is that the employee understood what they are approving. """ def test_heading_is_a_short_noun_phrase_per_language(self) -> None: action = get_derived("create_email_alias") assert action.localized_title("zh") == "新增邮件别名" assert action.localized_title("en") == "Add an email alias" assert action.localized_title("ja") == "メールエイリアスの追加" def test_heading_never_falls_back_to_model_facing_prose(self) -> None: """The tool description is written for a model ("target_kind='user'"). In a heading slot that is what makes a card look generated.""" for action in derived_catalogue().values(): for lang in ("zh", "en", "ja"): assert "=" not in action.localized_title(lang), action.key assert len(action.localized_title(lang)) <= 60, action.key def test_field_labels_are_localised_not_schema_titles(self) -> None: action = get_derived("create_email_alias") labels = {r.localized("zh")["label"] for r in action.summarise( {"target_kind": "user", "alias_local_part": "x"}, "zh")} assert "目标类型" in labels assert "别名前缀" in labels assert "Target Kind" not in labels def test_enum_values_are_localised_too(self) -> None: """``target_kind: user`` should read 用户邮箱, not a bare code.""" action = get_derived("create_email_alias") values = {r.localized("zh")["value"] for r in action.summarise( {"target_kind": "user"}, "zh")} assert "用户邮箱" in values def test_an_unlisted_field_reads_plain_rather_than_broken(self) -> None: from app.assistant.card_labels import humanise assert humanise("alias_local_part") == "Alias local part" def test_every_employee_facing_flow_has_a_real_heading(self) -> None: """A humanised tool name is an acceptable floor, not an acceptable default for something an employee actually reaches for. ``create_support_ticket`` / ``submit_vpn_application`` are exempt: they have curated cards, so the derived heading is never what renders. """ from app.assistant.actions import _REGISTRY from app.assistant.card_labels import TITLES gap = [ key for key in derived_catalogue() if key not in TITLES and key not in _REGISTRY and key.startswith(( "request_", "submit_", "create_", "reset_my", "cancel_", "join_", "leave_", "undo_", )) ] assert not gap, f"employee-facing flows with a machine heading: {gap}" class TestAuthzIsReadNotGuessed: """Classifying a tool from its NAME is what put a dangerous card in reach. ``approve_my_api_key_release`` reads self-scoped and is not: the caller is the requester's MANAGER, and approving synchronously revokes a subordinate's credential. A first cut of ``OPT_OUT`` waved it through on the "my" in its name and even titled the card 「同意释放我的 API Key」. """ @pytest.mark.parametrize( "tool", ["approve_my_api_key_release", "reject_my_api_key_release"], ) def test_my_prefixed_manager_decisions_are_excluded(self, tool: str) -> None: assert get_derived(tool) is None @pytest.mark.parametrize( "tool", [ "revoke_ai_tool_application", # "Admin only." — revokes someone's seat "set_arcade_economy", # global_admin platform config "retry_blocked_dns_request", # ADMIN-ONLY operational retry "upsert_onboarding_ack_item", # global_admin config "set_approval_stage_note", # admin config ], ) def test_admin_only_tools_are_excluded_by_their_own_description( self, tool: str, ) -> None: """Derived from the description rather than a hand list, which rots.""" assert get_derived(tool) is None @pytest.mark.parametrize( "tool", [ "update_my_software_catalog_entry", # only SOME fields are admin-gated "submit_dns_request", # "owned by someone else" is a self-scope guarantee "urge_request", # urges the approver of one's OWN request ], ) def test_reviewed_exceptions_survive_the_marker_rule(self, tool: str) -> None: """The marker is a heuristic; these were read and kept deliberately.""" assert get_derived(tool) is not None def test_no_excluded_tool_keeps_an_employee_facing_heading(self) -> None: """A heading for an unreachable tool is dead copy that would mislead if the tool ever came back.""" from app.assistant.card_labels import TITLES cat = derived_catalogue() assert not [k for k in TITLES if k not in cat and k not in { "create_support_ticket", "submit_vpn_application", }] class TestTheCardMatchesWhatRuns: """Everything here was a production defect on ONE flow that would have repeated on every other one, so each is asserted across the catalogue rather than on the flow that happened to surface it. """ def test_no_intent_line_carries_model_facing_prose(self) -> None: """The tool description is written for a model. Production put "Add an alias to an email mailbox (target_kind='user')..." verbatim on a Chinese confirmation card, under a heading that was already correct. """ for action in derived_catalogue().values(): for lang in ("zh", "en", "ja"): intent = action.localized_intent(lang) assert "=" not in intent, action.key assert "target_kind" not in intent, action.key assert len(intent) <= 120, action.key def test_the_model_cannot_choose_who_a_request_is_for(self) -> None: """Production set ``target_email`` to the alias being created instead of the requester's mailbox. Fields naming the subject are server-owned, so they are absent from the schema the model fills.""" from app.assistant.derived_actions import ( _CONDITIONAL_SELF_BOUND, SELF_BOUND_FIELDS, ) for action in derived_catalogue().values(): props = action.schema.get("properties") or {} for owned in SELF_BOUND_FIELDS: if owned in _CONDITIONAL_SELF_BOUND: # Server-owned only in the self case, so the model has to be # able to name the other one — a group alias is a documented # half of ``create_email_alias`` and was unproposable while # this field was stripped unconditionally. It stays optional, # ``_fill_self_bound`` still overrides it in the self case, # and ``TestAConditionallySelfBoundField`` below pins both. continue assert owned not in props, f"{action.key} still lets the model set {owned}" def test_a_server_owned_value_is_never_stored_from_the_model(self) -> None: """Storing the guess would render it on the card and then override it at execute time — approve one destination, use another.""" action = get_derived("create_email_alias") stored = action.normalise({ "target_kind": "user", "alias_local_part": "shuai4", "alias_suffix": "pingcap.com", "reason": "外部联系", "target_email": "somebody.else@pingcap.com", }) assert "target_email" not in stored def test_the_destination_still_appears_on_the_card(self) -> None: """Dropping the field must not hide the destination — the employee has to see where the alias delivers, named as the relationship.""" action = get_derived("create_email_alias") rows = action.summarise( action.normalise({ "target_kind": "user", "alias_local_part": "a", "alias_suffix": "pingcap.com", "reason": "r", }), "zh", ) assert any(r.localized("zh")["value"] == "你的公司邮箱" for r in rows) def test_a_domain_field_never_keeps_a_leading_at_sign(self) -> None: """``alias_suffix="@pingcap.com"`` built ``shuai4@@pingcap.com``: the service concatenates local + suffix.""" action = get_derived("create_email_alias") cleaned = action.normalise({ "target_kind": "user", "alias_local_part": "shuai4", "alias_suffix": "@pingcap.com", "reason": "外部联系", }) assert cleaned["alias_suffix"] == "pingcap.com" class TestPrivilegedSurfacesSayItManyWays: def test_hr_only_is_excluded_like_admin_only(self) -> None: """The marker list first knew only the word "admin", so an HR-only tool stayed proposable.""" assert get_derived("calculate_annual_leave_balance") is None def test_operating_on_a_named_colleague_is_excluded(self) -> None: for tool in ( "add_api_key_member", "hr_refresh_person_from_hris", "sync_person_to_identity_target", "sweep_leaver_cloud_assignments", ): assert get_derived(tool) is None, tool class TestNothingMachineReadableReachesTheEmployee: """An employee cannot be asked for ``plan_id`` and cannot check it either. 74 of 100 derived actions take at least one id, so this is the widest class of "machine language leaked into the product" — the same fault as the English tool prose on the intent line, one layer down. """ def test_every_id_field_demands_a_human_name_beside_it(self) -> None: from app.assistant.derived_actions import DISPLAY_SUFFIX, _id_fields for action in derived_catalogue().values(): schema = action.schema for field in _id_fields(schema): if field in (schema.get("required") or []): assert f"{field}{DISPLAY_SUFFIX}" in (schema.get("required") or []), ( f"{action.key}.{field} can be filled without naming it" ) def test_the_card_shows_the_name_and_never_the_id(self) -> None: action = get_derived("request_software_seat") params = action.normalise({ "plan_id": "7f3a-9c21-4e55", "plan_id_display": "Figma 专业版", }) shown = {r.localized("zh")["value"] for r in action.summarise(params, "zh")} assert "Figma 专业版" in shown assert "7f3a-9c21-4e55" not in shown def test_an_id_without_its_name_is_refused(self) -> None: """Refusing here is what forces a catalogue lookup instead of a guess.""" from app.core.exceptions import AppException action = get_derived("request_software_seat") with pytest.raises(AppException): action.normalise({"plan_id": "7f3a-9c21-4e55"}) def test_the_tool_still_receives_the_id_alone(self) -> None: from app.assistant.derived_actions import _strip_display action = get_derived("request_software_seat") params = action.normalise({ "plan_id": "7f3a", "plan_id_display": "Figma 专业版", }) assert _strip_display(params) == {"plan_id": "7f3a"} def test_plumbing_is_neither_asked_for_nor_displayed(self) -> None: """``idempotency_key`` is not a decision the employee makes.""" from app.assistant.derived_actions import SERVER_GENERATED_FIELDS for action in derived_catalogue().values(): props = action.schema.get("properties") or {} for plumbing in SERVER_GENERATED_FIELDS: assert plumbing not in props, f"{action.key} asks the model for {plumbing}" def test_plumbing_is_derived_from_the_proposal_so_retries_are_safe(self) -> None: """Keying it on the proposal makes a double-confirm a no-op rather than a second submission.""" from app.assistant.derived_actions import _fill_self_bound schema = {"properties": {"idempotency_key": {"type": "string"}}} class _P: id = "person-1" work_email = "a@b.com" first = _fill_self_bound(schema, {}, _P(), "PROP-9") again = _fill_self_bound(schema, {}, _P(), "PROP-9") assert first["idempotency_key"] == again["idempotency_key"] assert "PROP-9" in first["idempotency_key"] class TestAConditionallySelfBoundField: """``target_email`` means the employee's mailbox — except when it doesn't. ``create_email_alias`` takes ``target_kind`` of ``user`` or ``group``. Binding the address unconditionally (the fix for a real 2026-08-17 incident, where the model set it to the ALIAS being created) produced a card reading 「目标邮箱 = 你的公司邮箱」 beside 「目标类型 = 邮件组」 and then submitted the employee's own mailbox as the group. Both halves have to hold. """ def _alias(self): from app.assistant.derived_actions import get_derived action = get_derived("create_email_alias") assert action is not None return action def _base(self) -> dict: return { "alias_local_part": "shuai4", "alias_suffix": "pingcap.com", "reason": "对外项目联系", } def test_a_guessed_address_is_still_discarded_for_a_user_alias(self) -> None: """The 2026-08-17 incident, still fixed.""" params = self._alias().normalise( {**self._base(), "target_kind": "user", "target_email": "wrong@x.com"}, ) assert params.get("target_email") is None def test_the_card_says_your_mailbox_only_when_it_is_yours(self) -> None: action = self._alias() rows = { r.label.get("zh"): r.value for r in action.summarise( action.normalise({**self._base(), "target_kind": "user"}), "zh", ) } assert rows.get("目标邮箱") == "你的公司邮箱" def test_a_group_alias_keeps_the_group_on_the_card(self) -> None: action = self._alias() params = action.normalise({ **self._base(), "target_kind": "group", "target_email": "it-team@pingcap.com", }) assert params["target_email"] == "it-team@pingcap.com" rows = {r.label.get("zh"): r.value for r in action.summarise(params, "zh")} assert rows.get("目标邮箱") == "it-team@pingcap.com" def test_a_group_alias_without_an_address_is_refused_before_the_card(self) -> None: """Only the employee knows which group. Letting it through would fail at execute time — after the tap.""" from app.core.exceptions import AppException with pytest.raises(AppException, match="target_email"): self._alias().normalise({**self._base(), "target_kind": "group"}) class TestDatesPointTheWayTheirFieldSays: """A date field carries its own direction, and the assistant enforces it. Production, 2026-08-18: 「有效期到 9 月 20 号」 became ``2024-09-20`` — an LLM with no clock falls back on its training year. The domain schema rejects a past ``valid_from`` too, but only at EXECUTE time, i.e. after the employee has already tapped confirm. Refusing at propose time is what keeps a request that cannot run off the card. """ def _iso(self, days: int) -> str: return (datetime.now(UTC).date() + timedelta(days=days)).isoformat() def test_the_constraint_is_declared_on_the_field_not_listed_here(self) -> None: """The direction travels with the field, so a new flow inherits it. A hand-maintained list of "date fields" inside the assistant would rot on the first flow someone adds. """ action = get_derived("request_api_key") assert action is not None for field in ("valid_from", "valid_until"): spec = action.schema["properties"][field] assert spec.get("x_helix_date") == "future" # The model reads the description; it must say what to do. assert "YYYY-MM-DD" in (spec.get("description") or "") def test_a_date_from_the_training_year_is_refused(self) -> None: action = get_derived("request_api_key") assert action is not None with pytest.raises(AppException) as caught: action.normalise({ "provider_id": str(uuid.uuid4()), "provider_id_display": "Anthropic", "project_name": "abc", "reason": "bcd", "monthly_budget_usd": 200, "valid_from": "2024-06-01", "valid_until": "2024-09-20", }) message = caught.value.message # Actionable: it names today, or the model retries the same wrong year. assert self._iso(0) in message # And it forecloses the fallback the model actually took in production. assert "support ticket" in message def test_a_correctly_resolved_period_passes(self) -> None: action = get_derived("request_api_key") assert action is not None params = action.normalise({ "provider_id": str(uuid.uuid4()), "provider_id_display": "Anthropic", "project_name": "abc", "reason": "bcd", "monthly_budget_usd": 200, "valid_from": self._iso(0), "valid_until": self._iso(60), }) assert params["valid_until"] == self._iso(60) def test_yesterday_is_allowed_because_timezones_exist(self) -> None: """The domain schema allows a day of slack; propose time must agree, or a date the tool accepts is refused before the employee ever sees it.""" action = get_derived("request_api_key") assert action is not None params = action.normalise({ "provider_id": str(uuid.uuid4()), "provider_id_display": "Anthropic", "project_name": "abc", "reason": "bcd", "monthly_budget_usd": 200, "valid_from": self._iso(-1), "valid_until": self._iso(60), }) assert params["valid_from"] == self._iso(-1) def test_a_birthday_in_the_future_is_refused_and_a_past_one_is_not(self) -> None: """The rule is per-field, not "no past dates" — a blanket rule would make it impossible to record a date of birth at all.""" action = get_derived("feed_update_my_preferences") assert action is not None assert action.schema["properties"]["birthday"].get("x_helix_date") == "past" with pytest.raises(AppException): action.normalise({"birthday": self._iso(30)}) assert action.normalise({"birthday": "1990-09-20"})["birthday"] == "1990-09-20" def test_a_non_date_string_in_a_date_field_is_refused(self) -> None: action = get_derived("extend_asset_borrow") assert action is not None with pytest.raises(AppException): action.normalise({ "asset_id": str(uuid.uuid4()), "asset_id_display": "MacBook Pro HX-0001", "expected_return_date": "下个月底", "reason": "还在用", }) class TestTheCardShowsLineBreaksNotEscapes: """The model writes ``\\n`` inside its JSON arguments often enough that the card carried the two characters, visibly, in the text the employee was being asked to approve.""" def test_a_literal_escape_becomes_a_real_line_break(self) -> None: action = get_derived("extend_asset_borrow") assert action is not None params = action.normalise({ "asset_id": str(uuid.uuid4()), "asset_id_display": "MacBook Pro HX-0001", "expected_return_date": datetime.now(UTC).date().isoformat(), "reason": "项目名:abc\\n用途:bcd\\n预算:200 美元/月", }) assert "\\n" not in params["reason"] assert params["reason"].count("\n") == 2 def test_text_the_model_formatted_correctly_is_untouched(self) -> None: action = get_derived("extend_asset_borrow") assert action is not None already_fine = "第一行\n第二行" params = action.normalise({ "asset_id": str(uuid.uuid4()), "asset_id_display": "MacBook Pro HX-0001", "expected_return_date": datetime.now(UTC).date().isoformat(), "reason": already_fine, }) assert params["reason"] == already_fine