mainsequence 9.0.16.dev59__tar.gz → 9.0.17.dev60__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 (105) hide show
  1. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/PKG-INFO +1 -1
  2. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +24 -4
  3. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +41 -12
  4. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/_request_identity.py +2 -2
  5. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/models_user.py +12 -3
  6. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/server/fastapi.py +3 -2
  7. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence.egg-info/PKG-INFO +1 -1
  8. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/pyproject.toml +1 -1
  9. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_fastapi_mcp.py +41 -0
  10. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/LICENSE +0 -0
  11. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/README.md +0 -0
  12. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/AGENTS.md +0 -0
  13. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  14. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/mainsequence-inference/SKILL.md +0 -0
  15. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  16. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  17. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/maintenance/metatables_transition/SKILL.md +0 -0
  18. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  19. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  20. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/__init__.py +0 -0
  21. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/__main__.py +0 -0
  22. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/bootstrap.py +0 -0
  23. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/__init__.py +0 -0
  24. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/api.py +0 -0
  25. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/browser_auth.py +0 -0
  26. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/cli.py +0 -0
  27. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/code_repository_status.py +0 -0
  28. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/config.py +0 -0
  29. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/docker_utils.py +0 -0
  30. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/doctor.py +0 -0
  31. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/local_ops.py +0 -0
  32. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/model_filters.py +0 -0
  33. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/pydantic_cli.py +0 -0
  34. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/sdk_utils.py +0 -0
  35. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/ssh_utils.py +0 -0
  36. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/cli/ui.py +0 -0
  37. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/__init__.py +0 -0
  38. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/_transport.py +0 -0
  39. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/agent_runtime_models.py +0 -0
  40. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/base.py +0 -0
  41. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/client.py +0 -0
  42. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/compute_validation.py +0 -0
  43. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/exceptions.py +0 -0
  44. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/github_issues.py +0 -0
  45. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/inference.py +0 -0
  46. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/models_foundry.py +0 -0
  47. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/models_helpers.py +0 -0
  48. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/observability.py +0 -0
  49. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/utils.py +0 -0
  50. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/client/value_sets.py +0 -0
  51. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/code_repository_context.py +0 -0
  52. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/code_repository_skills.py +0 -0
  53. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/defaults.py +0 -0
  54. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/instrumentation/__init__.py +0 -0
  55. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/instrumentation/utils.py +0 -0
  56. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/logconf.py +0 -0
  57. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/repository_identity_security.py +0 -0
  58. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/runtime_credential_exchange.py +0 -0
  59. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/runtime_flags.py +0 -0
  60. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/scaffold_skills.py +0 -0
  61. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/server/__init__.py +0 -0
  62. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence/server/caller_assertions.py +0 -0
  63. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence.egg-info/SOURCES.txt +0 -0
  64. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence.egg-info/dependency_links.txt +0 -0
  65. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence.egg-info/entry_points.txt +0 -0
  66. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence.egg-info/requires.txt +0 -0
  67. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/mainsequence.egg-info/top_level.txt +0 -0
  68. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/setup.cfg +0 -0
  69. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_auth_bootstrap.py +0 -0
  70. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_auth_precedence.py +0 -0
  71. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_caller_assertions.py +0 -0
  72. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_canonical_api_route_contract.py +0 -0
  73. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_cli_auth_contract.py +0 -0
  74. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_cli_browser_auth.py +0 -0
  75. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_cli_secure_storage.py +0 -0
  76. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_client.py +0 -0
  77. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_code_repository_branch_contract.py +0 -0
  78. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_code_repository_documentation_contract.py +0 -0
  79. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_code_repository_runtime_context.py +0 -0
  80. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_code_repository_skills.py +0 -0
  81. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_credential_repr.py +0 -0
  82. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_dependency_extras.py +0 -0
  83. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_dev_release_version.py +0 -0
  84. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_environment_resource_context.py +0 -0
  85. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_environment_response_contracts.py +0 -0
  86. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_fastapi_request_identity.py +0 -0
  87. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_github_issues_client.py +0 -0
  88. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_http_retry_budget.py +0 -0
  89. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_independent_context.py +0 -0
  90. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_inference_client.py +0 -0
  91. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_instrumentation.py +0 -0
  92. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_job_client.py +0 -0
  93. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_local_ops.py +0 -0
  94. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_logconf.py +0 -0
  95. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_models_user_request_bound_auth.py +0 -0
  96. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_notification_uid_contract.py +0 -0
  97. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_observability_client.py +0 -0
  98. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_runtime_credential_exchange.py +0 -0
  99. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_scaffold_skills.py +0 -0
  100. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_secret_client_model.py +0 -0
  101. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_source_ownership.py +0 -0
  102. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_ssh_utils.py +0 -0
  103. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_tolerant_response_reading.py +0 -0
  104. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/tests/test_tolerant_value_sets.py +0 -0
  105. {mainsequence-9.0.16.dev59 → mainsequence-9.0.17.dev60}/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.16.dev59
3
+ Version: 9.0.17.dev60
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: MIT
@@ -191,6 +191,10 @@ def team_report(team_uid: str) -> dict[str, str]:
191
191
 
192
192
  - Authorize a requester-bound call against the requester. The acting
193
193
  application has no rights of its own unless the API's policy grants them.
194
+ - The requester can authorize reads and writes within that person's own
195
+ member-level permissions. Before a write, check the exact object and action;
196
+ the example's team-membership check is not a general edit, run, share or
197
+ delete grant. The getter verifies identity, not operation permission.
194
198
  - Fail closed: an operation that needs a person refuses the call when
195
199
  `User.get_requester()` is `None`. Never fall back to the caller's rights.
196
200
  - Never read a person's UID from the request body, a header or a query
@@ -201,9 +205,20 @@ def team_report(team_uid: str) -> dict[str, str]:
201
205
  `is_organization_admin` is always `False`: a requester's access reaches the
202
206
  API at member level only.
203
207
  - Inside a requester-bound call, `reads_as_caller()` raises
204
- `RequestIdentityError`: the requester's access is not passed on.
205
- - Test both paths: the requester-bound call authorized against the requester,
206
- and the same operation refused without a requester.
208
+ `RequestIdentityError`: a receiving application cannot forward its inbound
209
+ requester-bearing assertion to the directory endpoints. Platform-authorized
210
+ Agent delegation is separate and keeps the original person and request time.
211
+ - Hosted Agents never receive Secret values. The platform bounds work to at
212
+ most 24 hours after the original request and checks access on every call;
213
+ delegation does not restart that limit. This SDK does not enable platform
214
+ write endpoints or extend the inbound request scope.
215
+ - A model-driven Agent can be steered by prompt injection. Its writes can
216
+ cause damage within the person's permissions; keep object/action checks and
217
+ any mutation approval required by the application's policy outside model
218
+ claims or tool arguments.
219
+ - Test requester-authorized reads and writes, writes refused for a requester
220
+ without the necessary grant, and requester-required operations refused
221
+ without a requester even when the calling Agent has its own grants.
207
222
 
208
223
  Which applications may act for their requester, what that access covers, and
209
224
  what people are told:
@@ -218,7 +233,12 @@ To serve MCP tools from the same application, add the `mcp` extra and call
218
233
  after `install_request_identity(app)`, with a stateless FastMCP server that
219
234
  returns JSON. Tools authorize with `User.get_logged_user()` and
220
235
  `User.get_requester()` exactly like REST handlers; admission to the release
221
- does not authorize every tool. See
236
+ does not authorize every tool. For a tool invoked by an Agent on a person's
237
+ behalf, check that requester for both reads and writes. Refuse a
238
+ requester-required write when the getter returns `None`, and verify object- and
239
+ action-specific permission before mutation; read access and MCP annotations are not
240
+ write authority. Ordinary non-requester calls retain their own caller policy.
241
+ See
222
242
  [Serving MCP](https://mainsequence-sdk.github.io/mainsequence-sdk/knowledge/fastapi/#serving-mcp).
223
243
 
224
244
  ### Public provider callbacks and webhooks
@@ -240,14 +240,26 @@ whose own request it is serving. The platform setting is `acts_for_requester`.
240
240
 
241
241
  ### What the requester's access covers
242
242
 
243
- - Read only: the application never changes, shares or deletes anything with it.
243
+ - Reads and the creates, updates, runs, sharing changes and deletes the person
244
+ may make, where the platform endpoint supports requester-bound access.
245
+ - Check the person's permission for the exact object and operation on every
246
+ call. A read grant or team membership alone does not authorize a write.
244
247
  - The requester's member level, never an admin's, even when the requester is
245
248
  an Organization admin.
246
- - Never secret values.
249
+ - Hosted Agents never receive Secret values, including through this access.
247
250
  - At most 24 hours after the person's request, and only while the work for that
248
251
  request runs.
249
252
  - Checked again on every call, so it stops the moment the person's access ends.
250
- - Never passed on to another application.
253
+ - Platform-authorized Agent-to-Agent delegation keeps the person who started
254
+ the work and the original request time. Each delegated Agent must itself be
255
+ enabled to act for its requester. Delegation cannot restart the time limit,
256
+ select another person or increase their permissions.
257
+ - A receiving application cannot forward its inbound assertion to establish
258
+ another requester binding. `reads_as_caller()` still refuses these requests.
259
+
260
+ These writes and delegation require platform support; installing this SDK does
261
+ not enable them. Treat a refused operation as refused, never retry it with the
262
+ application's own grants.
251
263
 
252
264
  ### Never accept a person's UID from a request
253
265
 
@@ -255,19 +267,32 @@ The requester comes only from the platform. An application never accepts a
255
267
  person's UID from a request body, header or query parameter, and never lets a
256
268
  caller choose whom it acts for. A FastAPI application that receives a
257
269
  requester-bound call reads the requester with `User.get_requester()` and
258
- authorizes the call against that person:
270
+ authorizes reads and writes against that person's object- and operation-specific
271
+ permissions:
259
272
  `.agents/skills/mainsequence/application_surfaces/api_surfaces/SKILL.md`.
260
273
 
261
274
  ### What people are told
262
275
 
263
- Every client that shows an application with `acts_for_requester` shows this
264
- statement:
265
-
266
- > **This Agent works with your identity, securely.** It reads only what you can already read, only to answer your own requests, and for at most 24 hours after you ask. It cannot act as anyone else, cannot change, share or delete anything, never sees your secret values, and stops the moment your access ends. Your Organization's administrator approved it to work this way.
267
-
268
- Show the plain limit with it: while it works on your request, the Agent's code
269
- can read what you can read, which is why only administrators decide which
270
- Agents may work this way.
276
+ Every client that shows an application with `acts_for_requester` uses the exact
277
+ platform-approved statement from
278
+ [Platform ADR-0051, What people are told](https://github.com/Main-Sequence-Server-Side/tdag-django/blob/development/docs/platform/adr/adr-0051-requester-bound-access-for-workloads.md#11-what-people-are-told).
279
+ Do not invent or paraphrase a separate SDK statement. For write-enabled
280
+ deployments, the approved wording must cover supported reads and writes within
281
+ the person's member-level permissions, Organization-admin approval without
282
+ admin powers or Secret values, the original 24-hour limit including delegation,
283
+ access revocation, and prompt-injection risk. Never present a write-enabled
284
+ Agent as read-only or claim that it cannot change, share or delete anything.
285
+
286
+ The platform behavior and approved write-enabled statement must ship together.
287
+ If the platform still supplies only the earlier read-only text, do not claim
288
+ the disclosure is ready for a write-enabled rollout or invent replacement words.
289
+
290
+ Explain the risk alongside the statement: a model-driven Agent can be steered
291
+ by prompt injection. Its reads and writes can reach as far as the person's
292
+ member-level permissions allow, including sharing and deleting where
293
+ supported, for at most 24 hours after the original request. Only Organization
294
+ admins decide which Agents may receive this authority; the application's
295
+ per-operation permission checks remain necessary.
271
296
 
272
297
  ## Review Rules
273
298
 
@@ -286,6 +311,8 @@ When reviewing an access-control task, look for:
286
311
  - an application that takes a person's UID from a request instead of from the
287
312
  platform
288
313
  - `acts_for_requester` presented as something a non-admin can enable
314
+ - a requester-bound write authorized only by a read grant or the Agent's grants
315
+ - prompt-injection risk hidden behind a claim that requester access is read-only
289
316
 
290
317
  ## Validation Checklist
291
318
 
@@ -304,6 +331,8 @@ Do not claim success until you have checked:
304
331
  - the task did not confuse sharing policy with orchestration or producer logic
305
332
  - an application that acts for its requester takes the person only from the
306
333
  platform, and its own grants are intentional
334
+ - requester-bound writes enforce the person's permission for the operation,
335
+ and clients explain both the write risk and the original 24-hour limit
307
336
 
308
337
  ## This Skill Must Stop And Escalate When
309
338
 
@@ -79,8 +79,8 @@ def _reads_as_caller():
79
79
  )
80
80
  if scope.requester is not None:
81
81
  raise RequestIdentityError(
82
- "This request is requester-bound. The requester's access is not passed on, "
83
- "so a requester-bound request cannot read as its caller."
82
+ "This request is requester-bound. reads_as_caller() cannot forward a "
83
+ "requester-bearing assertion to the directory endpoints."
84
84
  )
85
85
  if scope.caller_assertion is None:
86
86
  raise RequestIdentityError(
@@ -1321,11 +1321,20 @@ class User(_CallerDirectoryReadMixin, UserApiBaseObjectOrm, BasePydanticModel):
1321
1321
  local mode and WebSockets. Raises ``RequestIdentityError`` outside an
1322
1322
  authenticated request, as ``get_logged_user()`` does.
1323
1323
 
1324
- Authorize a requester-bound request against the requester, and give the
1325
- acting application no rights of its own unless your policy grants them.
1324
+ Authorize reads and writes against the requester's permission for the
1325
+ exact object and operation. This getter verifies identity, not
1326
+ authorization; read access or team membership does not authorize a
1327
+ write. Give the acting application no rights of its own unless your
1328
+ policy grants them.
1326
1329
  An operation that needs a person fails closed when this is ``None``.
1327
1330
  Never take a person's UID from a request body, header or query
1328
- parameter instead.
1331
+ parameter instead. Platform-supported requester-bound writes stay
1332
+ within the person's member-level rights, never admin powers or Secret
1333
+ values. The platform checks access on every call and limits the work to
1334
+ at most 24 hours after the original request, including across approved
1335
+ Agent delegation. A model-driven Agent can be steered by prompt
1336
+ injection, so per-operation checks remain necessary. This getter does
1337
+ not enable platform write endpoints or extend its request scope.
1329
1338
  """
1330
1339
  requester = _get_requester()
1331
1340
  if requester is not None and not isinstance(requester, RequestUserIdentity):
@@ -295,8 +295,9 @@ def reads_as_caller():
295
295
  it. Raises ``RequestIdentityError`` outside an authenticated request or when
296
296
  the request carries no assertion (local mode, WebSockets); it never reads as
297
297
  the application instead. It also raises in a requester-bound request
298
- (``User.get_requester()`` is not ``None``): the requester's access is not
299
- passed on. See ADR-0036.
298
+ (``User.get_requester()`` is not ``None``): this helper cannot forward a
299
+ requester-bearing assertion to the directory endpoints. Platform-owned
300
+ Agent delegation is separate. See ADR-0036.
300
301
  """
301
302
  with _reads_as_caller():
302
303
  yield
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 9.0.16.dev59
3
+ Version: 9.0.17.dev60
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.16.dev59"
7
+ version = "9.0.17.dev60"
8
8
  description = "Main Sequence SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -135,6 +135,47 @@ def test_tools_read_the_verified_caller_and_rest_routes_keep_working(signed):
135
135
  assert "/mcp" not in app.openapi()["paths"]
136
136
 
137
137
 
138
+ def test_requester_bound_write_checks_the_person_without_workload_fallback(signed):
139
+ token, _, _ = signed
140
+ server = _server()
141
+ report_editors = {USER, OTHER}
142
+ writes = []
143
+
144
+ @server.tool()
145
+ async def publish_report() -> str:
146
+ requester = User.get_requester()
147
+ if requester is None or requester.uid not in report_editors:
148
+ raise PermissionError("The requester cannot publish this report.")
149
+ writes.append((User.get_logged_user().uid, requester.uid))
150
+ return requester.uid
151
+
152
+ app = _install(FastAPI(), server)
153
+ with TestClient(app) as client:
154
+ allowed = client.post(
155
+ "/mcp",
156
+ json=_call("publish_report"),
157
+ headers={
158
+ **MCP_HEADERS,
159
+ ASSERTION_HEADER: token(requester={"sub": OTHER, "team_uids": []}),
160
+ },
161
+ )
162
+ assert _text(allowed) == OTHER
163
+
164
+ for proof in (
165
+ token(requester={"sub": str(uuid4()), "team_uids": []}),
166
+ token(),
167
+ ):
168
+ refused = client.post(
169
+ "/mcp",
170
+ json=_call("publish_report"),
171
+ headers={**MCP_HEADERS, ASSERTION_HEADER: proof},
172
+ )
173
+ assert refused.status_code == 200
174
+ assert refused.json()["result"]["isError"] is True
175
+
176
+ assert writes == [(USER, OTHER)]
177
+
178
+
138
179
  def test_concurrent_callers_keep_their_own_identity(signed):
139
180
  token, _, _ = signed
140
181
  server = _server()