Skip to content

docs(rfc): propose SDK conformance testing - #3238

Draft
jiripetrlik wants to merge 1 commit into
NVIDIA:mainfrom
jiripetrlik:e2e-go-rfc
Draft

jiripetrlik wants to merge 1 commit into
NVIDIA:mainfrom
jiripetrlik:e2e-go-rfc

Conversation

@jiripetrlik

Copy link
Copy Markdown

Summary

This PR adds an RFC for SDK Conformance Testing. It describes how to verify the supported OpenShell SDKs against a real gateway. It defines a testing strategy and compatibility contract across SDKs, gateway behavior, E2E infrastructure, and CI.

Related Issue

Changes

  • Adds an RFC for SDK Conformance Testing

Testing

  • [*] mise run pre-commit passes
  • Unit tests added/updated - not needed
  • E2E tests added/updated (if applicable) - not needed

Checklist

  • [*] Follows Conventional Commits
  • [*] Commits are signed off (DCO)
  • [*] Architecture docs updated (if applicable)

@copy-pr-bot

copy-pr-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

All contributors have signed the DCO ✍️ ✅
Posted by the DCO Assistant Lite bot.

@jiripetrlik

Copy link
Copy Markdown
Author

I have read the DCO document and I hereby sign the DCO.

@purp

purp commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Noting that there was a related issue/PR pair a few weeks ago (#2292 and #2293) that suggested RFC-0014 for similar reasons; those were closed after some discussion as premature.

@purp purp added the rfc label Sep 9, 2026

@rhuss rhuss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc-review Summary

What Went Well

  • The three-tier fixture taxonomy (lines 99 to 124) with the rule that live resources are never shared between parallel tests is exactly the constraint that makes parallel execution reliable.
  • The scope broadening from the Go-only request in #3028 to a multi-SDK suite is declared, justified (lines 33 to 37), and phased so the Go adapter lands first, with the Go-only approach kept as a real alternative rather than a straw man (lines 236 to 240).
  • Keeping a shared contract with language-native execution (lines 65 to 97) avoids a generic cross-language runner while preserving each SDK's idioms.
  • The Docker-default harness decision comes with an operational rationale and explicitly scoped runtime-specific lanes (lines 166 to 176).
  • The risk mitigations (lines 217 to 233) are concrete design choices, not deferred intentions.

Findings

Severity File Description Source
Important README.md:14 The shared scenario set is referred to by nine different names across the document: "shared SDK c... architecture
Important README.md:28 This says the Go SDK client tests "do not exercise a built openshell-gateway" correctness
Important README.md:66 "Versioned" appears here and at lines 87 and 199, but the RFC never defines what is versioned (th... architecture
Important README.md:67 "Expected classified failures" are part of the scenario definition, but the RFC does not say how ... coderabbit
Important README.md:74 The table describes workflows ("Create, get, list, wait for ready, delete") but not what an adapt... test-quality
Important README.md:79 Negative behavior is specified only for workspaces ("classify missing resources as not found") an... test-quality
Important README.md:78 "Verify a sandbox receives placeholders rather than raw credential values" does not say what a pl... test-quality
Important README.md:81 This paragraph requires adapters to report omissions, but it does not define the conformance outc... coderabbit
Important README.md:87 The scenario document format is described only in prose (identifier, preconditions, ordered opera... architecture
Important README.md:93 Markdown is the normative contract, but with the machine-readable companion deferred there is no ... test-quality
Minor README.md:88 "Stable scenario identifier" is required but its format is not defined, nor how an adapter's test... test-quality
Minor README.md:128 "Each supported SDK owns a native adapter and test entry point under e2e/", but e2e/python/ a... architecture
Important README.md:137 "Register cleanup before making later calls" and bounded deadlines are necessary but not sufficient production
Minor README.md:178 "Every CI lane must capture gateway logs on failure" and adapters must emit gateway diagnostics, ... security
Minor README.md:188 This section distinguishes conformance tests from unit tests and CLI conformance, but gives no ru... test-quality
Minor README.md:206 Step 3 introduces mise run e2e:go without mentioning the existing go:test:integration task in... correctness
Important README.md:207 Adding e2e:go to the aggregate e2e task, and later the Rust, Python, and TypeScript adapters ... production

All findings are posted inline. Several overlap and can be addressed together: the existing Go integration tests (Motivation, Relationship to existing tests, step 3), assertion precision and error classification (scenario table), and scenario identifiers with coverage reporting (scenario documents).

Review Details

  • Findings posted: 17
  • Findings reviewed and not posted: 8
  • Gate outcome: FAIL (12 Important, 0 Critical)
  • Participating agents: correctness, architecture, security, production, test-quality, goal-alignment, coderabbit

Comment on lines +14 to +17
This RFC introduces a shared SDK conformance suite that verifies the supported
OpenShell SDKs against a real gateway. The suite defines the observable
behavior that an SDK must provide for selected gateway workflows, while each
language keeps native test code and idioms.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: The shared scenario set is referred to by nine different names across the document: "shared SDK conformance suite" (line 14), "shared behavioral specification" (64), "versioned SDK conformance specification" (66), "shared contract" (91, 133), "SDK contract" (43), "compatibility contract" (239), "portable contract" (174), "Markdown contract" (95), and "conformance specification" (193).

Why this matters: Readers and adapter authors cannot tell whether these are one deliverable or several, and the ambiguity will carry over into directory names, task names, and code comments.

Suggested fix: Pick one primary term (for example "SDK conformance specification"), define it here in the Summary, and use it throughout. Natural variation is fine once the primary term is established, but keep it to one or two alternatives.

Source: architecture agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RFC now:

  • Defines SDK conformance specification as the normative versioned Markdown scenarios.
  • Defines SDK conformance suite as the specification plus native adapters.
  • Consistently uses the specification term for scenarios/contracts and adapter/lane terms for execution.
  • Updates the diagram, fixture ownership table, risks, alternatives, and prior-art references.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. The Summary now defines "SDK conformance specification" and "SDK conformance suite" as distinct artifacts, and the rest of the document holds the distinction. Swept the file for the old variants: no stray "shared contract", "SDK contract", "compatibility contract", or "portable contract" remain.

Comment on lines +28 to +30
The Go SDK has extensive unit coverage, but its client tests use an in-process
transport and do not exercise a built `openshell-gateway`. That leaves a gap:
a protocol, conversion, authentication, or asynchronous-lifecycle regression

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: This says the Go SDK client tests "do not exercise a built openshell-gateway". sdk/go/openshell/v1/integration_test.go (build tag integration) already runs TestIntegration_HealthCheck, TestIntegration_SandboxExecSmoke, and TestIntegration_FileTransfer against a real gateway, and tasks/go.toml exposes them as go:test:integration.

Why this matters: The gap is real but narrower than stated. Reviewers who take this sentence at face value will not know there is existing gateway-backed coverage to build on or retire, and the implementation plan (step 3) currently ignores it.

Suggested fix: Rephrase to acknowledge the tagged integration tests and name the actual gap: they are not wired into go:ci or the e2e aggregate, cover only a smoke path, and share no scenarios with other SDKs. Then say in step 3 whether e2e:go supersedes go:test:integration.

Source: correctness agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to:

  • Acknowledge go:test:integration and its gateway-backed health and sandbox-exec smoke tests.
  • State that it requires a supplied gateway and is excluded from go:ci and aggregate e2e.
  • Identify the actual gap: narrow smoke coverage with no specification-backed shared scenarios.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and the revised text is accurate against the repo. go:test:integration exists at tasks/go.toml:12, the tests read OPENSHELL_GATEWAY_ADDRESS, and go:ci (line 53) does not depend on it. The gap is now stated correctly as narrow smoke coverage with no specification-backed scenarios.


### A shared behavioral specification

Add a versioned SDK conformance specification under `e2e/conformance/sdk/`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: "Versioned" appears here and at lines 87 and 199, but the RFC never defines what is versioned (the whole specification or individual scenarios), what the version looks like, or what triggers a bump. The CLI conformance crate has a precedent in PLAN_VERSION in crates/openshell-conformance/src/plan.rs.

Why this matters: Line 195 says specification changes are "reviewed as compatibility changes", but without a versioning rule nobody can tell whether a change is breaking, whether an adapter is current, or how four adapters coordinate when a required observation is added.

Suggested fix: Define the versioning unit, the scheme (a monotonic integer per specification version would match the CLI crate), and the policy: what kind of change bumps the version, and what happens to an adapter that has not implemented the new version (fails a completeness check, or is documented as behind).

Source: architecture agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented the versioning policy in RFC.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. "Specification versioning" defines the unit (the whole normative scenario set), the scheme (monotonic integer), what triggers a bump, and what happens to an adapter behind the current version. That was everything the comment asked for.

Comment on lines +67 to +70
The specification defines scenarios in terms of externally observable gateway
behavior: setup inputs, ordered operations, expected successful results,
expected classified failures, and cleanup requirements. It does not encode
language syntax or require one generic test runner to call every SDK.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: "Expected classified failures" are part of the scenario definition, but the RFC does not say how each adapter maps its SDK's native errors onto the shared classifications (for example NotFound), nor which transport-level errors must be treated as failures rather than retried.

Why this matters: Without a shared mapping, one adapter can classify a gRPC NOT_FOUND as NotFound while another surfaces a generic error, and both report conformance for behavior that differs.

Suggested fix: Add a short error-classification section to the specification: the set of shared failure classes, the gRPC status (or transport condition) each maps to, and explicit negative cases in the scenarios that exercise them.

Source: coderabbit (also flagged by: test-quality agent)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RFC now:

  • Defines adapter-level failure classifications without standardizing public SDK error APIs.
  • Requires v1 NotFound (NOT_FOUND) and AlreadyExists (ALREADY_EXISTS) scenarios for sandboxes, providers, and workspaces.
  • Requires generic/unclassified errors to fail those scenarios.
  • Treats connection, DNS, TLS, and unexpected gRPC failures—including UNAVAILABLE, DEADLINE_EXCEEDED, and CANCELLED—as scenario failures with no adapter-level retry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and I verified the four mappings against the gateway rather than just the text. ALREADY_EXISTS for sandbox, provider, and workspace (compute/mod.rs:1034, grpc/provider.rs:249, grpc/workspace.rs:198), INVALID_ARGUMENT for a provider type change (grpc/provider.rs:386), and FAILED_PRECONDITION for a missing provider on both create and attach (grpc/sandbox.rs:405 and :1096). The no-retry rule for transport failures is the right call.

Comment on lines +74 to +79
| Area | Required behavior |
| --- | --- |
| Sandbox lifecycle | Create, get, list, wait for ready, delete, and observe eventual removal. |
| Sandbox execution | Run a successful command, preserve sandbox filesystem state across executions, and surface a failed command's exit status and stderr. |
| Providers | Create, get, list, update, attach, detach, and delete a provider; verify a sandbox receives placeholders rather than raw credential values. |
| Workspaces | Create, get, list, delete, apply labels, scope sandbox visibility, and classify missing resources as not found. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: The table describes workflows ("Create, get, list, wait for ready, delete") but not what an adapter must observe: expected field values, status codes, or state transitions. Compare crates/openshell-conformance/src/scenarios/smoke.rs, which asserts exact phase strings, and the Python e2e tests, which assert specific gRPC codes.

Why this matters: Adapters will pick different assertion strengths. A Go adapter asserting phase == Ready and a TypeScript adapter asserting only that status is present both "implement" the wait-for-ready scenario, so a gateway regression would be caught in one language and missed in another.

Suggested fix: State in the specification (or in the example scenario document) the observation precision, for example: after wait-for-ready returns, get MUST report phase Ready; delete of an unknown name MUST return NotFound; a failing exec MUST return a non-zero exit code and non-empty stderr.

Source: test-quality agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new section requires adapters to verify exact observable behavior, including:

  • Requested names, stable returned identifiers, and list membership.
  • READY after readiness waiting.
  • NotFound plus list absence after deletion.
  • Exact success output, exit code, and empty stderr.
  • Persistent filesystem markers across executions.
  • Nonzero exit plus a failure sentinel in stderr.
  • Credential placeholders that never equal the raw secret.
  • Exact workspace labels and visibility scope.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new "Observation precision" section is a real improvement and most of it is exactly what was missing. Two of the assertions are still not precise enough to implement identically across four SDKs, so I would like one more pass here.

First, "phase READY" is not a token that appears anywhere. The wire enum value is SANDBOX_PHASE_READY (proto/openshell.proto:1091), while the CLI conformance scenario asserts the rendered string "Ready" (crates/openshell-conformance/src/scenarios/smoke.rs:85). In the one section whose purpose is exactness, name the enum value and say how language-specific renderings map onto it.

Second, "stdout equals the scenario's success sentinel, and stderr is empty" is only well defined when the exec runs without a PTY. ExecSandboxRequest has a tty field (proto/openshell.proto:1475), and a PTY merges the two streams, so the assertion changes meaning depending on a flag the scenario never sets. No suite in the repo currently asserts an empty stderr; the Python and Rust tests only capture it for diagnostics. Please pin tty=false in the scenario and consider whether empty stderr is worth the flake risk compared to asserting exit code and stdout.

Third, a related race: SANDBOX_PHASE_COMPLETED and SANDBOX_PHASE_STOPPED exist, so a short-lived entrypoint can leave READY between the readiness wait and the follow-up get. The conformance fixture should pin a long-running entrypoint, or the observation should accept READY or a later terminal phase.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Replaced the ambiguous READY token with protobuf enum value SANDBOX_PHASE_READY. Adapters may assert their SDK-native representation, provided it maps to that wire value.
  • Required the lifecycle fixture to remain running until cleanup. Reaching COMPLETED, STOPPED, or another terminal phase before the follow-up get now fails the scenario.
  • Defined exec scenarios as non-interactive and non-PTY, with tty=false and no_login_shell=true, while allowing SDKs to express those semantics through their native APIs.
  • Made output assertions deterministic: the success command emits only the exact stdout sentinel and exits 0; the failure command emits only the exact stderr sentinel and exits with the scenario’s specified nonzero code.
  • Required adapters to compare collected output without trimming or normalization.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, all four points. The phase observation now names SANDBOX_PHASE_READY with an explicit rule that an SDK-native representation must map to the named wire value, which is the right shape. Exec scenarios are pinned to tty = false and no_login_shell = true, and I verified both are real fields (proto/openshell.proto:1586 defines bool no_login_shell = 10). Keeping the empty-stderr assertion is defensible now that the sentinels are fixed ASCII with no trailing newline and adapters must compare without trimming, and requiring empty stdout plus an exact exit code on the failure path is stronger than what I asked for. The terminal-phase rule closes the readiness race.

Comment on lines +137 to +140
- create unique test resources and register cleanup before making later calls;
- use bounded operation and cleanup deadlines;
- tolerate documented asynchronous deletion by polling only the specified
observable state;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: "Register cleanup before making later calls" and bounded deadlines are necessary but not sufficient. Go's -timeout kills the test binary without running t.Cleanup. In the external-gateway mode this RFC supports (line 143), the gateway survives the run, so leaked sandboxes, providers, and workspaces accumulate. The Risks section proposes only manual recovery.

Why this matters: On a shared or long-lived development gateway, stale resources from interrupted runs confuse later list and get scenarios and can exhaust quotas.

Suggested fix: Require adapters to either run a pre-test scavenger that removes stale resources by name prefix, or scope every resource of a run into a uniquely named workspace that can be deleted in one call if cleanup is interrupted.

Source: production agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented the interrupted-run recovery requirement. Adapters must now use run-prefixed resource names, scavenge stale adapter-owned resources before using a persistent external gateway, delete dependencies in safe order, and fail setup if eligible stale resources cannot be removed. The TTL must exceed the maximum lane duration to protect active concurrent runs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Run-prefixed names, a bounded pre-run scavenger, a TTL that must exceed the maximum lane duration, and a defined deletion order address the leak. One data point in favor: MAX_NAME_LEN is 253 (crates/openshell-server/src/grpc/mod.rs:132), so the prefix leaves plenty of room for scenario suffixes.

may be added later to validate its harness and driver behavior, but is not a
prerequisite for the shared SDK contract.

Every CI lane must capture gateway logs on failure. Per-language test timeouts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: "Every CI lane must capture gateway logs on failure" and adapters must emit gateway diagnostics, but there is no guidance on redacting JWTs, mTLS material, or credential values from what gets uploaded.

Why this matters: CI artifacts are visible to all collaborators for the retention period. The credential values in provider scenarios are synthetic, so the practical exposure is auth material in gateway logs, but the RFC should say so explicitly.

Suggested fix: Add one sentence: captured logs and adapter diagnostics must not contain tokens, key material, or credential values, and adapters must never print raw credential values even on failure.

Source: security agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the CI artifact redaction requirement: gateway logs and adapter diagnostics must exclude tokens, TLS key material, and credential values, including synthetic fixture secrets.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Naming tokens, TLS key material, and credential values, and extending it to synthetic fixture secrets, covers what the comment asked for.

Comment on lines +188 to +191
SDK unit tests continue to validate conversion details, retry behavior, and
language-specific ergonomics cheaply. Existing Rust and Python E2E tests remain
valid. Their scenarios should be mapped to the shared specification over time;
the project should not rewrite stable coverage merely to satisfy a new layout.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: This section distinguishes conformance tests from unit tests and CLI conformance, but gives no rule for what belongs where. sdk/go/openshell/v1/integration_test.go already covers sandbox create, exec, and delete against a real gateway, and the RFC does not say whether it is removed, subsumed, or kept.

Why this matters: Without a boundary rule, the same behavior accumulates at several levels with different assertion strength, growing CI time and letting a weaker test mask a regression the conformance suite would catch.

Suggested fix: Add a short decision rule: gateway-dependent portable behavior listed in the scenario table belongs in the conformance adapter; SDK integration tests cover only SDK-specific behavior (context cancellation, streaming, transport negotiation).

Source: test-quality agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the test-ownership rule.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. The rule now says portable gateway-dependent behavior belongs in the adapter and SDK-local tests keep context cancellation, streaming, authentication, and transport negotiation. That is the boundary that was missing.

harness, and implement it as the first adapter. Strengthen its assertions to
check returned resource state, readiness state, provider read/list/update,
failed exec behavior, and missing-sandbox errors.
3. Add `mise run e2e:go` and a focused Go CI lane. Keep it out of the aggregate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Step 3 introduces mise run e2e:go without mentioning the existing go:test:integration task in tasks/go.toml or the tests it runs.

Why this matters: Implementers may end up with two overlapping gateway-backed Go lanes, or drop the existing coverage by accident.

Suggested fix: State whether e2e:go supersedes go:test:integration and how the tests in integration_test.go migrate into the adapter.

Source: correctness agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made step 3 explicit: e2e:go initially complements go:test:integration, maps all three existing Go integration tests into corresponding conformance scenarios, and becomes the authoritative portable lane only after equivalent coverage exists.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Step 3 maps all three existing Go integration tests onto scenarios and states that the adapter lane becomes authoritative only once equivalent coverage exists, so nothing is dropped by accident.

Comment on lines +207 to +208
`e2e` task until the Docker default path is in place; then add it to the
aggregate task.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: Adding e2e:go to the aggregate e2e task, and later the Rust, Python, and TypeScript adapters (step 4), means each adapter runs its own with-docker-gateway.sh lifecycle. The aggregate in tasks/test.toml runs its dependencies sequentially, so it grows from three gateway cycles to seven.

Why this matters: Local mise run e2e, the documented pre-PR command, roughly doubles in wall-clock time. Contributors then skip it and discover failures only in CI.

Suggested fix: Specify a shared-gateway pattern for the aggregate: an umbrella task that starts one gateway and runs all SDK adapters against it, or adapters that honor OPENSHELL_GATEWAY_ENDPOINT (which the harness already supports) so the aggregate can reuse one instance.

Source: production agent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the shared-gateway task pattern.

The RFC now requires:

  • e2e::adapter tasks that never manage gateway lifecycle
  • standalone e2e: wrappers for focused runs
  • one e2e:sdk umbrella task that starts a Docker gateway once and runs all adapters sequentially
  • aggregate e2e to depend on e2e:sdk, not each language task

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The umbrella task is the right shape, but my original comment gave you a wrong premise and I want to correct it before this is locked in.

I claimed the aggregate runs its dependencies sequentially. It does not. This repo sets jobs = 8, and mise run --dry-run e2e shows e2e:rust, e2e:python, and e2e:mcp starting concurrently, each against its own gateway on a dynamically chosen port (e2e_pick_port in e2e/with-docker-gateway.sh:463). So local mise run e2e does not double in wall-clock time today, and the rationale at the end of "Gateway harness and runtime coverage" should be rewritten.

The design still stands on its own merits: one gateway instead of four reduces CPU, memory, and port pressure, which matters more on CI runners than raw fan-out does. But as written, e2e:sdk runs four adapters strictly sequentially where mise would have parallelized them, which could make the aggregate slower rather than faster. Since the fixture rules already require uniquely named per-test resources and permit sharing a connection within a process, consider allowing adapters to run in parallel against the single shared gateway, and state the actual reason for the umbrella (bounded concurrent gateways) rather than wall-clock time.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected the shared-gateway execution model and rationale. e2e:sdk now runs adapter-only tasks concurrently against one shared gateway, waits for all adapters, and tears down the gateway afterward. Sharing limits concurrent gateway containers and reduces CPU, memory, and dynamic-port pressure while preserving adapter parallelism; it no longer assumes mise dependencies execute sequentially.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and thank you for taking the correction cleanly. The rationale now rests on bounded concurrent gateway containers and port pressure rather than wall-clock time, and says explicitly that it does not depend on mise dependencies running sequentially. Running adapters concurrently against the one shared gateway, waiting for all of them, and tearing down only after they complete is exactly the shape I was hoping for.

@jiripetrlik

Copy link
Copy Markdown
Author

@rhuss Thank you for your review. I've tried to address your comments.

@rhuss

rhuss commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Second review pass on 6950fc2f. I checked each of the 17 threads against the updated document and, where the RFC now makes a factual claim about gateway behavior, against the code itself.

Fourteen are properly addressed and I have resolved those threads. The error classification table and the credential placeholder matcher deserve specific credit: both match the implementation exactly, including FAILED_PRECONDITION for a missing provider on attach and the 64 character lowercase hex stable handle. Those were the two places I expected to find a plausible-sounding but wrong requirement.

Three threads remain open with follow-ups: observation precision (the phase token and the stdout/stderr assertions), the two open questions that now contradict the manifest and coverage-report requirement, and the shared-gateway rationale, where my original comment gave you an incorrect premise about how mise runs task dependencies.

Three further points that do not map onto an existing thread:

  1. The adapter tasks never say how the gateway address reaches the adapter. The repo has two conventions and the RFC cites both: the Go SDK integration tests read OPENSHELL_GATEWAY_ADDRESS, while e2e/run.sh:391 exports OPENSHELL_GATEWAY_ENDPOINT. Since e2e:sdk:<language>:adapter is defined as running against "the gateway configuration supplied by its caller", please name the variable and say whether adapters are expected to accept both.

  2. The harness endpoint mode is HTTP-only (e2e/with-docker-gateway.sh:269 rejects anything that is not http://). That is worth one sentence, because the Motivation lists authentication regressions among the things this suite should catch, and the shared or external gateway path cannot exercise TLS or mTLS as it stands.

  3. Minor: the RFC introduces a third directory convention. The specification lives at e2e/conformance/sdk/, adapters at e2e/go/ and e2e/rust-sdk/, alongside the existing e2e/mcp-conformance/. Worth a sentence on why the specification is nested under conformance/ while adapters are not.

Two process notes: the frontmatter still says state: draft, and rfc/README.md puts an RFC under active pull request discussion at state: review. Separately, @purp pointed at #2292 and #2293, closed in July as premature. The Prior art section does not mention that attempt. A short paragraph on what has changed since would help reviewers who remember it.

markdownlint passes on the file.

Signed-off-by: Jiri Petrlik <jpetrlik@redhat.com>
@jiripetrlik

Copy link
Copy Markdown
Author

Hello @rhuss ,
thank you for your second review. I've tried to fix all three remaining comments. I've also tried to address new additional comments:

  1. Defined OPENSHELL_GATEWAY as the caller-to-adapter contract. Adapters do not consume OPENSHELL_GATEWAY_ADDRESS or OPENSHELL_GATEWAY_ENDPOINT directly. The harness accepts endpoint mode, registers a named gateway configuration, and exports its name through OPENSHELL_GATEWAY. The older Go integration tests retain their separate address convention.

  2. Documented that raw endpoint mode is HTTP-only and provides functional conformance coverage but cannot qualify TLS, mTLS, OIDC, or other authentication behavior. The required default CI lane uses the harness-managed HTTPS gateway with mTLS.

  3. Explained the directory split: e2e/conformance/sdk/ owns the language-neutral, versioned specification, while adapters stay in language-owned E2E roots to use their native modules, test frameworks, and fixtures. e2e/mcp-conformance/ is an older protocol-specific executable suite rather than the adapter-layout model.

Can you please take a look again?

@rhuss

rhuss commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Third pass on 38c6e2da. All three remaining threads are resolved from my side, and the three additional points are addressed.

Two of the answers are better than what I asked for. On the gateway variable, rather than picking between OPENSHELL_GATEWAY_ADDRESS and OPENSHELL_GATEWAY_ENDPOINT, you adopted OPENSHELL_GATEWAY, which the harness already exports (e2e/with-docker-gateway.sh:279, :285, :681) and the CLI already reads (crates/openshell-cli/src/tls.rs:125). That makes the adapter contract an existing convention rather than a new one. On the HTTP-only caveat, the harness itself makes the same argument at with-docker-gateway.sh:14-15, and committing the required default lane to the harness-managed mTLS configuration gives TLS coverage a defined home.

Two small process items remain, both from the end of my previous comment:

  1. The frontmatter still reads state: draft. rfc/README.md puts an RFC under active pull request discussion at state: review, and the pull request itself is still a draft.
  2. Prior art still does not mention feat(sdk): SDK acceptance scorecard and conformance tiers #2292 and docs(rfc): add RFC 0014 SDK acceptance scorecard #2293, closed in July as premature. A short paragraph on what has changed since would help reviewers who remember that round.

Neither blocks the design. Once the state field is updated and the pull request is marked ready, this is in good shape from my side.

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

This pull request has had no activity for 14 days and is now marked stale. It may be closed in 7 days if there is no further activity.

@github-actions github-actions Bot added the state:stale Inactive item at risk of automatic closure. label Oct 3, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rfc state:stale Inactive item at risk of automatic closure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants