mainsequence 8.1.2__tar.gz → 8.1.4__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.2/mainsequence.egg-info → mainsequence-8.1.4}/PKG-INFO +1 -1
  2. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/agent_runtime_models.py +22 -0
  3. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/base.py +10 -1
  4. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/models_foundry.py +1 -0
  5. {mainsequence-8.1.2 → mainsequence-8.1.4/mainsequence.egg-info}/PKG-INFO +1 -1
  6. {mainsequence-8.1.2 → mainsequence-8.1.4}/pyproject.toml +1 -1
  7. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_environment_resource_context.py +50 -0
  8. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_environment_response_contracts.py +6 -0
  9. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_filter_normalization.py +74 -1
  10. {mainsequence-8.1.2 → mainsequence-8.1.4}/LICENSE +0 -0
  11. {mainsequence-8.1.2 → mainsequence-8.1.4}/MANIFEST.in +0 -0
  12. {mainsequence-8.1.2 → mainsequence-8.1.4}/README.md +0 -0
  13. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/AGENTS.md +0 -0
  14. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  15. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  16. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  17. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  18. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  19. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  20. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  21. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  22. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  23. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  24. {mainsequence-8.1.2 → mainsequence-8.1.4}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  25. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/__init__.py +0 -0
  26. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/__main__.py +0 -0
  27. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/bootstrap.py +0 -0
  28. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/__init__.py +0 -0
  29. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/api.py +0 -0
  30. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/browser_auth.py +0 -0
  31. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/cli.py +0 -0
  32. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/code_repository_status.py +0 -0
  33. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/config.py +0 -0
  34. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/docker_utils.py +0 -0
  35. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/doctor.py +0 -0
  36. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/local_ops.py +0 -0
  37. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/migrations.py +0 -0
  38. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/model_filters.py +0 -0
  39. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/pydantic_cli.py +0 -0
  40. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/sdk_utils.py +0 -0
  41. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/ssh_utils.py +0 -0
  42. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/cli/ui.py +0 -0
  43. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/__init__.py +0 -0
  44. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/client.py +0 -0
  45. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/compute_validation.py +0 -0
  46. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  47. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  48. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  49. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  50. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/dtype_codec.py +0 -0
  51. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/exceptions.py +0 -0
  52. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/metatables/__init__.py +0 -0
  53. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/metatables/core.py +0 -0
  54. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/models_helpers.py +0 -0
  55. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/models_user.py +0 -0
  56. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/observability.py +0 -0
  57. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/client/utils.py +0 -0
  58. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/code_repository_context.py +0 -0
  59. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/code_repository_skills.py +0 -0
  60. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/defaults.py +0 -0
  61. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/instrumentation/__init__.py +0 -0
  62. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/instrumentation/utils.py +0 -0
  63. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/logconf.py +0 -0
  64. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/__init__.py +0 -0
  65. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/__main__.py +0 -0
  66. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  67. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  68. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/future_registry.py +0 -0
  69. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/hashing.py +0 -0
  70. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  71. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  72. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/env.py +0 -0
  73. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/provider.py +0 -0
  74. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/registry.py +0 -0
  75. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  76. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  77. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  78. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  79. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  80. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/schema_names.py +0 -0
  81. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  82. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  83. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  84. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  85. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  86. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
  87. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  88. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
  89. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
  90. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  91. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/repository_identity_security.py +0 -0
  92. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/runtime_flags.py +0 -0
  93. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence/scaffold_skills.py +0 -0
  94. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence.egg-info/SOURCES.txt +0 -0
  95. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence.egg-info/dependency_links.txt +0 -0
  96. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence.egg-info/entry_points.txt +0 -0
  97. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence.egg-info/requires.txt +0 -0
  98. {mainsequence-8.1.2 → mainsequence-8.1.4}/mainsequence.egg-info/top_level.txt +0 -0
  99. {mainsequence-8.1.2 → mainsequence-8.1.4}/setup.cfg +0 -0
  100. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  101. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_auth_precedence.py +0 -0
  102. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_canonical_api_route_contract.py +0 -0
  103. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_cli.py +0 -0
  104. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_cli_browser_auth.py +0 -0
  105. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_cli_migrations.py +0 -0
  106. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_client.py +0 -0
  107. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
  108. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_code_repository_branch_contract.py +0 -0
  109. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_code_repository_documentation_contract.py +0 -0
  110. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_code_repository_runtime_context.py +0 -0
  111. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_code_repository_skills.py +0 -0
  112. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_dependency_extras.py +0 -0
  113. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_duckdb_interface_dimensions.py +0 -0
  114. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_instrumentation.py +0 -0
  115. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_local_ops.py +0 -0
  116. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_logconf.py +0 -0
  117. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_meta_table_migrations.py +0 -0
  118. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_meta_tables_client_models.py +0 -0
  119. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  120. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_models_user_request_bound_auth.py +0 -0
  121. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_notification_uid_contract.py +0 -0
  122. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_observability_client.py +0 -0
  123. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_pod_project_resolution.py +0 -0
  124. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_scaffold_skills.py +0 -0
  125. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_schema_names.py +0 -0
  126. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_secret_client_model.py +0 -0
  127. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_source_table_configuration.py +0 -0
  128. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_sqlite_interface_dimensions.py +0 -0
  129. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_ssh_utils.py +0 -0
  130. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  131. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_time_index_table_dimension_queries.py +0 -0
  132. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_time_index_table_update_configuration.py +0 -0
  133. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_time_index_table_update_configuration_client.py +0 -0
  134. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_time_index_table_update_flow.py +0 -0
  135. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_time_index_table_update_hard_cut.py +0 -0
  136. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_update_runner_uid_runtime.py +0 -0
  137. {mainsequence-8.1.2 → mainsequence-8.1.4}/tests/test_update_statistics.py +0 -0
  138. {mainsequence-8.1.2 → mainsequence-8.1.4}/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.2
3
+ Version: 8.1.4
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -1992,6 +1992,14 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
1992
1992
  "harness. Capability names are backend-extensible."
1993
1993
  ),
1994
1994
  )
1995
+ catalog_digest: str | None = Field(
1996
+ ...,
1997
+ pattern=r"^sha256:[0-9a-f]{64}$",
1998
+ description=(
1999
+ "Read-only digest of the model-provider catalog used to resolve the "
2000
+ "session, or null when no provider-control projection is available."
2001
+ ),
2002
+ )
1995
2003
  agent_uid: str | None = Field(
1996
2004
  None, description="Public UID of the agent definition used for this session."
1997
2005
  )
@@ -2028,6 +2036,20 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
2028
2036
  "",
2029
2037
  description="Read-only helper with the canonical machine-readable agent runtime or workflow type.",
2030
2038
  )
2039
+ organization_environment_uid: str = Field(
2040
+ ...,
2041
+ description=(
2042
+ "Read-only public UID of the Organization Environment that owns the "
2043
+ "session through its Agent."
2044
+ ),
2045
+ )
2046
+ organization_environment_name: str = Field(
2047
+ ...,
2048
+ description=(
2049
+ "Read-only name of the Organization Environment that owns the session "
2050
+ "through its Agent."
2051
+ ),
2052
+ )
2031
2053
  harness: AgentHarnessKind | None = Field(
2032
2054
  None,
2033
2055
  description=(
@@ -130,6 +130,8 @@ class BaseObjectOrm:
130
130
  DESTROY_QUERY_PARAM_DESCRIPTIONS: ClassVar[dict[str, str] | None] = None
131
131
  PUBLIC_LOOKUP_FIELD: ClassVar[str] = "uid"
132
132
  SDK_OWNED_CONTEXT_FIELDS: ClassVar[frozenset[str]] = frozenset()
133
+ # Opt in when collection responses intentionally omit detail-only fields.
134
+ HYDRATE_FILTER_GET_FROM_DETAIL: ClassVar[bool] = False
133
135
 
134
136
  END_POINTS = {
135
137
  "User": "users",
@@ -633,7 +635,14 @@ class BaseObjectOrm:
633
635
  if len(candidates) > 1:
634
636
  raise ApiError(f"Multiple objects returned for {cls.__name__} with filters={filters}")
635
637
 
636
- return candidates[0]
638
+ candidate = candidates[0]
639
+ if cls.HYDRATE_FILTER_GET_FROM_DETAIL:
640
+ return cls.get(
641
+ pk=candidate._public_detail_reference(),
642
+ timeout=timeout,
643
+ )
644
+
645
+ return candidate
637
646
 
638
647
  @classmethod
639
648
  def get_by_uid(cls, uid: str, timeout=None, **filters):
@@ -1083,6 +1083,7 @@ class Secret(
1083
1083
  BasePydanticModel,
1084
1084
  BaseObjectOrm,
1085
1085
  ):
1086
+ HYDRATE_FILTER_GET_FROM_DETAIL: ClassVar[bool] = True
1086
1087
  FILTERSET_FIELDS: ClassVar[dict[str, list[str]]] = {
1087
1088
  "uid": ["in", "exact"],
1088
1089
  "name": ["in", "exact"],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.2
3
+ Version: 8.1.4
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.2"
7
+ version = "8.1.4"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -55,6 +55,7 @@ def test_environment_resource_reads_use_sdk_owned_scope(monkeypatch):
55
55
  detail = models_module.Secret.get_by_uid(_secret_payload()["uid"])
56
56
 
57
57
  assert listed[0].organization_environment_uid == ENVIRONMENT_UID
58
+ assert listed[0].value is None
58
59
  assert detail.organization_environment_name == "Development"
59
60
  assert requests[0]["payload"]["params"] == {
60
61
  "name": "API_KEY",
@@ -65,6 +66,55 @@ def test_environment_resource_reads_use_sdk_owned_scope(monkeypatch):
65
66
  }
66
67
 
67
68
 
69
+ def test_secret_get_by_name_hydrates_value_from_scoped_detail(monkeypatch):
70
+ requests = []
71
+
72
+ def fake_make_request(**kwargs):
73
+ requests.append(kwargs)
74
+ if kwargs["url"].rstrip("/").endswith("secrets"):
75
+ return _Response([_secret_payload()])
76
+ return _Response(_secret_payload() | {"value": "provider-secret"})
77
+
78
+ monkeypatch.setattr(base_module, "make_request", fake_make_request)
79
+
80
+ secret = models_module.Secret.get(name="API_KEY", timeout=17)
81
+
82
+ assert secret.value is not None
83
+ assert secret.value.get_secret_value() == "provider-secret"
84
+ assert "value" not in secret.model_dump()
85
+ assert len(requests) == 2
86
+ assert requests[0]["payload"]["params"] == {
87
+ "name": "API_KEY",
88
+ "organization_environment_uid": ENVIRONMENT_UID,
89
+ }
90
+ assert requests[1]["url"].endswith(f"/secrets/{_secret_payload()['uid']}/")
91
+ assert requests[1]["payload"]["params"] == {
92
+ "organization_environment_uid": ENVIRONMENT_UID,
93
+ }
94
+ assert [request["time_out"] for request in requests] == [17, 17]
95
+
96
+
97
+ @pytest.mark.parametrize("list_payload", [[], [_secret_payload(), _secret_payload()]])
98
+ def test_secret_get_by_name_preserves_unique_lookup_errors_without_detail_request(
99
+ monkeypatch,
100
+ list_payload,
101
+ ):
102
+ requests = []
103
+
104
+ def fake_make_request(**kwargs):
105
+ requests.append(kwargs)
106
+ return _Response(list_payload)
107
+
108
+ monkeypatch.setattr(base_module, "make_request", fake_make_request)
109
+
110
+ expected_error = base_module.DoesNotExist if not list_payload else base_module.ApiError
111
+ with pytest.raises(expected_error):
112
+ models_module.Secret.get(name="API_KEY")
113
+
114
+ assert len(requests) == 1
115
+ assert requests[0]["url"].rstrip("/").endswith("secrets")
116
+
117
+
68
118
  def test_environment_resource_create_delete_and_detail_actions_are_scoped(monkeypatch):
69
119
  requests = []
70
120
 
@@ -25,6 +25,12 @@ RUNTIME_UPDATE_FIELD = "runtime_update"
25
25
  {ENVIRONMENT_UID_FIELD, ENVIRONMENT_NAME_FIELD, RUNTIME_UPDATE_FIELD},
26
26
  id="agent-semantic-search-result",
27
27
  ),
28
+ pytest.param(
29
+ ("AgentSessionSerializer",),
30
+ agent_models.AgentSession,
31
+ {ENVIRONMENT_UID_FIELD, ENVIRONMENT_NAME_FIELD},
32
+ id="agent-session",
33
+ ),
28
34
  pytest.param(
29
35
  ("JobSerializer",),
30
36
  helper_models.Job,
@@ -1870,6 +1870,8 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1870
1870
  "agent_uid": agent_uid,
1871
1871
  "agent_name": "Research Copilot",
1872
1872
  "agent_type": "custom",
1873
+ "organization_environment_uid": environment_uid,
1874
+ "organization_environment_name": "production",
1873
1875
  "harness": "tau",
1874
1876
  "harness_protocol": "tau-session-v1",
1875
1877
  "harness_version": "0.3.1",
@@ -1912,10 +1914,14 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1912
1914
  "tau_activity_sequence": "v1",
1913
1915
  "tau_turn_commit": "v1",
1914
1916
  },
1917
+ "catalog_digest": "sha256:" + ("a" * 64),
1915
1918
  }
1916
1919
  )
1917
1920
  assert session.uid == session_uid
1918
1921
  assert session.agent_uid == agent_uid
1922
+ assert session.organization_environment_uid == environment_uid
1923
+ assert session.organization_environment_name == "production"
1924
+ assert session.catalog_digest == "sha256:" + ("a" * 64)
1919
1925
  assert session.name == "Research follow-up"
1920
1926
  assert session.harness is agent_models_mod.AgentHarnessKind.TAU
1921
1927
  assert session.harness_protocol is agent_models_mod.AgentHarnessProtocol.TAU_SESSION_V1
@@ -2246,6 +2252,8 @@ def test_agent_session_list_and_detail_parse_runtime_capabilities(monkeypatch):
2246
2252
  "agent_uid": agent_uid,
2247
2253
  "agent_name": "Research Copilot",
2248
2254
  "agent_type": "custom",
2255
+ "organization_environment_uid": ENVIRONMENT_UID,
2256
+ "organization_environment_name": "production",
2249
2257
  "harness": "tau",
2250
2258
  "harness_protocol": "tau-session-v1",
2251
2259
  "harness_version": "0.3.1",
@@ -2266,6 +2274,7 @@ def test_agent_session_list_and_detail_parse_runtime_capabilities(monkeypatch):
2266
2274
  "tau_activity_sequence": "v1",
2267
2275
  "tau_turn_commit": "v1",
2268
2276
  },
2277
+ "catalog_digest": "sha256:" + ("b" * 64),
2269
2278
  }
2270
2279
  responses = iter(
2271
2280
  [
@@ -2295,6 +2304,9 @@ def test_agent_session_list_and_detail_parse_runtime_capabilities(monkeypatch):
2295
2304
  assert listed[0].runtime_capabilities["tau_turn_commit"] == "v1"
2296
2305
  assert detailed.runtime_capabilities == listed[0].runtime_capabilities
2297
2306
  assert detailed.observability.application_logs_url.endswith(f"/{session_uid}/logs/")
2307
+ assert listed[0].organization_environment_uid == ENVIRONMENT_UID
2308
+ assert detailed.organization_environment_name == "production"
2309
+ assert detailed.catalog_digest == "sha256:" + ("b" * 64)
2298
2310
 
2299
2311
  with pytest.raises(ValidationError, match="unexpected_projection"):
2300
2312
  agent_models_mod.AgentSession.model_validate(
@@ -2302,6 +2314,41 @@ def test_agent_session_list_and_detail_parse_runtime_capabilities(monkeypatch):
2302
2314
  )
2303
2315
 
2304
2316
 
2317
+ def test_agent_session_environment_and_catalog_projection_contract_is_strict():
2318
+ payload = {
2319
+ "organization_environment_uid": ENVIRONMENT_UID,
2320
+ "organization_environment_name": "production",
2321
+ "catalog_digest": None,
2322
+ "llm_provider": "openai",
2323
+ "llm_model": "gpt-5.4",
2324
+ }
2325
+
2326
+ session = agent_models_mod.AgentSession.model_validate(payload)
2327
+ assert session.catalog_digest is None
2328
+
2329
+ for field_name in (
2330
+ "organization_environment_uid",
2331
+ "organization_environment_name",
2332
+ "catalog_digest",
2333
+ ):
2334
+ incomplete = dict(payload)
2335
+ incomplete.pop(field_name)
2336
+ with pytest.raises(ValidationError, match=field_name):
2337
+ agent_models_mod.AgentSession.model_validate(incomplete)
2338
+
2339
+ for field_name in (
2340
+ "organization_environment_uid",
2341
+ "organization_environment_name",
2342
+ ):
2343
+ with pytest.raises(ValidationError, match=field_name):
2344
+ agent_models_mod.AgentSession.model_validate(payload | {field_name: None})
2345
+
2346
+ with pytest.raises(ValidationError, match="catalog_digest"):
2347
+ agent_models_mod.AgentSession.model_validate(
2348
+ payload | {"catalog_digest": "not-a-catalog-digest"}
2349
+ )
2350
+
2351
+
2305
2352
  def test_agent_session_get_insights_parses_pi_response(monkeypatch):
2306
2353
  session_uid = "3f1cc452-43ec-49cb-b2ba-87dbac164d29"
2307
2354
  captured = {}
@@ -2461,6 +2508,8 @@ def test_agent_session_archive_actions_return_current_session_contract(monkeypat
2461
2508
  "agent_uid": "9d81d63f-b8c9-404d-9f1a-5f2ad29dbf16",
2462
2509
  "agent_name": "Research Copilot",
2463
2510
  "agent_type": "custom",
2511
+ "organization_environment_uid": ENVIRONMENT_UID,
2512
+ "organization_environment_name": "production",
2464
2513
  "harness": "tau",
2465
2514
  "harness_protocol": "tau-session-v1",
2466
2515
  "harness_version": "0.3.1",
@@ -2484,6 +2533,7 @@ def test_agent_session_archive_actions_return_current_session_contract(monkeypat
2484
2533
  "thread_id": "thread-123",
2485
2534
  "session_metadata": {},
2486
2535
  "bound_handle": None,
2536
+ "catalog_digest": None,
2487
2537
  }
2488
2538
 
2489
2539
  def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
@@ -3671,6 +3721,16 @@ def test_agent_session_runtime_access_accepts_minimal_image_drift(monkeypatch):
3671
3721
  def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3672
3722
  captured = {"resolve_count": 0, "runtime": {}}
3673
3723
  session_uid = "3f1cc452-43ec-49cb-b2ba-87dbac164d29"
3724
+ session = agent_models_mod.AgentSession.model_validate(
3725
+ {
3726
+ "uid": session_uid,
3727
+ "organization_environment_uid": ENVIRONMENT_UID,
3728
+ "organization_environment_name": "production",
3729
+ "catalog_digest": "sha256:" + ("e" * 64),
3730
+ "llm_provider": "openai",
3731
+ "llm_model": "gpt-5.4",
3732
+ }
3733
+ )
3674
3734
 
3675
3735
  agent_models_mod.AgentSession.clear_cached_runtime_access(session_uid)
3676
3736
 
@@ -3733,7 +3793,7 @@ def test_agent_session_send_a2a_message_posts_standard_contract(monkeypatch):
3733
3793
  )
3734
3794
 
3735
3795
  payload = agent_models_mod.AgentSession.send_a2a_message(
3736
- session_uid,
3796
+ session,
3737
3797
  message="What can this agent do?",
3738
3798
  timeout=15,
3739
3799
  )
@@ -4455,6 +4515,8 @@ def test_agent_get_or_create_session_posts_new_contract(monkeypatch):
4455
4515
  "agent_uid": agent_uid,
4456
4516
  "agent_name": "Research Copilot",
4457
4517
  "agent_type": "custom",
4518
+ "organization_environment_uid": ENVIRONMENT_UID,
4519
+ "organization_environment_name": "production",
4458
4520
  "created_by_user_uid": user_uid,
4459
4521
  "parent_session_uid": parent_session_uid,
4460
4522
  "name": "Quarterly portfolio review",
@@ -4477,6 +4539,7 @@ def test_agent_get_or_create_session_posts_new_contract(monkeypatch):
4477
4539
  "owner_user_uid": user_uid,
4478
4540
  "is_locked": False,
4479
4541
  },
4542
+ "catalog_digest": "sha256:" + ("c" * 64),
4480
4543
  }
4481
4544
 
4482
4545
  def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
@@ -4501,6 +4564,8 @@ def test_agent_get_or_create_session_posts_new_contract(monkeypatch):
4501
4564
  assert session.uid == session_uid
4502
4565
  assert session.name == "Quarterly portfolio review"
4503
4566
  assert session.parent_session_uid == parent_session_uid
4567
+ assert session.organization_environment_uid == ENVIRONMENT_UID
4568
+ assert session.catalog_digest == "sha256:" + ("c" * 64)
4504
4569
  assert captured == {
4505
4570
  "r_type": "POST",
4506
4571
  "url": (
@@ -4551,6 +4616,8 @@ def test_agent_get_or_create_session_parses_reused_handle_capabilities(monkeypat
4551
4616
  "agent_uid": agent_uid,
4552
4617
  "agent_name": "Research Copilot",
4553
4618
  "agent_type": "custom",
4619
+ "organization_environment_uid": ENVIRONMENT_UID,
4620
+ "organization_environment_name": "production",
4554
4621
  "harness": "tau",
4555
4622
  "harness_protocol": "tau-session-v1",
4556
4623
  "harness_version": "0.3.1",
@@ -4577,6 +4644,7 @@ def test_agent_get_or_create_session_parses_reused_handle_capabilities(monkeypat
4577
4644
  "tau_activity_sequence": "v1",
4578
4645
  "tau_turn_commit": "v1",
4579
4646
  },
4647
+ "catalog_digest": "sha256:" + ("d" * 64),
4580
4648
  }
4581
4649
 
4582
4650
  def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
@@ -4591,6 +4659,8 @@ def test_agent_get_or_create_session_parses_reused_handle_capabilities(monkeypat
4591
4659
  assert session.bound_handle["handle_unique_id"] == handle_unique_id
4592
4660
  assert session.runtime_capabilities["tau_runtime_bootstrap"] == "v1"
4593
4661
  assert session.observability.application_logs_url.endswith(f"/{session_uid}/logs/")
4662
+ assert session.organization_environment_name == "production"
4663
+ assert session.catalog_digest == "sha256:" + ("d" * 64)
4594
4664
  assert captured["payload"] == {"json": {"handle_unique_id": handle_unique_id}}
4595
4665
  assert captured["timeout"] == 13
4596
4666
 
@@ -4625,12 +4695,15 @@ def test_agent_get_or_create_session_by_uid_sends_only_session_uid(monkeypatch):
4625
4695
  "agent_uid": agent_uid,
4626
4696
  "agent_name": "Research Copilot",
4627
4697
  "agent_type": "custom",
4698
+ "organization_environment_uid": ENVIRONMENT_UID,
4699
+ "organization_environment_name": "production",
4628
4700
  "name": "Existing session",
4629
4701
  "status": "running",
4630
4702
  "llm_provider": "openai",
4631
4703
  "llm_model": "gpt-5.4",
4632
4704
  "llm_thinking": "",
4633
4705
  "bound_handle": None,
4706
+ "catalog_digest": None,
4634
4707
  }
4635
4708
 
4636
4709
  def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
File without changes
File without changes
File without changes
File without changes