mainsequence 9.0.25.dev69__tar.gz → 9.0.26__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 (112) hide show
  1. {mainsequence-9.0.25.dev69/mainsequence.egg-info → mainsequence-9.0.26}/PKG-INFO +1 -1
  2. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +49 -3
  3. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +7 -0
  4. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26/mainsequence.egg-info}/PKG-INFO +1 -1
  5. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/pyproject.toml +1 -1
  6. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_team_memberships.py +21 -0
  7. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/LICENSE +0 -0
  8. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/README.md +0 -0
  9. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  10. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/mainsequence-inference/SKILL.md +0 -0
  11. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  12. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  13. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/maintenance/metatables_transition/SKILL.md +0 -0
  14. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  15. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  16. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/__init__.py +0 -0
  17. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/__main__.py +0 -0
  18. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/_request_identity.py +0 -0
  19. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/_team_memberships.py +0 -0
  20. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/_transport.py +0 -0
  21. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/bootstrap.py +0 -0
  22. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/__init__.py +0 -0
  23. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/api.py +0 -0
  24. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/browser_auth.py +0 -0
  25. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/cli.py +0 -0
  26. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/code_repository_status.py +0 -0
  27. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/config.py +0 -0
  28. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/docker_utils.py +0 -0
  29. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/doctor.py +0 -0
  30. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/local_ops.py +0 -0
  31. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/model_filters.py +0 -0
  32. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/pydantic_cli.py +0 -0
  33. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/sdk_utils.py +0 -0
  34. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/ssh_utils.py +0 -0
  35. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/cli/ui.py +0 -0
  36. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/__init__.py +0 -0
  37. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/agent_runtime_models.py +0 -0
  38. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/base.py +0 -0
  39. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/client.py +0 -0
  40. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/compute_validation.py +0 -0
  41. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/exceptions.py +0 -0
  42. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/github_issues.py +0 -0
  43. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/inference.py +0 -0
  44. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/models_foundry.py +0 -0
  45. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/models_helpers.py +0 -0
  46. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/models_user.py +0 -0
  47. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/observability.py +0 -0
  48. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/utils.py +0 -0
  49. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/client/value_sets.py +0 -0
  50. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/code_repository_context.py +0 -0
  51. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/code_repository_skills.py +0 -0
  52. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/defaults.py +0 -0
  53. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/instrumentation/__init__.py +0 -0
  54. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/instrumentation/utils.py +0 -0
  55. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/logconf.py +0 -0
  56. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/repository_identity_security.py +0 -0
  57. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/runtime_credential_exchange.py +0 -0
  58. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/runtime_flags.py +0 -0
  59. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/scaffold_skills.py +0 -0
  60. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/server/__init__.py +0 -0
  61. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/server/_mcp_skills_upstream/__init__.py +0 -0
  62. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/server/_mcp_skills_upstream/server.py +0 -0
  63. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/server/_mcp_skills_upstream/shared.py +0 -0
  64. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/server/caller_assertions.py +0 -0
  65. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/server/fastapi.py +0 -0
  66. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence/server/mcp_skills.py +0 -0
  67. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence.egg-info/SOURCES.txt +0 -0
  68. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence.egg-info/dependency_links.txt +0 -0
  69. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence.egg-info/entry_points.txt +0 -0
  70. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence.egg-info/requires.txt +0 -0
  71. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/mainsequence.egg-info/top_level.txt +0 -0
  72. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/setup.cfg +0 -0
  73. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_auth_bootstrap.py +0 -0
  74. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_auth_precedence.py +0 -0
  75. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_caller_assertions.py +0 -0
  76. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_canonical_api_route_contract.py +0 -0
  77. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_cli_auth_contract.py +0 -0
  78. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_cli_browser_auth.py +0 -0
  79. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_cli_request_budget.py +0 -0
  80. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_cli_secure_storage.py +0 -0
  81. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_client.py +0 -0
  82. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_code_repository_branch_contract.py +0 -0
  83. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_code_repository_documentation_contract.py +0 -0
  84. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_code_repository_runtime_context.py +0 -0
  85. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_code_repository_skills.py +0 -0
  86. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_credential_repr.py +0 -0
  87. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_dependency_extras.py +0 -0
  88. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_dev_release_version.py +0 -0
  89. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_environment_resource_context.py +0 -0
  90. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_environment_response_contracts.py +0 -0
  91. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_fastapi_mcp.py +0 -0
  92. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_fastapi_request_identity.py +0 -0
  93. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_github_issues_client.py +0 -0
  94. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_http_retry_budget.py +0 -0
  95. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_independent_context.py +0 -0
  96. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_inference_client.py +0 -0
  97. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_instrumentation.py +0 -0
  98. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_job_client.py +0 -0
  99. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_local_ops.py +0 -0
  100. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_logconf.py +0 -0
  101. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_mcp_skills.py +0 -0
  102. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_models_user_request_bound_auth.py +0 -0
  103. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_notification_uid_contract.py +0 -0
  104. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_observability_client.py +0 -0
  105. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_runtime_credential_exchange.py +0 -0
  106. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_scaffold_skills.py +0 -0
  107. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_secret_client_model.py +0 -0
  108. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_source_ownership.py +0 -0
  109. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_ssh_utils.py +0 -0
  110. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_tolerant_response_reading.py +0 -0
  111. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_tolerant_value_sets.py +0 -0
  112. {mainsequence-9.0.25.dev69 → mainsequence-9.0.26}/tests/test_user_application_facts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 9.0.25.dev69
3
+ Version: 9.0.26
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: MIT
@@ -160,6 +160,52 @@ connection is active. See the version-matched
160
160
  [FastAPI identity documentation](https://mainsequence-sdk.github.io/mainsequence-sdk/knowledge/fastapi/)
161
161
  for the lifecycle and trust contract.
162
162
 
163
+ ### Team membership
164
+
165
+ The application owns its team policies. Check whether the caller belongs to
166
+ the teams a policy names with `in_teams()`. Never take team facts from the
167
+ request body, a header, a query parameter or the assertion:
168
+
169
+ ```python
170
+ from fastapi import HTTPException
171
+ from mainsequence.client import User
172
+
173
+
174
+ @app.post("/teams/{team_uid}/reports")
175
+ def publish_report(team_uid: str) -> dict[str, str]:
176
+ caller = User.get_logged_user()
177
+ # A write: ask the platform now instead of reusing an older answer.
178
+ if team_uid not in caller.in_teams([team_uid], max_age_seconds=0):
179
+ raise HTTPException(status_code=403, detail="Only the team's members can publish.")
180
+ return {"team_uid": team_uid, "published_by": caller.uid}
181
+ ```
182
+
183
+ - `in_teams(team_uids, *, max_age_seconds=None)` returns the subset of
184
+ `team_uids` the person is an active member of, as a `frozenset` of canonical
185
+ UIDs; an empty set means none. Unknown and inactive teams are left out. It
186
+ works on `User.get_logged_user()` and `User.get_requester()`, asks only about
187
+ that request's caller or requester, and sends longer lists in calls of at
188
+ most 100 teams.
189
+ - How long an answer may be reused is the application's choice.
190
+ `install_request_identity(app, team_membership_cache_seconds=60)` reuses an
191
+ answer across requests while it is younger than 60 seconds; the default, `0`,
192
+ reuses it only within its request. `max_age_seconds` sets the lifetime for
193
+ one call, and `max_age_seconds=0` always asks the platform: use it before a
194
+ write or anything that must see a removal at once.
195
+ - It raises `RequestIdentityError` outside a signed HTTP request, in local mode
196
+ and on WebSockets, and the SDK's API error when the platform cannot answer.
197
+ Let the request fail: never read an exception as "member" or "not a member".
198
+ - It calls the platform when it has no answer to reuse, and that call blocks.
199
+ Call it from a `def` handler, or from an `async def` handler with
200
+ `await asyncio.to_thread(caller.in_teams, team_uids)`, which keeps the
201
+ request's identity.
202
+ - `team_uids` on the identity is deprecated and warns when read. In a signed
203
+ HTTP request it raises `RequestIdentityError` when the assertion carried no
204
+ team list, as every assertion will once the platform stops signing them.
205
+ Replace every check that reads it with `in_teams()`.
206
+ - Membership is not team administration and not a grant on an object: check
207
+ the exact object and action as well.
208
+
163
209
  ### Requester-bound calls
164
210
 
165
211
  Another application, for example an Agent, can call the API while it works for
@@ -168,9 +214,9 @@ assertion it sends to the API, and the SDK verifies it before the handler runs.
168
214
  `User.get_logged_user()` still returns the caller, the acting application.
169
215
  `User.get_requester()` returns the requester, the checked delegation, with the
170
216
  person's own `uid` and `is_organization_admin`, or `None` when the call carries
171
- no delegation. `in_teams(team_uids)` on either identity asks the platform which
172
- of those teams the person belongs to; `team_uids` is deprecated. A requester-bound call carries the person's
173
- ordinary permissions, including administrative ones.
217
+ no delegation. `in_teams()` on the requester answers for the person's teams
218
+ ([Team membership](#team-membership)). A requester-bound call carries the
219
+ person's ordinary permissions, including administrative ones.
174
220
 
175
221
  Each handler decides whether it needs a person. One that needs a person fails
176
222
  with its own error when there is none; one where the person is optional
@@ -136,6 +136,10 @@ Keep these separate:
136
136
  - inherited access to shared resources
137
137
  - administration of the team itself
138
138
 
139
+ An application that checks whether its caller or requester belongs to a team
140
+ uses `in_teams()`, never the deprecated `team_uids`; see
141
+ `.agents/skills/mainsequence/application_surfaces/api_surfaces/SKILL.md`.
142
+
139
143
  ### 5. `Constant` vs `Secret` is a security boundary
140
144
 
141
145
  Use `Constant` for non-sensitive runtime values.
@@ -304,6 +308,9 @@ When reviewing an access-control task, look for:
304
308
  - using direct user grants where a team should be used
305
309
  - using a team when the access is clearly one-off
306
310
  - treating team membership as team administration
311
+ - team checks that read the deprecated `team_uids` instead of `in_teams()`,
312
+ that read an `in_teams()` error as "not a member", or that reuse an older
313
+ answer (no `max_age_seconds=0`) before a write that must see a removal
307
314
  - storing sensitive data in a `Constant`
308
315
  - creating a `Constant` or `Secret` blindly without resolving whether the name already exists
309
316
  - weak or unverified claims about who can access a resource
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 9.0.25.dev69
3
+ Version: 9.0.26
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "9.0.25.dev69"
7
+ version = "9.0.26"
8
8
  description = "Main Sequence SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -5,6 +5,7 @@ membership check is a fake ``make_request`` that answers from a set of
5
5
  memberships and records every call.
6
6
  """
7
7
 
8
+ import asyncio
8
9
  import time
9
10
  import warnings
10
11
  from types import SimpleNamespace
@@ -157,6 +158,26 @@ def test_the_caller_learns_which_of_the_asked_teams_it_belongs_to(sign, platform
157
158
  assert call.payload["headers"] == {ASSERTION_HEADER: proof}
158
159
 
159
160
 
161
+ def test_an_async_handler_asks_through_a_worker_thread_as_its_caller(sign, platform):
162
+ # The call blocks, so an async handler runs it in a worker thread; the
163
+ # thread keeps the request's identity (the skill and knowledge page say so).
164
+ research, operations = teams(2)
165
+ platform.members = {(CALLER, research)}
166
+ proof = sign()
167
+
168
+ async def handler():
169
+ caller = User.get_logged_user()
170
+ return sorted(await asyncio.to_thread(caller.in_teams, [research, operations]))
171
+
172
+ with TestClient(app_with(handler)) as client:
173
+ answer = client.get("/check", headers={ASSERTION_HEADER: proof}).json()
174
+
175
+ assert answer == [research]
176
+ (call,) = platform.calls
177
+ assert call.person == CALLER
178
+ assert call.payload["headers"] == {ASSERTION_HEADER: proof}
179
+
180
+
160
181
  def test_the_requester_and_the_acting_caller_are_asked_about_separately(sign, platform):
161
182
  research, operations = teams(2)
162
183
  platform.members = {(PERSON, research), (CALLER, operations)}