Skip to content

Commit d91b199

Browse files
authored
feat(api)!: use sandbox names as canonical RPC references (#3272)
* feat(api)!: use sandbox names as canonical references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(cli): update forward color fixture for workspace scope Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(supervisor): use sandbox names for settings lookup Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): use canonical sandbox request fields Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): use canonical sandbox receipt field Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(server): harden sandbox mutation handling Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): update rebased sandbox references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(api)!: standardize canonical entity references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(api): codify protobuf API conventions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): preserve workspace selector semantics Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): restore workspace selector parity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): preserve descriptive name fields Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): update e2e request fixtures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(core): omit workspace selector during bootstrap Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(api): remove proto convention checker Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(api): refresh schema fingerprints after rebase Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(cli): use canonical provider receipt field Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent 903d9a0 commit d91b199

137 files changed

Lines changed: 6012 additions & 4635 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/tui-development/SKILL.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -479,12 +479,14 @@ use openshell_core::proto::{
479479

480480
### Proto field gotchas
481481

482-
- `DeleteSandboxRequest` uses the `name` field (not `id`):
482+
- `DeleteSandboxRequest` uses `name` for the primary sandbox and an explicit
483+
workspace selector:
483484
```rust
484485
let req = openshell_core::proto::DeleteSandboxRequest {
485486
name: sandbox_name,
486487
workspace_scope: Some(workspace_selector(workspace)),
487488
allow_missing: true,
489+
..Default::default()
488490
};
489491
```
490492
- Delete responses carry `DeletionOutcome`: distinguish `Accepted` (cleanup
@@ -493,20 +495,22 @@ use openshell_core::proto::{
493495
- `WatchSandboxRequest` has extra fields beyond what you might need — always use `..Default::default()`:
494496
```rust
495497
let req = openshell_core::proto::WatchSandboxRequest {
496-
id: sandbox_id,
498+
sandbox: sandbox_name,
497499
follow_status: false,
498500
follow_logs: true,
499501
follow_events: false,
500502
log_tail_lines: 0,
503+
workspace_scope: Some(workspace_selector(workspace)),
501504
..Default::default()
502505
};
503506
```
504507
- `SandboxLogLine` proto fields: `sandbox_id`, `event_time` (`Option<prost_types::Timestamp>`), `level`, `target`, `message`, `source`, `fields` (`HashMap<String, String>`).
505-
- Workspace-scoped request fields use `workspace_scope: Option<WorkspaceSelector>`.
506-
Select one workspace with `Some(workspace_selector(name))`. List requests that
507-
explicitly support cross-workspace access also accept
508+
- Workspace-scoped requests use
509+
`workspace_scope: Option<WorkspaceSelector>`. Select one workspace with
510+
`Some(workspace_selector(name))`. Collection list requests that explicitly
511+
support cross-workspace access also accept
508512
`Some(all_workspaces_selector())`; do not use that marker on other requests.
509-
- `GetSandboxLogsRequest` fields: `sandbox_id`, `lines` (u32), `since_time` (`Option<prost_types::Timestamp>`),
513+
- `GetSandboxLogsRequest` fields: `sandbox`, `lines` (u32), `since_time` (`Option<prost_types::Timestamp>`),
510514
`sources` (Vec<String>), `min_level` (String), `workspace_scope`.
511515
- `ListSandboxesRequest` fields: `page_size` (i32), `page_token` (String),
512516
`label_selector` (String), `workspace_scope`.
@@ -517,11 +521,12 @@ use openshell_core::proto::{
517521
- Paginated list responses return `next_page_token`. Continue with the same
518522
request parameters and that token until it is empty; changing filters or
519523
scope invalidates the token.
520-
- `UpdateConfigRequest` fields include `name` (String, sandbox name or empty for
521-
global), `setting_key`, `setting_value`, `delete_setting` (bool), `global`
522-
(bool), and `workspace_scope`. Sandbox-scoped updates require a named selector;
523-
gateway-global updates must leave `workspace_scope` as `None`.
524-
- Most resource requests require an explicit named `workspace_scope`, including
524+
- `UpdateConfigRequest` fields include `sandbox` (String, canonical sandbox name),
525+
`setting_key`, `setting_value`, `delete_setting` (bool), `global` (bool), and
526+
`workspace_scope`. Sandbox-scoped updates require canonical `sandbox` and a
527+
named selector; gateway-global updates leave `sandbox` empty and
528+
`workspace_scope` as `None`.
529+
- Most workspace-scoped requests require an explicit named selector, including
525530
the `default` workspace. An omitted selector is not an implicit default.
526531

527532
### gRPC timeouts

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ These pipelines connect skills into end-to-end workflows. Individual skill files
8383
| `.agents/agents/` | Agent personas | Sub-agent definitions (e.g., reviewer, doc writer) |
8484
| `architecture/` | Architecture docs | Design decisions and component documentation |
8585

86+
## Public API Conventions
87+
88+
Follow [proto/README.md](proto/README.md) for public protobuf API design. It is
89+
the canonical source for entity-reference naming, workspace selectors, field
90+
design, and schema evolution.
91+
8692
## Vouch System
8793

8894
- First-time external contributors must be vouched before their PRs are accepted. The `vouch-check` workflow auto-closes PRs from unvouched users.

CONTRIBUTING.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ These are the primary `mise` tasks for day-to-day development:
465465
| `python/` | Python SDK and bindings |
466466
| `sdk/go/` | Go SDK (types, gRPC clients, converters) |
467467
| `sdk/typescript/` | TypeScript SDK (Connect client and generated protobuf bindings) |
468-
| `proto/` | Protocol buffer definitions |
468+
| `proto/` | Protocol buffer definitions and [public API conventions](proto/README.md) |
469469
| `tasks/` | `mise` task definitions and build scripts |
470470
| `deploy/` | Dockerfiles, Helm chart, Kubernetes manifests |
471471
| `docs/` | Published Fern docs source, navigation, and content assets |
@@ -479,6 +479,12 @@ These are the primary `mise` tasks for day-to-day development:
479479

480480
New features always start as GitHub issues using the feature request template. For cross-cutting architectural decisions, API contract changes, or process proposals that need broad consensus, maintainers may ask for an RFC from the issue and assign an RFC number there. RFCs live in `rfc/`. See [rfc/README.md](rfc/README.md) for the full lifecycle and guidelines.
481481

482+
## Public API conventions
483+
484+
Follow [the protobuf API conventions](proto/README.md) when adding or changing
485+
gRPC contracts. The guide defines entity-reference naming, workspace selectors,
486+
field design, and schema-evolution rules.
487+
482488
## Documentation
483489

484490
If your change affects user-facing behavior (new flags, changed defaults, new features, bug fixes that contradict existing docs), update the relevant pages under `docs/` in the same PR and adjust `docs/index.yml` if navigation changes. For explicit navigation entries, keep `page:` aligned with `sidebar-title` when present and put relative `slug:` values in `docs/index.yml`. Reserve frontmatter `slug` for folder-discovered pages or absolute URL overrides.

architecture/gateway.md

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,34 @@ health, metrics, or tunnel routes. The plaintext service router also rejects
112112
browser requests whose Fetch Metadata, Origin, or Referer headers indicate a
113113
cross-origin or sibling-subdomain request.
114114

115-
Public workspace-scoped RPCs carry a typed `WorkspaceSelector`. A request must
116-
select one non-empty workspace explicitly; `default` is an ordinary explicit
117-
name, not an omitted-value fallback. Sandbox, sandbox template, provider, and
118-
service list RPCs also accept an all-workspaces marker after Platform Admin
119-
authorization. Single-workspace handlers reject that marker. Platform-global
120-
policy operations require the selector to be absent, while workspace policy
121-
operations require it. The gateway authorizes the selected scope before
122-
performing resource lookup so malformed, unsupported, and unauthorized scopes
123-
have consistent behavior across resource types.
115+
The normative public contract rules live in the
116+
[protobuf API conventions](../proto/README.md). Public API fields follow one
117+
entity-reference convention. `name` identifies the
118+
primary resource targeted by an RPC. A role field such as `sandbox`, `provider`,
119+
`service`, or `workload_template` identifies an entity referenced while
120+
operating on another resource or relationship. Entity references never append
121+
`_name`; their string value is already the canonical name.
122+
123+
Public workspace-scoped RPCs declare `workspace_scope` first and use the typed
124+
`WorkspaceSelector`. A request that targets one workspace selects a non-empty
125+
canonical workspace name; `default` is an ordinary explicit name, not an
126+
omitted-value fallback. Only sandbox, sandbox template, provider, and service
127+
collection list RPCs accept `all_workspaces`, after Platform Admin
128+
authorization. Provider-profile requests may omit the selector to address the
129+
platform profile scope. The authenticated sandbox bootstrap request may also
130+
omit it because the gateway resolves the immutable sandbox identity before the
131+
supervisor has learned its workspace. Canonical sandbox
132+
IDs remain internal metadata used at authentication, persistence, and
133+
compute-driver boundaries; public callers do not use them as sandbox
134+
references. The gateway resolves the name to the persisted sandbox record only
135+
after authorizing the selected workspace. A
136+
sandbox principal is instead resolved by the immutable ID in its authenticated
137+
identity, then checked against the requested name and workspace. Missing and
138+
unauthorized references use the same response within each principal class so
139+
the resolver does not expose an object-existence oracle. Sandbox, sandbox
140+
template, provider, and service collection list RPCs use the same field with an
141+
all-workspaces marker. Platform-global policy operations omit both `sandbox`
142+
and `workspace_scope`, while sandbox policy operations require both.
124143

125144
Docker and Podman report the local address through which their sandboxes can
126145
reach the gateway. When the primary listener covers that address, the gateway
@@ -852,8 +871,8 @@ channel and returns the exec with an error. Once a command reports its exit
852871
status, the gateway also bounds how long it waits for the trailing channel close.
853872

854873
`ForwardTcp` is the client-facing byte stream for SSH and service forwarding.
855-
The first frame is a `TcpForwardInit` that carries the sandbox ID, an
856-
authorization token from `CreateSshSession`, and an explicit target:
874+
The first frame is a `TcpForwardInit` that carries the workspace-scoped sandbox
875+
name, an authorization token from `CreateSshSession`, and an explicit target:
857876
`target.ssh` for the sandbox SSH socket or `target.tcp` for a loopback service
858877
inside the sandbox. The gateway validates the token and sandbox readiness,
859878
sends a targeted `RelayOpen` to the supervisor, then bridges

0 commit comments

Comments
 (0)