mainsequence 8.1.4__tar.gz → 8.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. {mainsequence-8.1.4/mainsequence.egg-info → mainsequence-8.1.6}/PKG-INFO +1 -1
  2. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/agent_runtime_models.py +29 -2
  3. {mainsequence-8.1.4 → mainsequence-8.1.6/mainsequence.egg-info}/PKG-INFO +1 -1
  4. {mainsequence-8.1.4 → mainsequence-8.1.6}/pyproject.toml +1 -1
  5. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_cli.py +3 -3
  6. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_filter_normalization.py +88 -8
  7. {mainsequence-8.1.4 → mainsequence-8.1.6}/LICENSE +0 -0
  8. {mainsequence-8.1.4 → mainsequence-8.1.6}/MANIFEST.in +0 -0
  9. {mainsequence-8.1.4 → mainsequence-8.1.6}/README.md +0 -0
  10. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/AGENTS.md +0 -0
  11. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  12. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  13. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  14. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  15. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  16. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  17. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  18. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  19. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  20. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  21. {mainsequence-8.1.4 → mainsequence-8.1.6}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  22. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/__init__.py +0 -0
  23. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/__main__.py +0 -0
  24. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/bootstrap.py +0 -0
  25. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/__init__.py +0 -0
  26. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/api.py +0 -0
  27. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/browser_auth.py +0 -0
  28. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/cli.py +0 -0
  29. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/code_repository_status.py +0 -0
  30. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/config.py +0 -0
  31. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/docker_utils.py +0 -0
  32. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/doctor.py +0 -0
  33. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/local_ops.py +0 -0
  34. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/migrations.py +0 -0
  35. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/model_filters.py +0 -0
  36. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/pydantic_cli.py +0 -0
  37. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/sdk_utils.py +0 -0
  38. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/ssh_utils.py +0 -0
  39. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/cli/ui.py +0 -0
  40. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/__init__.py +0 -0
  41. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/base.py +0 -0
  42. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/client.py +0 -0
  43. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/compute_validation.py +0 -0
  44. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  45. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  46. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  47. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  48. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/dtype_codec.py +0 -0
  49. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/exceptions.py +0 -0
  50. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/metatables/__init__.py +0 -0
  51. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/metatables/core.py +0 -0
  52. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/models_foundry.py +0 -0
  53. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/models_helpers.py +0 -0
  54. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/models_user.py +0 -0
  55. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/observability.py +0 -0
  56. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/client/utils.py +0 -0
  57. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/code_repository_context.py +0 -0
  58. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/code_repository_skills.py +0 -0
  59. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/defaults.py +0 -0
  60. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/instrumentation/__init__.py +0 -0
  61. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/instrumentation/utils.py +0 -0
  62. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/logconf.py +0 -0
  63. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/__init__.py +0 -0
  64. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/__main__.py +0 -0
  65. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  66. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  67. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/future_registry.py +0 -0
  68. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/hashing.py +0 -0
  69. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  70. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  71. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/env.py +0 -0
  72. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/provider.py +0 -0
  73. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/registry.py +0 -0
  74. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  75. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  76. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  77. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  78. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  79. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/schema_names.py +0 -0
  80. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  81. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  82. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  83. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  84. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  85. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
  86. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  87. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
  88. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
  89. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  90. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/repository_identity_security.py +0 -0
  91. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/runtime_flags.py +0 -0
  92. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence/scaffold_skills.py +0 -0
  93. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence.egg-info/SOURCES.txt +0 -0
  94. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence.egg-info/dependency_links.txt +0 -0
  95. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence.egg-info/entry_points.txt +0 -0
  96. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence.egg-info/requires.txt +0 -0
  97. {mainsequence-8.1.4 → mainsequence-8.1.6}/mainsequence.egg-info/top_level.txt +0 -0
  98. {mainsequence-8.1.4 → mainsequence-8.1.6}/setup.cfg +0 -0
  99. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  100. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_auth_precedence.py +0 -0
  101. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_canonical_api_route_contract.py +0 -0
  102. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_cli_browser_auth.py +0 -0
  103. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_cli_migrations.py +0 -0
  104. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_client.py +0 -0
  105. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
  106. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_code_repository_branch_contract.py +0 -0
  107. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_code_repository_documentation_contract.py +0 -0
  108. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_code_repository_runtime_context.py +0 -0
  109. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_code_repository_skills.py +0 -0
  110. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_dependency_extras.py +0 -0
  111. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_duckdb_interface_dimensions.py +0 -0
  112. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_environment_resource_context.py +0 -0
  113. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_environment_response_contracts.py +0 -0
  114. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_instrumentation.py +0 -0
  115. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_local_ops.py +0 -0
  116. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_logconf.py +0 -0
  117. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_meta_table_migrations.py +0 -0
  118. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_meta_tables_client_models.py +0 -0
  119. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  120. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_models_user_request_bound_auth.py +0 -0
  121. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_notification_uid_contract.py +0 -0
  122. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_observability_client.py +0 -0
  123. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_pod_project_resolution.py +0 -0
  124. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_scaffold_skills.py +0 -0
  125. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_schema_names.py +0 -0
  126. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_secret_client_model.py +0 -0
  127. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_source_table_configuration.py +0 -0
  128. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_sqlite_interface_dimensions.py +0 -0
  129. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_ssh_utils.py +0 -0
  130. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  131. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_time_index_table_dimension_queries.py +0 -0
  132. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_time_index_table_update_configuration.py +0 -0
  133. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_time_index_table_update_configuration_client.py +0 -0
  134. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_time_index_table_update_flow.py +0 -0
  135. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_time_index_table_update_hard_cut.py +0 -0
  136. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_update_runner_uid_runtime.py +0 -0
  137. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_update_statistics.py +0 -0
  138. {mainsequence-8.1.4 → mainsequence-8.1.6}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.4
3
+ Version: 8.1.6
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -30,6 +30,8 @@ DEFAULT_AGENT_RUNTIME_ACCESS_CACHE_EXPIRY_SKEW_SECONDS = 30.0
30
30
  STANDARD_A2A_MESSAGE_SEND_PATH = "/api/a2a/v1/message:send"
31
31
  STANDARD_A2A_TASKS_PATH = "/api/a2a/v1/tasks"
32
32
  STANDARD_A2A_CONTENT_TYPE = "application/a2a+json"
33
+ STANDARD_A2A_REQUESTER_ROLE = "ROLE_REQUESTER"
34
+ STANDARD_A2A_RESPONDER_ROLE = "ROLE_RESPONDER"
33
35
  STANDARD_A2A_RESPONSE_KIND_EXTENSION_URI = "https://mainsequence.ai/a2a/extensions/response-kind/v1"
34
36
  STANDARD_A2A_OUTPUT_CONTRACT_METADATA_KEY = (
35
37
  "https://mainsequence.ai/a2a/extensions/output-contract/v1"
@@ -105,6 +107,14 @@ class A2AMessageSendResult(BasePydanticModel):
105
107
  def _require_exact_result(self) -> A2AMessageSendResult:
106
108
  if (self.message is None) == (self.task is None):
107
109
  raise ValueError("A2A message-send result must contain exactly one of message or task")
110
+ if (
111
+ self.message is not None
112
+ and self.message.get("role") != STANDARD_A2A_RESPONDER_ROLE
113
+ ):
114
+ raise ValueError(
115
+ "A2A message result role must be "
116
+ f"{STANDARD_A2A_RESPONDER_ROLE}."
117
+ )
108
118
  return self
109
119
 
110
120
  @property
@@ -227,6 +237,13 @@ class AgentRuntimeImageDrift(BasePydanticModel):
227
237
  has_drift: bool = Field(
228
238
  False, description="Whether any included drift check is currently drifting."
229
239
  )
240
+ requires_user_action: bool = Field(
241
+ False,
242
+ description=(
243
+ "Whether at least one current drift condition requires explicit user "
244
+ "action instead of backend autohealing."
245
+ ),
246
+ )
230
247
  autoheal_available: bool = Field(
231
248
  False,
232
249
  description="Whether all currently drifting checks can be repaired automatically by the backend.",
@@ -670,7 +687,7 @@ class Agent(
670
687
  body: dict[str, Any] = {
671
688
  "message": {
672
689
  "messageId": str(message_id or "").strip() or f"msg-{uuid.uuid4()}",
673
- "role": "ROLE_USER",
690
+ "role": STANDARD_A2A_REQUESTER_ROLE,
674
691
  "parts": parts,
675
692
  },
676
693
  "configuration": {
@@ -1519,7 +1536,7 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
1519
1536
  body: dict[str, Any] = {
1520
1537
  "message": {
1521
1538
  "messageId": normalized_message_id,
1522
- "role": "ROLE_USER",
1539
+ "role": STANDARD_A2A_REQUESTER_ROLE,
1523
1540
  "contextId": agent_session_uid,
1524
1541
  "parts": parts,
1525
1542
  },
@@ -1725,6 +1742,16 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
1725
1742
  response=response,
1726
1743
  payload=body,
1727
1744
  )
1745
+ if (
1746
+ result.message is not None
1747
+ and result.message.get("contextId") != session_uid
1748
+ ):
1749
+ raise ApiError(
1750
+ "Standard A2A message response contextId does not match the target "
1751
+ "AgentSession.",
1752
+ response=response,
1753
+ payload=body,
1754
+ )
1728
1755
  return result
1729
1756
 
1730
1757
  @classmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.4
3
+ Version: 8.1.6
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "8.1.4"
7
+ version = "8.1.6"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -1055,7 +1055,7 @@ def test_send_agent_session_a2a_message_uses_client_model(cli_mod, monkeypatch):
1055
1055
  return {
1056
1056
  "message": {
1057
1057
  "messageId": "msg-runtime-output",
1058
- "role": "ROLE_AGENT",
1058
+ "role": "ROLE_RESPONDER",
1059
1059
  "contextId": agent_session,
1060
1060
  "parts": [{"text": "Done."}],
1061
1061
  }
@@ -6785,7 +6785,7 @@ def test_agent_session_a2a_send_always_returns_json(cli_mod, runner, monkeypatch
6785
6785
  return {
6786
6786
  "message": {
6787
6787
  "messageId": "msg-runtime-output",
6788
- "role": "ROLE_AGENT",
6788
+ "role": "ROLE_RESPONDER",
6789
6789
  "contextId": agent_session_uid,
6790
6790
  "parts": [{"text": '{"ok": true}'}],
6791
6791
  }
@@ -6860,7 +6860,7 @@ def test_agent_session_a2a_send_does_not_require_runtime_resolve(cli_mod, runner
6860
6860
  return {
6861
6861
  "message": {
6862
6862
  "messageId": "msg-runtime-output",
6863
- "role": "ROLE_AGENT",
6863
+ "role": "ROLE_RESPONDER",
6864
6864
  "contextId": args[0],
6865
6865
  "parts": [{"text": "Done."}],
6866
6866
  }
@@ -3609,6 +3609,7 @@ def test_agent_session_runtime_access_uses_session_uid_route(monkeypatch):
3609
3609
  "agent_kind": "astro_orchestrator",
3610
3610
  "available": True,
3611
3611
  "has_drift": False,
3612
+ "requires_user_action": False,
3612
3613
  "autoheal_available": False,
3613
3614
  "autoheal_message": "No automatic drift repair is needed.",
3614
3615
  "checks": [
@@ -3667,6 +3668,7 @@ def test_agent_session_runtime_access_uses_session_uid_route(monkeypatch):
3667
3668
  assert access.knative_service_runtime_uid == "70c6efb9-8e80-4051-ad3a-f432b2c37f5a"
3668
3669
  assert access.image_drift is not None
3669
3670
  assert access.image_drift.agent_kind == "astro_orchestrator"
3671
+ assert access.image_drift.requires_user_action is False
3670
3672
  assert access.image_drift.checks[0].key == "orchestrator_image"
3671
3673
  assert access.image_drift.catalog_state is not None
3672
3674
  assert access.image_drift.catalog_state["refresh_required"] is False
@@ -3711,6 +3713,7 @@ def test_agent_session_runtime_access_accepts_minimal_image_drift(monkeypatch):
3711
3713
 
3712
3714
  assert access.image_drift is not None
3713
3715
  assert access.image_drift.has_drift is False
3716
+ assert access.image_drift.requires_user_action is False
3714
3717
  assert access.image_drift.detail is None
3715
3718
  assert access.model_dump()["reconciliation"] == {
3716
3719
  "queued": False,
@@ -3718,6 +3721,20 @@ def test_agent_session_runtime_access_accepts_minimal_image_drift(monkeypatch):
3718
3721
  }
3719
3722
 
3720
3723
 
3724
+ @pytest.mark.parametrize("requires_user_action", [False, True])
3725
+ def test_agent_runtime_image_drift_parses_user_action_signal(requires_user_action):
3726
+ image_drift = agent_models_mod.AgentRuntimeImageDrift.model_validate(
3727
+ {"requires_user_action": requires_user_action}
3728
+ )
3729
+
3730
+ assert image_drift.requires_user_action is requires_user_action
3731
+
3732
+ with pytest.raises(ValidationError, match="requires_user_action"):
3733
+ agent_models_mod.AgentRuntimeImageDrift.model_validate(
3734
+ {"requires_user_action": None}
3735
+ )
3736
+
3737
+
3721
3738
  def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3722
3739
  captured = {"resolve_count": 0, "runtime": {}}
3723
3740
  session_uid = "3f1cc452-43ec-49cb-b2ba-87dbac164d29"
@@ -3747,6 +3764,13 @@ def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3747
3764
  "token": "tok-secret",
3748
3765
  "expires_at": "2999-01-01T00:00:00Z",
3749
3766
  **_ready_runtime_contract(),
3767
+ "image_drift": {
3768
+ "agent_kind": "astro_orchestrator",
3769
+ "available": True,
3770
+ "has_drift": False,
3771
+ "requires_user_action": False,
3772
+ "checks": [],
3773
+ },
3750
3774
  }
3751
3775
 
3752
3776
  class FakeRuntimeResponse:
@@ -3759,7 +3783,7 @@ def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3759
3783
  return {
3760
3784
  "message": {
3761
3785
  "messageId": "msg-runtime-output",
3762
- "role": "ROLE_AGENT",
3786
+ "role": "ROLE_RESPONDER",
3763
3787
  "contextId": session_uid,
3764
3788
  "parts": [{"text": "I can analyze workspaces."}],
3765
3789
  }
@@ -3799,6 +3823,10 @@ def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3799
3823
  )
3800
3824
 
3801
3825
  assert captured["resolve_count"] == 1
3826
+ cached_access = agent_models_mod.AgentSession.get_cached_runtime_access(session)
3827
+ assert cached_access is not None
3828
+ assert cached_access.image_drift is not None
3829
+ assert cached_access.image_drift.requires_user_action is False
3802
3830
  assert captured["resolve"] == {
3803
3831
  "r_type": "POST",
3804
3832
  "url": f"{agent_models_mod.AgentSession.get_object_url()}/{session_uid}/resolve-runtime-access/",
@@ -3821,7 +3849,7 @@ def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3821
3849
  assert request_body == {
3822
3850
  "message": {
3823
3851
  "messageId": "msg-00000000-0000-4000-8000-000000000001",
3824
- "role": "ROLE_USER",
3852
+ "role": "ROLE_REQUESTER",
3825
3853
  "contextId": session_uid,
3826
3854
  "parts": [{"text": "What can this agent do?"}],
3827
3855
  },
@@ -3833,6 +3861,58 @@ def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3833
3861
  assert "omit_reasoning" not in captured["runtime"]["data"]
3834
3862
 
3835
3863
 
3864
+ @pytest.mark.parametrize("invalid_role", [None, "ROLE_REQUESTER", "requester"])
3865
+ def test_a2a_message_result_requires_responder_role(invalid_role):
3866
+ with pytest.raises(ValidationError, match="ROLE_RESPONDER"):
3867
+ agent_models_mod.A2AMessageSendResult.model_validate(
3868
+ {
3869
+ "message": {
3870
+ "messageId": "msg-output",
3871
+ "role": invalid_role,
3872
+ "contextId": "session-uid",
3873
+ "parts": [{"text": "Result."}],
3874
+ }
3875
+ }
3876
+ )
3877
+
3878
+
3879
+ def test_agent_session_send_rejects_mismatched_response_context(monkeypatch):
3880
+ session_uid = "3f1cc452-43ec-49cb-b2ba-87dbac164d29"
3881
+
3882
+ class FakeResponse:
3883
+ status_code = 200
3884
+
3885
+ @staticmethod
3886
+ def json():
3887
+ return {
3888
+ "message": {
3889
+ "messageId": "msg-output",
3890
+ "role": "ROLE_RESPONDER",
3891
+ "contextId": "different-session",
3892
+ "parts": [{"text": "Result."}],
3893
+ }
3894
+ }
3895
+
3896
+ monkeypatch.setattr(
3897
+ agent_models_mod.AgentSession,
3898
+ "_resolve_runtime_access_for_message_send",
3899
+ classmethod(lambda cls, agent_session, timeout=None: object()),
3900
+ )
3901
+ monkeypatch.setattr(
3902
+ agent_models_mod.AgentSession,
3903
+ "_post_standard_a2a_message",
3904
+ classmethod(
3905
+ lambda cls, access, *, body, timeout=None: FakeResponse()
3906
+ ),
3907
+ )
3908
+
3909
+ with pytest.raises(agent_models_mod.ApiError, match="contextId"):
3910
+ agent_models_mod.AgentSession.send_a2a_message(
3911
+ session_uid,
3912
+ message="Return a result.",
3913
+ )
3914
+
3915
+
3836
3916
  def test_agent_session_send_waits_only_while_runtime_interaction_is_transient(
3837
3917
  monkeypatch,
3838
3918
  ):
@@ -3887,7 +3967,7 @@ def test_agent_session_send_waits_only_while_runtime_interaction_is_transient(
3887
3967
  return {
3888
3968
  "message": {
3889
3969
  "messageId": "msg-output",
3890
- "role": "ROLE_AGENT",
3970
+ "role": "ROLE_RESPONDER",
3891
3971
  "contextId": session_uid,
3892
3972
  "parts": [{"text": "Ready."}],
3893
3973
  }
@@ -4170,7 +4250,7 @@ def test_agent_session_send_a2a_message_posts_strict_dictionary_contract(monkeyp
4170
4250
  return {
4171
4251
  "message": {
4172
4252
  "messageId": "msg-runtime-output",
4173
- "role": "ROLE_AGENT",
4253
+ "role": "ROLE_RESPONDER",
4174
4254
  "contextId": session_uid,
4175
4255
  "parts": [{"text": '{"ok": true}'}],
4176
4256
  }
@@ -4253,7 +4333,7 @@ def test_agent_session_send_a2a_message_refreshes_access_and_reuses_body(monkeyp
4253
4333
  return {
4254
4334
  "message": {
4255
4335
  "messageId": "msg-runtime-output",
4256
- "role": "ROLE_AGENT",
4336
+ "role": "ROLE_RESPONDER",
4257
4337
  "contextId": session_uid,
4258
4338
  "parts": [{"text": "Done."}],
4259
4339
  }
@@ -4329,7 +4409,7 @@ def test_agent_respond_uses_agent_scoped_sessionless_contract(monkeypatch):
4329
4409
  "message": {
4330
4410
  "kind": "message",
4331
4411
  "messageId": "msg-output",
4332
- "role": "ROLE_AGENT",
4412
+ "role": "ROLE_RESPONDER",
4333
4413
  "parts": [{"data": {"answer": 42}, "mediaType": "application/json"}],
4334
4414
  }
4335
4415
  }
@@ -4383,7 +4463,7 @@ def test_agent_respond_uses_agent_scoped_sessionless_contract(monkeypatch):
4383
4463
  body = runtime["body"]
4384
4464
  assert body["message"] == {
4385
4465
  "messageId": "msg-input",
4386
- "role": "ROLE_USER",
4466
+ "role": "ROLE_REQUESTER",
4387
4467
  "parts": [{"text": "Return an answer."}],
4388
4468
  }
4389
4469
  assert "contextId" not in body["message"]
@@ -4460,7 +4540,7 @@ def test_agent_respond_waits_for_transient_runtime_interaction(monkeypatch):
4460
4540
  return {
4461
4541
  "message": {
4462
4542
  "messageId": "msg-output",
4463
- "role": "ROLE_AGENT",
4543
+ "role": "ROLE_RESPONDER",
4464
4544
  "parts": [{"text": "Ready."}],
4465
4545
  }
4466
4546
  }
File without changes
File without changes
File without changes
File without changes