feat: offline delegation for the Scheduler - #1815
Draft
DmytroZaichenkoDev wants to merge 21 commits into
Draft
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
User level now reflects the caller's platform-wide offline credentials instead of per-service records this type never writes. App level already meant approved. Also annotates the new endpoints for the OpenAPI spec. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DmytroZaichenkoDev
force-pushed
the
feat/offline-delegation
branch
from
August 10, 2026 17:11
8d91e21 to
4325659
Compare
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
…d paths Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Collaborator
|
Let’s split the task into smaller pieces. |
Collapse the stray double blank lines, move the newAppKey helper below the tests that use it, and restore the trailing newlines and the one-line userRoles array the config fixtures lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- an opaque access token no longer surfaces as a 500: with several providers configured there is no issuer to match on, so refuse with 503 - a request carrying no bearer token is a bad request, not an NPE - a single configured provider no longer ignores the recorded issuer. When it has an issuerPattern that disclaims that issuer, the record was minted by a provider that is gone, and refreshing it against this one would use the wrong client - provider resolution failures during redemption map to 503 rather than 400, which blamed the caller for server-side state - the status enricher returns early for a userless caller instead of guarding downstream of code that already fails on a null user id, which also removes an intermittent 500 on the external-services listing for an admin API key Shares asRuntime through ExternalServiceErrors rather than duplicating it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The enricher's early return for a null user id skipped the base status computation entirely, dropping app-level statuses the raw resource GET previously reported for API-key callers. Only the offline-credentials lookup needs a user: a userless caller holds none by definition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The consent record lives at APPLICATION level under the same scope the generic sign-out addresses, and sign-out admits app owners with write access — so an owner could withdraw an administrator's consent, with no consent audit event. There is nothing legitimate to sign out of at any level: withdrawal belongs to the audited, admin-only consent endpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
putCredentialsRecord now sets createdAt/updatedAt from the service's TimeProvider, replacing the controller's direct System.currentTimeMillis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"connected": false alone could not tell "not connected yet" from "cannot connect": a provider with no offline client left chat offering a flow that could only end in an error, and an unresolvable provider turned the status GET into a 503. The status now carries "available", and every way the feature can be unusable collapses into false — the loud refusal stays on sign-in, where it matters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Includes the Entra nuance: issuerPattern must also match the ID token's issuer, which differs from the v1 access-token issuer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Offline delegation for the Scheduler
Closes #1814
Lets a headless application act as a user who is not present — a scheduled run calling DIAL as
the person who set it up, days later — without that user logging in per application.
Two decisions, made by two people. The user enables offline credentials once, platform-wide:
an ordinary authorization-code flow with
offline_access, stored in their own bucket. A DIALadmin approves a specific (application, service) pair. Neither works alone — credentials with no
consent reach no application, consent with no credentials has no identity to act with.
The admin's approval reaches every user who has enabled offline access, not only users who
opted into that application. That is a deliberate trade — it is what keeps the feature deployable
without prompting every user about every app — and it is the thing most worth arguing with in
review.
What is here
GET/POST /v1/user/offline-credentials,/signin,/signoutPOST/DELETE /v1/applications/{appId}/external-services/{id}/consentDIAL_NATIVEauthentication type/obo-credentialsNotable decisions a reviewer should push on:
attribution and a core-controlled expiry, at the cost of grant machinery and client changes.
Deferred, not dismissed.
fails closed when there is no ID token — otherwise nothing refuses code injection.
offlinestorage id is reserved and sits outside theapplications/…/external_services/…shape the app-facing credential endpoints can address, so no application can reach it. Pinned by
CredentialsPathReservationTest.whether a given user has credentials.
🤖 Generated with Claude Code