Skip to content

rfc: let Studio reach managed clusters on the CLI's login until its OAuth client lands - #1152

Merged
ragnorc merged 2 commits into
ModernRelay:mainfrom
ragnorc:claude/rfc-1017-a2-cli-login-interim
Oct 25, 2026
Merged

ragnorc merged 2 commits into
ModernRelay:mainfrom
ragnorc:claude/rfc-1017-a2-cli-login-interim

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Amends RFC 1017's appendix A2 ("OAuth for standard clients and managed access") with one stated exception for Studio.

A2's rule stays as it was: a client other than the CLI reaches an OIDC-enabled server, self-hosted or managed, as a standard OAuth public client. For a managed cluster, that client waits on the managed control plane enabling the resource profile and registering Studio. Until that holds and Studio implements the client, Studio may reach a managed cluster with the CLI's data token for it.

  • Scope: only after a person selects a managed target inside Studio. The launcher stays refused (the target table and Opening Studio are unchanged).

  • Source: the token comes from omnigraph credentials export --target NAME, the one credential command that accepts a managed target. It writes only to a Unix FIFO and never starts an interactive login.

  • What export returns:

    • the CLI's cached data token, while it validates;
    • otherwise a new one: the CLI asks for 3600 seconds, which the issuer may shorten;
    • a token stored by cluster token --ttl can last up to 86400 seconds.

    After a 401, export returns the same token while the cache validates. A target whose endpoint pin no longer matches fails the export.

  • Studio's obligations: keep the token in memory, and only while target inspect still names that managed target at that endpoint.

What the exception accepts for now, all of which the OAuth client removes:

  • Studio and the CLI hold the same bearer as one Cedar principal, so the server cannot give Studio less authority.
  • The token stays valid until it expires or its signing key retires. Removing the person's issuer membership does not cut that short, though a policy change denies both on their next request.

Following the RFC process, the decision log names the qualified sentence and the unchanged launcher refusal.

Checks

  • python3 scripts/check-docs.py: Documentation OK (400 Markdown files checked)

  • typos on the changed file: clean

  • The export behavior was checked against crates/omnigraph-cli/src/managed/data.rs:

    • 3600-second mint (issue_for_principal);
    • expiry refused past the TTL plus 30 seconds;
    • TTL window 60–86400;
    • data_endpoint_mismatch.

    The server's 60–86400-second window is in crates/omnigraph-server/src/data_tokens.rs.

CI Gate fails on cargo deny (RUSTSEC-2026-0387 on crypto-bigint 0.5.5), which is on the parent and addressed by #1151.

Tracking: #1025.

… its OAuth client lands

RFC 1017's A2 has Studio reach managed clusters only as a standard OAuth public client, which waits on the managed control plane. Until then, Studio takes the data token that `omnigraph credentials export` exchanges from the person's login: by export alone, in memory, asked for again after a 401, and kept only while the target still names that managed endpoint. The amendment records that interim and what it accepts for now (the CLI's token, which removing admission does not retract, and no separate client identity), and names the superseded sentence in the decision log.

ragnorc commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Review

This amendment lets Studio use the managed data token from the person’s CLI login until Studio can be a standard OAuth public client. The direction is sound, and the three security costs are real: the same reusable bearer, no online retraction, and no separate client identity. The text needs to match the CLI that issues that token, and the exception needs one end condition. I recommend changes before merge.

The OAuth client flow in A2 can stay. The interim can stay once it describes credentials export as the code behaves.

Blocking

  1. The export contract in the interim does not match the CLI.

omnigraph credentials export --target NAME on a managed cluster calls export_credential, which calls data::resource. A cache entry that still validates is returned as-is. A new token is requested only when the cache is missing, expired, or belongs to another principal. That request sends ttl_seconds: 3600. The issuer may shorten it. The CLI rejects an expiry later than the requested TTL plus 30 seconds.

match resolved {
    Ok(credential) => resolve_loaded(graph, context, credential),
    Err(failure)
        if matches!(
            failure.body["type"].as_str(),
            Some(
                "data_credential_required"
                    | "data_credential_expired"
                    | "data_credential_identity_mismatch"
            )
        ) => {
        // ...
        let credential =
            issue_for_principal(store, context, &api, 3600, expected.as_deref()).await?;

A 401 from the data server is outside that set. Asking export again returns the same cached bearer while Credential::validate succeeds. Key retirement, trust mismatch, and clock skew are in that case. data_endpoint_mismatch also returns an error and does not mint a replacement. cluster token --ttl can store a credential of up to 86400 seconds, and a later export prints that cached credential.

The server accepts a lifetime of 60 to 86400 seconds (valid_identity_claims in crates/omnigraph-server/src/data_tokens.rs). RFC 0053 states the same window. “One hour at the exchange observed on 2026-10-25” is not that contract. This repository has no record of that exchange.

Replace that sentence with the cache rule, the 3600-second mint, the issuer’s right to shorten, and the 86400-second ceiling for a cached cluster token. State that a 401 causes a new export to return the same token while the cache validates. State that an endpoint pin mismatch fails the next export.

  1. The rewritten rule moves the self-hosted client, and the interim has two end gates.

The old sentence required a standard OAuth public client for an OIDC server, self-hosted or managed. The new sentence says that rule holds “once the managed control plane supports one”. A self-hosted OIDC server does not wait on that control plane. The interim then allows borrowing for managed clusters only. Self-hosted OAuth is left between those two clauses.

The interim ends with “Until the client lands” and “The client replaces it”. The opening sentence ends when the control plane supports a client. A2 already requires both: the control plane must enable the resource profile and register Studio, and Studio must implement the client. Use that conjunction as the only end. Keep the self-hosted OAuth rule in force now. Put the same end in the 2026-10-25 decision-log entry. That entry currently repeats the widened reading.

  1. The rest of this RFC still refuses the path the interim opens.

The target table says a managed cluster is “Deferred to A2” for studio, and “Refused: managed credentials stay the CLI’s” for credential get. Opening Studio says Studio accepts an existing server target and never opens a destination the launcher did not validate. PR #1137 implements that refusal: a managed target is target_kind_unsupported before any link is minted.

RFC process step 4 says a post-merge amendment rewrites each body sentence it changes, and the decision log names each superseded sentence. This PR rewrites the A2 opening sentence and quotes it. It leaves the table cell and the Opening Studio sentences in place. Add one explicit entry. Either the launcher stays refused and Studio uses this token only after a person selects a managed target inside Studio, or the table and Opening Studio change and the log names those sentences. One of those is the current rule.

  1. The PR title fails the title check.

CI run on this head reports [rfc-type] docs(rfc) is not the RFC type. CONTRIBUTING.md requires rfc: <subject> for an RFC PR. Use:

rfc: let Studio reach managed clusters on the CLI's login until its OAuth client lands

CI Gate also fails because cargo deny reports RUSTSEC-2026-0387 on crypto-bigint 0.5.5. That advisory is on the parent 7711e4a3. Open PR #1151 addresses it. This docs diff does not.

Optional

  1. Limit the login label to an omnigraph profile. login and logout require provider: omnigraph. credentials export --target also returns a data token for a token or exec profile (ResourceStore::for_profile). Those tokens are not the CLI login. Show “CLI’s login” only for an omnigraph profile, and show the profile name in every case. target inspect is offline and already includes that profile field.

  2. Record the export limits the user guide already states. Export requires a Unix FIFO and refuses other platforms, terminals, and regular files before acquisition (stdout_is_pipe in crates/omnigraph-cli/src/commands/targets/mod.rs). It never starts an interactive login. For an omnigraph profile the CLI copy stays in the OS keychain. The pipe consumer can store the token. Studio’s in-memory rule is a Studio obligation. The server still applies Cedar on the next request. A policy change denies Studio and the CLI together. The bearer stays valid until expiry or trust retirement. “Removing admission” here matches that offline check and the offboarding receipt (CREDENTIAL_LIFETIME is 86400). It is issuer membership, not the cluster writer lock this RFC calls admission elsewhere.

  3. Keep the tracker and the neighboring PRs on the same rule. Issue track: OAuth for standard clients and managed access from Studio #1025 still forbids borrowing these tokens and has no interim. Appendix A3 still says an MCP relay would keep the token inside the CLI. PR rfc: narrow graph access to client contracts #1149 edits this same RFC and does not contain this interim. PR fix(cli): support managed introspection and mcp credential renewal #1146 renews a managed credential when the local cache is expired, which is the same cache rule as finding 1, and it does not open Studio on a managed target.

Tradeoffs and liability

The accepted costs match the token. IdentityTokenClaims has no client id. The server derives principal:<sub>. The same Cedar principal applies to every holder. jti is not a revocation list. RFC 0053 states that an issued identity stays authenticated until expiry or signing-key retirement. Login-session revocation stops a later mint. It does not retract a token already in Studio’s memory or in the keychain.

Invariant 10 stays intact at the server. The client does not supply an actor. The loss is client separation: Studio and the CLI are one principal for up to 24 hours, and removing issuer membership does not cut that window short.

This adds no code and no second authority. It adds a prose exception that issue #1025 was opened to avoid. The exception is removable only by a later edit. Five similar interims would leave five borrowed-token paths in the appendix while the target table still says “Deferred”. Put the one-line exception in the target table, with the two-part end condition, so removal deletes one sentence.

Robustness of client separation is the quality this interim gives up. That is a fair short-term choice for Studio on a managed cluster before the control plane registers it. Clarity has to carry the bound, because nothing in code expires the exception.

What matches

  • credentials status rejects a managed cluster target. Set, status, and remove require --profile. Export is the credential command that accepts that target (planes.rs).
  • Export with --target returns the cluster data bearer, not the control-plane login token.
  • target inspect is offline and prints the configured target, including endpoint and profile. A local “still names this target at this endpoint” check can use that output.
  • The server cannot give this bearer a smaller grant than the CLI principal.
  • The decision log quotes the old A2 sentence. updated is 2026-10-25. Status stays accepted.

Validation

  • Reviewed head: 1a567f710347ac258783b7d6b9cf819496ffe133. Parent: 7711e4a3def5c179cf95ec2525a6263819155916. Diff is one RFC file, +24/−5.
  • No prior reviews or review comments on this head.
  • Local: python3 scripts/check-docs.py at that head. Result: Documentation OK (400 Markdown files checked).
  • CI on that head: PR Title fails as above. Fix Regression Gate passes. cargo deny fails on the parent advisory.
  • A GQT regression does not apply. The change is documentation. It changes no rows, counts, result types, or errors. No temporary case was added.
  • No Lance-shaped behavior changes. The credential check is data_tokens.rs and RFC 0053. Lance format pages are outside this amendment.
  • The 2026-10-25 exchange observation and Studio’s screen text are outside this repository.

…-token exception

Restores A2's opening rule, so the self-hosted OAuth rule stays in force, and keeps the exception to Studio on a managed cluster a person selects inside Studio; the launcher stays refused. The exception ends when the managed control plane enables the resource profile and registers Studio and Studio implements the client. It describes `credentials export` as the CLI behaves: the cached data token while it validates, a 3600-second mint the issuer may shorten, up to 86400 seconds for a cached `cluster token`, the same token after a 401, and a failed export on an endpoint pin mismatch. The decision-log entry names the qualified sentence and the unchanged launcher refusal.
@ragnorc ragnorc changed the title docs(rfc): let Studio reach managed clusters on the CLI's login until its OAuth client lands rfc: let Studio reach managed clusters on the CLI's login until its OAuth client lands Oct 25, 2026
@cursor

cursor Bot commented Oct 25, 2026

Copy link
Copy Markdown

Review

I recommend merge. Commit 55cf2c89 answers the four blocking findings from the review on 1a567f71. The exception now states the CLI export contract, keeps one end, leaves the launcher refused, and the PR title passes the title check. The new text adds no blocking defect.

This amendment lets Studio send the CLI's managed data token until Studio can be a standard OAuth public client. The token is a reusable bearer. Studio and the CLI are one Cedar principal. The server cannot give Studio a smaller grant. The text now matches the command that prints that token.

Earlier findings

  1. Export contract — resolved. The interim states the cache rule, the 3600-second mint, the issuer's right to shorten the token, the CLI refusal past that request plus 30 seconds, and the 86400-second ceiling for a cached cluster token. It states that a new export after a 401 returns the same token while the cache validates, and that an endpoint-pin mismatch fails the export. Those sentences match acquire_resource, issue_for_principal, and Credential::validate in crates/omnigraph-cli/src/managed/data.rs.

  2. Self-hosted rule and one end — resolved. The opening sentence is the accepted sentence again. A self-hosted OIDC server still requires a standard OAuth public client. The exception ends only when the managed control plane enables the resource profile and registers Studio, and Studio implements the client. The 2026-10-25 decision-log entry names that same end.

  3. Launcher refusal — resolved. The target table still says "Deferred to A2" for studio. Opening Studio is unchanged. The exception says the launcher stays refused, and Studio uses the token only after a person selects a managed target inside Studio. The decision log names that choice. PR feat(cli): open Studio on a configured server target #1137 still refuses a managed target with target_kind_unsupported before it mints a link.

  4. PR title — resolved. The title is rfc: let Studio reach managed clusters on the CLI's login until its OAuth client lands. scripts/check-pr-title.py accepts it (86/100). The PR Title check on this head passed. CI Gate still fails cargo deny on RUSTSEC-2026-0387 (crypto-bigint 0.5.5). That advisory is on parent 7711e4a3. PR fix(server): remove crypto-bigint 0.5 for RUSTSEC-2026-0387 #1151 addresses it. This diff does not.

Optional

  1. Profile name — partly resolved; the remainder is open. The body calls the export the CLI's data token, and it says an omnigraph profile keeps the CLI copy in the OS keychain. credentials export --target also returns a data token for a token or exec profile (ResourceStore::for_profile). Those profiles are not a login. The text leaves the profile name out of Studio's duties. target inspect is offline and already includes that field.

  2. Export limits — resolved. The exception records the Unix FIFO, the refusal to start an interactive login, the keychain copy, the pipe reader, the shared Cedar principal, policy denial on the next request, and non-revocation. The opening sentence still says "removing admission". That sentence is the accepted text, and this RFC also uses admission for the cluster writer lock. The team guide calls the same non-revocation organization membership and provider membership (docs/user/cli/managed-team.md). The behavior matches: membership removal does not revoke the bearer.

  3. Tracker and neighboring PRs — still open. Issue track: OAuth for standard clients and managed access from Studio #1025 still forbids borrowing these tokens and has no interim. PR rfc: narrow graph access to client contracts #1149 edits this same RFC, and its A2 has no exception. A rebase of either PR has to keep this paragraph. PR feat(cli): open Studio on a configured server target #1137 also edits Opening Studio (the Linux opener) and the updated date, so the decision-log entries have to both survive. PR fix(cli): support managed introspection and mcp credential renewal #1146 still renews a managed credential when the local cache is expired, which is the cache rule this text now states, and it does not open Studio on a managed target. Appendix A3 remains a separate proposal: an MCP relay would keep the token inside the CLI.

Blocking

None.

Tradeoffs and liability

The accepted costs match the token. IdentityTokenClaims has no client id. The server derives principal:<sub> (AuthenticatedActor::signed_identity). The same Cedar principal applies to every holder. jti is not a revocation list. RFC 0053 states that an issued identity stays authenticated until expiry, or until the serving process restarts with trust that excludes its signing key. Login-session revocation stops a later mint. It leaves a token already in Studio's memory valid. The exception uses the guide's shorter form, "signing key retires".

Invariant 10 stays intact at the server. The client does not supply an actor. The loss is client separation: Studio and the CLI are one principal for up to 24 hours, and removing organization membership does not cut that window short. A policy change denies that principal on a later request. RFC 0053 states that an already admitted mutation or stream runs to its end.

The diff is one RFC file. The code remains the source of the export behavior. The RFC adds one prose exception with one end. Removal deletes that paragraph and the decision-log entry. The launcher cell stays "Deferred to A2", so the opener rule stays one sentence. Five similar interims would be five paragraphs in A2. That shape is easier to delete than five borrowed paths hidden in the target table.

Robustness of client separation is the quality this interim gives up. That is a fair short-term choice before the control plane registers Studio. Clarity has to carry the bound, because code does not expire the exception. This commit makes that bound true.

What matches

  • credentials export --target on a managed cluster calls export_credential, which returns the data bearer. Set, status, and remove on that target bail and require --profile (planes.rs, commands/targets/mod.rs).
  • A cache entry that passes Credential::validate is returned as-is. A new token is requested only for data_credential_required, data_credential_expired, or data_credential_identity_mismatch. That request sends ttl_seconds: 3600. The CLI rejects an expiry later than now plus that TTL plus 30 seconds. The issuer may return a shorter lifetime. parse_ttl accepts 60 to 86400 seconds for cluster token --ttl, and a later export prints that cached credential.
  • A 401 from the data server is outside the refresh set. data_endpoint_mismatch returns an error and does not mint a replacement. A missing endpoint fails with data_endpoint_required before a mint.
  • Export checks stdout_is_pipe before acquisition. The check accepts a Unix FIFO and refuses other platforms, terminals, and regular files. The export path returns login_required when no session exists. It can refresh a control session or run an exec helper when it mints. It does not start device-code login.
  • target inspect reads the local target, including kind: managed and the endpoint pin.
  • The server verifies the bearer offline. Applied Cedar policy authorizes the principal on each request. The token has identity claims only.

RFC process

The amendment qualifies the accepted A2 sentence and leaves that sentence in the body. The 2026-10-25 log entry quotes it, limits the qualification to Studio on a managed cluster a person selects inside Studio, and states that the launcher refusal is unchanged. updated is 2026-10-25. Status stays accepted. The body alone carries the current rule. That meets process step 4.

Validation

  • Reviewed head: 55cf2c89d2985f3c93fc4e013ab31ae06c76c8ce. Prior review head: 1a567f710347ac258783b7d6b9cf819496ffe133. Parent: 7711e4a3def5c179cf95ec2525a6263819155916.
  • The follow-up diff is one RFC file, +36/−22. The full PR diff is that same file, +34/−1.
  • This head has the one earlier issue comment and no pull-request review.
  • Local: python3 scripts/check-docs.py at this head. Result: Documentation OK (400 Markdown files checked).
  • Local: python3 scripts/check-pr-title.py on the PR title. Result: PASS, 86/100.
  • CI on this head: PR Title passed (run 40162263490). Fix Regression Gate passed. CI Gate failed because cargo deny reports RUSTSEC-2026-0387. That failure is the parent advisory.
  • A GQT regression does not apply. The change is documentation. It changes no rows, counts, result column types, or errors. I did not add a temporary case.
  • This amendment does not change a Lance surface. The credential check is data_tokens.rs and RFC 0053.

Recommendation: merge.

@ragnorc
ragnorc merged commit d5bf9fe into ModernRelay:main Oct 25, 2026
31 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant