Skip to content

Commit a9ab0d3

Browse files
feat: Fence browser allowlist updates with revision checks
Stainless-Generated-From: 98e3c1efbf1a574ca1fd596e616e5a0dd857b703
1 parent c1a6765 commit a9ab0d3

7 files changed

Lines changed: 83 additions & 2 deletions

File tree

‎api.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ Types:
117117

118118
```python
119119
from kernel.types import (
120+
BrowserAllowlistState,
120121
BrowserMemory,
121122
BrowserMemoryRequest,
122123
BrowserNetworkConfig,

‎src/kernel/types/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
from .invocation_state_event import InvocationStateEvent as InvocationStateEvent
9999
from .recommendation_summary import RecommendationSummary as RecommendationSummary
100100
from .api_key_retrieve_params import APIKeyRetrieveParams as APIKeyRetrieveParams
101+
from .browser_allowlist_state import BrowserAllowlistState as BrowserAllowlistState
101102
from .browser_create_response import BrowserCreateResponse as BrowserCreateResponse
102103
from .browser_retrieve_params import BrowserRetrieveParams as BrowserRetrieveParams
103104
from .browser_update_response import BrowserUpdateResponse as BrowserUpdateResponse
@@ -138,6 +139,7 @@
138139
from .deployment_retrieve_response import DeploymentRetrieveResponse as DeploymentRetrieveResponse
139140
from .invocation_retrieve_response import InvocationRetrieveResponse as InvocationRetrieveResponse
140141
from .audit_log_export_chunk_params import AuditLogExportChunkParams as AuditLogExportChunkParams
142+
from .browser_allowlist_state_param import BrowserAllowlistStateParam as BrowserAllowlistStateParam
141143
from .browser_pool_acquire_response import BrowserPoolAcquireResponse as BrowserPoolAcquireResponse
142144
from .config_registry_lookup_params import ConfigRegistryLookupParams as ConfigRegistryLookupParams
143145
from .credential_totp_code_response import CredentialTotpCodeResponse as CredentialTotpCodeResponse
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
from typing import Optional
4+
5+
from .._models import BaseModel
6+
7+
__all__ = ["BrowserAllowlistState"]
8+
9+
10+
class BrowserAllowlistState(BaseModel):
11+
revision: str
12+
"""Opaque revision to pass as network.expected_allowlist_revision on an update."""
13+
14+
base_revision: Optional[str] = None
15+
"""
16+
Revision against which that mutation was accepted; reuse it when retrying the
17+
mutation.
18+
"""
19+
20+
mutation_id: Optional[str] = None
21+
"""ID of the last conditional allowlist update saved on this browser."""
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
from __future__ import annotations
4+
5+
from typing_extensions import Required, TypedDict
6+
7+
__all__ = ["BrowserAllowlistStateParam"]
8+
9+
10+
class BrowserAllowlistStateParam(TypedDict, total=False):
11+
revision: Required[str]
12+
"""Opaque revision to pass as network.expected_allowlist_revision on an update."""
13+
14+
base_revision: str
15+
"""
16+
Revision against which that mutation was accepted; reuse it when retrying the
17+
mutation.
18+
"""
19+
20+
mutation_id: str
21+
"""ID of the last conditional allowlist update saved on this browser."""

‎src/kernel/types/browser_network_config.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import List, Optional
44

55
from .._models import BaseModel
6+
from .browser_allowlist_state import BrowserAllowlistState
67

78
__all__ = ["BrowserNetworkConfig", "ProxyRoute", "ProxyRouteProxy"]
89

@@ -58,6 +59,14 @@ class BrowserNetworkConfig(BaseModel):
5859
replaced on release rather than given an allowlist again. Requires proxy v3.
5960
"""
6061

62+
allowlist_state: Optional[BrowserAllowlistState] = None
63+
"""Concurrency metadata for a browser's saved allowlist configuration.
64+
65+
Browser pools omit it. This remains present after removal so an uncertain
66+
removal can be retried. It does not prove proxy or VM application completed. Do
67+
not include it in creation or pool updates.
68+
"""
69+
6170
private_hosts: Optional[List[str]] = None
6271
"""
6372
Destinations the browser reaches directly through the session's own network

‎src/kernel/types/browser_network_update_param.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,22 @@ class BrowserNetworkUpdateParam(TypedDict, total=False):
3434
safely restored. If the request fails, retry it: the new list may already apply
3535
to some requests.
3636
"""
37+
38+
expected_allowlist_revision: str
39+
"""The network.allowlist_state.revision from the browser's last saved snapshot.
40+
41+
Supply together with mutation_id to opt into conditional allowlist updates. The
42+
revision is checked atomically with the configuration write. A stale request
43+
returns 409 stale_network without changing the allowlist. A GET alone does not
44+
fence an earlier unfinished PATCH.
45+
"""
46+
47+
mutation_id: str
48+
"""
49+
A unique ID for this allowlist update, supplied together with
50+
expected_allowlist_revision. Reuse both values and the identical allowed_hosts
51+
payload when retrying an uncertain update. If it is still the current mutation,
52+
the retry finishes proxy and VM application without creating another revision. A
53+
superseded mutation returns 409 stale_network. These fields guard only
54+
network.allowed_hosts, not other browser update fields.
55+
"""

‎tests/api_resources/test_browsers.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
211211
id_or_name="htzv5orfit78e1m2biiifpbv",
212212
disable_default_proxy=True,
213213
name="checkout-flow-1",
214-
network={"allowed_hosts": ["example.com", "*.example.com"]},
214+
network={
215+
"allowed_hosts": ["example.com", "*.example.com"],
216+
"expected_allowlist_revision": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
217+
"mutation_id": "mutation_id",
218+
},
215219
profile={
216220
"id": "id",
217221
"name": "name",
@@ -758,7 +762,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
758762
id_or_name="htzv5orfit78e1m2biiifpbv",
759763
disable_default_proxy=True,
760764
name="checkout-flow-1",
761-
network={"allowed_hosts": ["example.com", "*.example.com"]},
765+
network={
766+
"allowed_hosts": ["example.com", "*.example.com"],
767+
"expected_allowlist_revision": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
768+
"mutation_id": "mutation_id",
769+
},
762770
profile={
763771
"id": "id",
764772
"name": "name",

0 commit comments

Comments
 (0)