Skip to content

Commit f48e808

Browse files
UN-3479 [FIX] Agentic project sharing via share action (groups included)
Share axes are read-only on AgenticProjectSerializer, so a detail PATCH silently no-ops. Route share replication through the dedicated share action (POST agentic/projects/{id}/share/), which also handles the polymorphic group axis — so agentic group shares now replicate like every other shared resource. Drops the now-dead update_agentic_project_share client method and the include_groups=False special-case. Found while prepping live staging test scenarios for the clone tool. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsGrHbs5SWmQkKqiimg6CF
1 parent a405e90 commit f48e808

3 files changed

Lines changed: 22 additions & 37 deletions

File tree

‎src/unstract/clone/client.py‎

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -845,19 +845,6 @@ def create_agentic_project(self, payload: dict[str, Any]) -> dict[str, Any]:
845845
"""Create an agentic project. Returns the created row (carries ``id``)."""
846846
return self._request("POST", "agentic/projects/", json=payload)
847847

848-
def update_agentic_project_share(
849-
self, project_id: str, payload: dict[str, Any]
850-
) -> dict[str, Any]:
851-
"""Replicate share state onto an agentic project via its detail PATCH.
852-
853-
``payload`` carries ``shared_to_org`` + ``shared_users`` (target user
854-
pks). ``shared_groups`` is polymorphic/read-only on this serializer and
855-
is handled by the share helper's group-omission warning.
856-
"""
857-
return self._request(
858-
"PATCH", f"agentic/projects/{project_id}/", json=payload
859-
)
860-
861848
def list_agentic_prompt_versions(
862849
self, *, project_id: str | None = None
863850
) -> list[dict[str, Any]]:

‎src/unstract/clone/phases/agentic_studio.py‎

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from unstract.clone.exceptions import NameConflictError
4242
from unstract.clone.phases.base import Phase, build_post_payload
4343
from unstract.clone.report import CloneReport, PhaseResult
44-
from unstract.clone.sharing import replicate_share
44+
from unstract.clone.sharing import apply_share_state
4545

4646
logger = logging.getLogger(__name__)
4747

@@ -216,23 +216,18 @@ def _replicate_share(
216216
result: PhaseResult,
217217
lock: threading.Lock,
218218
) -> None:
219-
# The project list/detail share the same serializer, so the source row
220-
# already carries shared_users (target-mappable user pks), shared_to_org
221-
# and created_by — no detail fetch needed.
222-
# Group sharing isn't replicated: shared_groups is polymorphic/read-only
223-
# on the project serializer (the detail PATCH only reaches shared_users +
224-
# shared_to_org), so include_groups is off and a source group share
225-
# yields a single warning.
226-
replicate_share(
219+
# The list row already carries shared_users / shared_groups /
220+
# shared_to_org, so no detail fetch is needed. Share axes are read-only
221+
# on the serializer (a detail PATCH is a silent no-op); they're written
222+
# via the dedicated share action, which handles the polymorphic group
223+
# axis too — so groups replicate like every other shared resource.
224+
apply_share_state(
227225
self.ctx,
228-
apply_fn=lambda p: self.ctx.target.update_agentic_project_share(
229-
tgt_project_id, p
230-
),
226+
share_path=f"agentic/projects/{tgt_project_id}/share/",
231227
entity_label=f"agentic project '{name}'",
232228
src=src,
233229
result=result,
234230
lock=lock,
235-
include_groups=False,
236231
)
237232

238233
# ----- prompt versions -----

‎tests/clone/test_agentic_studio_phase.py‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ def create_agentic_project(self, payload):
7979
self.created_projects.append(new)
8080
return new
8181

82-
def update_agentic_project_share(self, project_id, payload):
83-
self.shared_projects.append((project_id, payload))
84-
return {"id": project_id, **payload}
82+
def share_resource(self, share_path, payload):
83+
self.shared_projects.append((share_path, payload))
84+
return payload
8585

8686
# ----- users (share replication) -----
8787

@@ -403,32 +403,35 @@ def test_share_replicated_with_mapped_users_and_org_flag():
403403

404404
tgt_pid = tgt.created_projects[0]["id"]
405405
assert len(tgt.shared_projects) == 1
406-
pid, payload = tgt.shared_projects[0]
407-
assert pid == tgt_pid
406+
share_path, payload = tgt.shared_projects[0]
407+
assert share_path == f"agentic/projects/{tgt_pid}/share/"
408408
assert payload["shared_to_org"] is True
409409
assert payload["shared_users"] == [42] # owner dropped, alice remapped
410-
assert "shared_groups" not in payload # include_groups=False
410+
assert payload["shared_groups"] == [] # no source groups, axis still sent
411411

412412

413-
def test_source_group_share_warns_without_failure():
413+
def test_source_group_share_replicated_via_remap():
414+
# Group sharing IS supported on agentic projects (via the share action),
415+
# so a source group share maps through the `group` remap onto the target.
414416
src = FakeClient(
415417
projects=[
416418
{
417419
"id": "src-p",
418420
"name": "Receipts",
419421
"description": "d",
420422
"created_by": 1,
421-
"shared_groups": [7], # unsupported via the project PATCH
423+
"shared_groups": [7],
422424
}
423425
],
424426
users=[{"id": 1, "email": "owner@x.com"}],
425427
)
426428
tgt = FakeClient()
427429
ctx = _ctx(src, tgt)
430+
ctx.remap.record("group", "7", "70")
428431

429432
result = AgenticStudioPhase(ctx).run(CloneReport())
430433

431434
assert result.failed == 0
432-
assert sum("group share(s) not supported" in w for w in result.warnings) == 1
433-
# No shareable axes remained, so no PATCH fires.
434-
assert tgt.shared_projects == []
435+
assert len(tgt.shared_projects) == 1
436+
_, payload = tgt.shared_projects[0]
437+
assert payload["shared_groups"] == [70]

0 commit comments

Comments
 (0)