-
Notifications
You must be signed in to change notification settings - Fork 589
feat(platform): agent mounts, one durable folder per agent #5247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
beb9caa
9b77f2e
fc0e857
d78504c
c859139
3f5c822
307a74b
990659c
5be9ab4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| from oss.src.core.mounts.interfaces import MountsDAOInterface | ||
| from oss.src.core.store.storage import ObjectStore | ||
| from oss.src.core.mounts.types import ( | ||
| MountArtifactIdInvalid, | ||
| MountFileNotFound, | ||
| MountNameInvalid, | ||
| MountNotFound, | ||
|
|
@@ -73,6 +74,21 @@ def mint_session_slug(*, session_id: str, name: str) -> str: | |
| return f"{_RESERVED_SLUG_PREFIX}{uuid5(_MOUNTS_NAMESPACE, session_id)}__{slugify_mount_name(name)}" | ||
|
|
||
|
|
||
| def mint_agent_slug(*, artifact_id: str, name: str) -> str: | ||
| """Mint the deterministic reserved slug for an artifact mount. | ||
|
|
||
| Artifact IDs are UUID-parsed and rendered lowercase. Sign and query must use | ||
| this same derivation byte-identically so they address the same mount. | ||
| """ | ||
| try: | ||
| canonical_artifact_id = UUID(str(artifact_id)) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sign and query both derive the slug from the artifact id, so if the two derivations ever differed by a byte, a real mount would read as absent in the UI. Parsing to a UUID and lowercasing keeps them identical. It also turns a non-UUID id into a clean 422 here, instead of a 500 later when the slug validator rejects it. |
||
| except (ValueError, TypeError, AttributeError) as e: | ||
| raise MountArtifactIdInvalid(str(artifact_id)) from e | ||
|
|
||
| slug_name = slugify_mount_name(name) | ||
| return f"{_RESERVED_SLUG_PREFIX}agent__{canonical_artifact_id}__{slug_name}" | ||
|
|
||
|
|
||
| def reject_reserved_slug(slug: str) -> None: | ||
| """A caller may not author a slug in the reserved namespace (the service mints those).""" | ||
| if slug.startswith(_RESERVED_SLUG_PREFIX): | ||
|
|
@@ -188,6 +204,26 @@ async def get_or_create_session_mount( | |
| mount_create=mount_create, | ||
| ) | ||
|
|
||
| async def get_or_create_agent_mount( | ||
| self, | ||
| *, | ||
| project_id: UUID, | ||
| user_id: UUID, | ||
| artifact_id: str, | ||
| name: str = "default", | ||
| ) -> Mount: | ||
| """Bind idempotently one durable mount for an artifact, keyed by name.""" | ||
| slug_name = slugify_mount_name(name) | ||
| mount_create = MountCreate( | ||
| slug=mint_agent_slug(artifact_id=artifact_id, name=name), | ||
| name=slug_name, | ||
| ) | ||
| return await self.mounts_dao.upsert_mount( | ||
| project_id=project_id, | ||
| user_id=user_id, | ||
| mount_create=mount_create, | ||
| ) | ||
|
|
||
| async def get_or_create_session_cwd( | ||
| self, | ||
| *, | ||
|
|
@@ -216,6 +252,20 @@ async def fetch_mount( | |
| mount_id=mount_id, | ||
| ) | ||
|
|
||
| async def fetch_agent_mount( | ||
| self, | ||
| *, | ||
| project_id: UUID, | ||
| artifact_id: str, | ||
| name: str = "default", | ||
| ) -> Optional[Mount]: | ||
| """Fetch the active artifact mount keyed by name without creating it.""" | ||
| slug = mint_agent_slug(artifact_id=artifact_id, name=name) | ||
| return await self.mounts_dao.fetch_mount_by_slug( | ||
| project_id=project_id, | ||
| slug=slug, | ||
| ) | ||
|
|
||
| async def edit_mount( | ||
| self, | ||
| *, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| """Acceptance tests for artifact-scoped agent mounts. | ||
|
|
||
| Query-only tests run without an object store. Credential-signing tests skip when | ||
| the running API has no mount storage backend configured. | ||
| """ | ||
|
|
||
| from uuid import uuid4 | ||
|
|
||
| import pytest | ||
|
|
||
|
|
||
| def _query_agent_mount(authed_api, artifact_id, *, name="default"): | ||
| return authed_api( | ||
| "POST", | ||
| "/mounts/agents/query", | ||
| json={"artifact_id": artifact_id, "name": name}, | ||
| ) | ||
|
|
||
|
|
||
| def _sign_agent_mount(authed_api, artifact_id, *, name="default"): | ||
| response = authed_api( | ||
| "POST", | ||
| "/mounts/agents/sign", | ||
| params={"artifact_id": artifact_id, "name": name}, | ||
| ) | ||
| if response.status_code == 503: | ||
| pytest.skip("Mount storage backend not configured in this environment") | ||
| return response | ||
|
|
||
|
|
||
| class TestAgentMountReads: | ||
| def test_query_rejects_non_uuid_artifact_id(self, authed_api): | ||
| response = _query_agent_mount(authed_api, "not-a-uuid") | ||
| assert response.status_code == 422, response.text | ||
|
|
||
| def test_query_unknown_uuid_stays_empty(self, authed_api): | ||
| artifact_id = str(uuid4()) | ||
|
|
||
| first = _query_agent_mount(authed_api, artifact_id) | ||
| assert first.status_code == 200, first.text | ||
| assert first.json()["count"] == 0 | ||
| assert first.json()["mounts"] == [] | ||
|
|
||
| second = _query_agent_mount(authed_api, artifact_id) | ||
| assert second.status_code == 200, second.text | ||
| assert second.json()["count"] == 0 | ||
| assert second.json()["mounts"] == [] | ||
|
|
||
|
|
||
| class TestAgentMountSign: | ||
| def test_sign_then_query_returns_same_mount(self, authed_api): | ||
| artifact_id = str(uuid4()) | ||
| signed = _sign_agent_mount(authed_api, artifact_id) | ||
| assert signed.status_code == 200, signed.text | ||
| mount_id = signed.json()["mount"]["id"] | ||
|
|
||
| queried = _query_agent_mount(authed_api, artifact_id) | ||
| assert queried.status_code == 200, queried.text | ||
| assert queried.json()["count"] == 1 | ||
| assert queried.json()["mounts"][0]["id"] == mount_id | ||
|
|
||
| def test_sign_twice_returns_same_mount(self, authed_api): | ||
| artifact_id = str(uuid4()) | ||
| first = _sign_agent_mount(authed_api, artifact_id) | ||
| assert first.status_code == 200, first.text | ||
|
|
||
| second = _sign_agent_mount(authed_api, artifact_id) | ||
| assert second.status_code == 200, second.text | ||
| assert second.json()["mount"]["id"] == first.json()["mount"]["id"] |
Uh oh!
There was an error while loading. Please reload this page.